
// localFilesystem provides functions to read and write to the filesystem

package localFilesystem

import "seekia/internal/globalSettings"
import "seekia/internal/appMemory"

import goFilepath "path/filepath"

import "os"
import "errors"


// This must be run upon application startup
// It also must be run before certain tests
func InitializeAppDatastores()error{

	seekiaDirectoryPath, err := GetSeekiaDataFolderPath()
	if (err != nil) { return err }

	_, err = CreateFolder(seekiaDirectoryPath)
	if (err != nil) { return err }

	err = globalSettings.InitializeGlobalSettingsDatastore()
	if (err != nil) { return err }

	databaseFolderPath, err := GetAppDatabaseFolderPath()
	if (err != nil) { return err }

	_, err = CreateFolder(databaseFolderPath)
	if (err != nil) { return err }

	userDataFolderPath, err := GetAppUsersDataFolderPath()
	if (err != nil) { return err }

	_, err = CreateFolder(userDataFolderPath)
	if (err != nil) { return err }

	parametersFolderpath := goFilepath.Join(seekiaDirectoryPath, "Parameters")
	parametersNetwork1Folderpath := goFilepath.Join(parametersFolderpath, "Network1")
	parametersNetwork2Folderpath := goFilepath.Join(parametersFolderpath, "Network2")

	_, err = CreateFolder(parametersFolderpath)
	if (err != nil) { return err }
	_, err = CreateFolder(parametersNetwork1Folderpath)
	if (err != nil) { return err }
	_, err = CreateFolder(parametersNetwork2Folderpath)
	if (err != nil) { return err }

	return nil
}

// This returns the folderpath where Seekia globalSettings and userData is stored
// It may also contain the database, unless the user has manually changed the database location
func GetSeekiaDataFolderPath() (string, error) {

	localFileDirectory, err := os.UserConfigDir() 
	if (err != nil) { return "", err }
	
	seekiaDirectory := goFilepath.Join(localFileDirectory, "SeekiaData")

	return seekiaDirectory, nil
}

// This folder stores all app user data within it
// Each user has a folder, the name being the name of the user
func GetAppUsersDataFolderPath()(string, error){

	seekiaDirectoryPath, err := GetSeekiaDataFolderPath()
	if (err != nil) { return "", err }

	appUsersDirectory := goFilepath.Join(seekiaDirectoryPath, "UserData")

	return appUsersDirectory, nil
}

// This returns the folder where the currently signed-in user's data is stored
func GetAppUserFolderPath() (string, error) {

	exists, appUserName := appMemory.GetMemoryEntry("AppUser")
	if (exists == false){
		return "", errors.New("GetAppUserFolderPath called when user is not signed in.")
	}

	appUsersFolderPath, err := GetAppUsersDataFolderPath()
	if (err != nil) { return "", err }
	
	userDirectory := goFilepath.Join(appUsersFolderPath, appUserName)	

	return userDirectory, nil
}

// This folder location is customizable in the app.
func GetAppDatabaseFolderPath()(string, error){

	exists, directoryPath, err := globalSettings.GetSetting("DatabaseFolderpath")
	if (err != nil) { return "", err }
	if (exists == true){
		return directoryPath, nil
	}

	seekiaDirectory, err := GetSeekiaDataFolderPath()
	if (err != nil) { return "", err }

	databasePath := goFilepath.Join(seekiaDirectory, "SeekiaDatabase")

	return databasePath, nil
}


// Function will create new file or overwite existing:
func CreateOrOverwriteFile(content []byte, folderPath string, filename string) error{

	_, err := CreateFolder(folderPath)
	if (err != nil) { return err }

	filepath := goFilepath.Join(folderPath, filename)

	newFile, err := os.Create(filepath)
	if (err != nil) { return err }

	_, err = newFile.Write(content)
	if (err != nil) {
		newFile.Close()
		return err
	}
	newFile.Close()

	return nil
}

//Outputs:
//	-bool: File exists
//	-[]byte: File contents
//	-error
func GetFileContents(filePath string) (bool, []byte, error){

	fileContents, err := os.ReadFile(filePath)
	if (err == nil) {
		return true, fileContents, nil
	}

	isNotExistError := os.IsNotExist(err)
	if (isNotExistError == false){
		return false, nil, err
	}

	emptyFileContents := make([]byte, 0)
	return false, emptyFileContents, nil
}

//Outputs:
//	-[][]byte: List of each file's contents
//	-error
func GetAllFilesInFolderAsList(folderPath string)([][]byte, error){

	folderMap, err := GetFolderContentsAsMap(folderPath)
	if (err != nil) { return nil, err }

	filesList := make([][]byte, 0, len(folderMap))

	for _, fileContents := range folderMap{

		filesList = append(filesList, fileContents)
	}

	return filesList, nil
}

// Outputs:
//	-bool: Folder exists
//	-[]string: Filenames for all files in folder
//	-error
func GetAllFilenamesInFolder(folderPath string)(bool, []string, error) {

	fileList, err := os.ReadDir(folderPath)
	if (err != nil) {

		isNotExistError := os.IsNotExist(err)
		if (isNotExistError == true){
			return false, nil, nil
		}

		return false, nil, err
	}

	filenamesList := make([]string, 0)

	for _, filesystemObject := range fileList{

		filepathIsFolder := filesystemObject.IsDir()
		if (filepathIsFolder == true){
			continue
		}

		fileName := filesystemObject.Name()

		filenamesList = append(filenamesList, fileName)
	}

	return true, filenamesList, nil
}

// Outputs:
//	-bool: Folder exists
//	-int: Quantity of files in folder
//	-error
func GetQuantityOfFilesInFolder(folderPath string)(bool, int, error) {

	fileList, err := os.ReadDir(folderPath)
	if (err != nil) {

		isNotExistError := os.IsNotExist(err)
		if (isNotExistError == true){
			return false, 0, nil
		}

		return false, 0, err
	}

	quantityOfFilesInFolder := 0

	for _, filesystemObject := range fileList{

		filepathIsFolder := filesystemObject.IsDir()
		if (filepathIsFolder == false){
			quantityOfFilesInFolder += 1
		}
	}

	return true, quantityOfFilesInFolder, nil
}


// Outputs:
//	-map[string][]byte: File name -> File Contents
//	-error
func GetFolderContentsAsMap(folderPath string)(map[string][]byte, error) {

	fileList, err := os.ReadDir(folderPath)
	if (err != nil) { return nil, err }

	folderMap := make(map[string][]byte)

	for _, filesystemObject := range fileList{

		filepathIsFolder := filesystemObject.IsDir()
		if (filepathIsFolder == true){
			continue
		}

		fileName := filesystemObject.Name()
		filePath := goFilepath.Join(folderPath, fileName)

		fileBytes, err := os.ReadFile(filePath)
		if (err != nil){ return nil, err }

		folderMap[fileName] = fileBytes
	}

	return folderMap, nil
}

// This does not work with nested folders
// You must create all parent folders before calling this function on a folderPath
//Outputs:
//	-bool: Folder already exists
//	-error
func CreateFolder(folderPath string)(bool, error){

	filesystemObject, err := os.Open(folderPath)
	if (err == nil){
		// Folder already exists
		filesystemObject.Close()
		return true, nil
	}

	isNotExistErr := os.IsNotExist(err)
	if (isNotExistErr == false){
		return false, err
	}

	//Folder does not exist, create folder

	err = os.Mkdir(folderPath, os.ModePerm)
	if (err != nil){ return false, err }

	return false, nil
}

func CheckIfFileExists(filepath string)(bool, error){

	_, err := os.Stat(filepath)
	if (err != nil) {
		isNotExistErr := os.IsNotExist(err)
		if (isNotExistErr == true) {
			// File does not exist
			return false, nil
		}
		return false, err
	}

	return true, nil
}

// This function works for files and directories
func DeleteFileOrFolder(filepath string)error{

	err := os.RemoveAll(filepath)
	if (err != nil) { return err }

	return nil
}

//Outputs:
//	-bool: Folder exists and its contents were deleted
//	-error
func DeleteAllFolderContents(inputFolderpath string)(bool, error){

	fileList, err := os.ReadDir(inputFolderpath)
	if (err != nil) {
		isNotExistError := os.IsNotExist(err)
		if (isNotExistError == true){
			// Folder does not exist, nothing to delete
			return false, nil
		}
		return false, err
	}

	for _, filesystemObject := range fileList{

		fileName := filesystemObject.Name()
		filePath := goFilepath.Join(inputFolderpath, fileName)

		err := os.RemoveAll(filePath)
		if (err != nil){ return false, err }
	}

	return true, nil
}


func GetFolderSizeInBytes(folderPath string)(int64, error){

	sizeInBytes := int64(0)

	filesList, err := os.ReadDir(folderPath)
	if (err != nil) { return 0, err }

	for _, fileObject := range filesList{

		isFolder := fileObject.IsDir()
		if (isFolder == true){

			subFolderName := fileObject.Name()

			subFolderPath := goFilepath.Join(folderPath, subFolderName)

			subFolderSize, err := GetFolderSizeInBytes(subFolderPath)
			if (err != nil) { return 0, err }
			
			sizeInBytes += subFolderSize

			continue
		}

		fileName := fileObject.Name()
		filePath := goFilepath.Join(folderPath, fileName)
		fileExists, fileSize, err := GetFileSize(filePath)
		if (err != nil) { return 0, err }
		if (fileExists == false) {
			return 0, errors.New("File not found after being found during GetFolderSizeInBytes.")
		}

		sizeInBytes += fileSize
	}
	
	return sizeInBytes, nil
}

//Outputs:
//	-bool: File exists
//	-int64: Size in bytes
//	-error
func GetFileSize(filepath string)(bool, int64, error){

	fileObject, err := os.Stat(filepath)
	if (err != nil){
		isNotExistError := os.IsNotExist(err)
		if (isNotExistError == true){
			return false, 0, nil
		}
		return false, 0, err
	}

	fileSizeBytes := fileObject.Size()

	return true, fileSizeBytes, nil
}


