
// myMap provides functions for managing user maps
// Maps are concurrency safe, and are stored in-memory and on disk.

package myMap

import "seekia/internal/localFilesystem"
import "seekia/internal/appMemory"

import "encoding/json"
import "path/filepath"
import "sync"
import "errors"
import "maps"


func InitializeMyMapsFolder()error{

	userDirectory, err := localFilesystem.GetAppUserFolderPath()
	if (err != nil) { return err }

	myMapsFolderpath := filepath.Join(userDirectory, "MyMaps")

	_, err = localFilesystem.CreateFolder(myMapsFolderpath)
	if (err != nil) { return err }

	return nil
}


type MyMap struct{

	// Name of the map. The map filename is derived from this name.
	mapName string

	// The folderpath where the map file is stored.
	mapFolderpath string

	// We lock this whenever we are editing the memory map and map file
	updatingMapMutex sync.Mutex

	// We lock this when we edit the memoryMap
	// memoryMutex only needs to be RLock/RUnlocked by GetMap/GetMapEntry, because updatingMapMutex is locked for all other reads
	memoryMutex sync.RWMutex 

	// The map stored in memory
	memoryMap map[string]string
}

func CreateNewMap(newMapName string)(*MyMap, error){

	if (newMapName == ""){
		return nil, errors.New("CreateNewMap called with empty myMap name")
	}

	var newMap MyMap

	newMap.mapName = newMapName

	userDirectory, err := localFilesystem.GetAppUserFolderPath()
	if (err != nil) { return nil, err }

	mapFolderpath := filepath.Join(userDirectory, "MyMaps")

	newMap.mapFolderpath = mapFolderpath

	mapFilepath := filepath.Join(mapFolderpath, newMapName + "Map.json")

	getMapFromFile := func()(map[string]string, error){

		fileExists, fileBytes, err := localFilesystem.GetFileContents(mapFilepath)
		if (err != nil) { return nil, err }
		if (fileExists == false){

			emptyMap := make(map[string]string)
			return emptyMap, nil
		}

		currentMap := make(map[string]string)

		err = json.Unmarshal(fileBytes, &currentMap)
		if (err != nil) {
			return nil, errors.New("Stored myMap is corrupted: " + newMapName) 
		}

		return currentMap, nil
	}

	mapFromFile, err := getMapFromFile()
	if (err != nil) { return nil, err }

	newMap.memoryMap = mapFromFile

	return &newMap, nil
}


func (inputMapObject *MyMap) GetMap()(map[string]string, error){
	
	if (inputMapObject == nil){
		return nil, errors.New("GetMap called when MyMap is not initialized.")
	}

	exists, _ := appMemory.GetMemoryEntry("AppUser")
	if (exists == false){
		return nil, errors.New("GetMap called when no user is signed in.")
	}

	inputMapObject.memoryMutex.RLock()

	currentMap := inputMapObject.memoryMap

	mapCopy := maps.Clone(currentMap)

	inputMapObject.memoryMutex.RUnlock()

	return mapCopy, nil
}

//Outputs:
//	-bool: Entry exists
//	-string: Entry value
//	-error
func (inputMapObject *MyMap) GetMapEntry(key string)(bool, string, error){

	if (inputMapObject == nil){
		return false, "", errors.New("GetMapEntry called when MyMap is not initialized.")
	}

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

	inputMapObject.memoryMutex.RLock()
	value, exists := inputMapObject.memoryMap[key]
	inputMapObject.memoryMutex.RUnlock()

	if (exists == false){
		return false, "", nil
	}

	return true, value, nil
}

func (inputMapObject *MyMap) SetMapEntry(key string, value string)error{

	if (inputMapObject == nil){
		return errors.New("SetMapEntry called when MyMap not initialized.")
	}

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

	// We see if identical entry already exists
	exists, currentValue, err := inputMapObject.GetMapEntry(key)
	if (err != nil) { return err }
	if (exists == true && currentValue == value){
		return nil
	}

	inputMapObject.updatingMapMutex.Lock()
	defer inputMapObject.updatingMapMutex.Unlock()

	inputMapObject.memoryMutex.Lock()
	inputMapObject.memoryMap[key] = value
	inputMapObject.memoryMutex.Unlock()

	mapFolderpath := inputMapObject.mapFolderpath
	mapName := inputMapObject.mapName

	newMap := inputMapObject.memoryMap

	err = overwriteMapFileWithMap(mapFolderpath, mapName, newMap)
	if (err != nil) { return err }

	return nil
}

func (inputMapObject *MyMap) DeleteMapEntry(key string)error{

	if (inputMapObject == nil){
		return errors.New("DeleteMapEntry called when MyMap is not initialized.")
	}

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

	// We see if entry exists
	exists, _, err := inputMapObject.GetMapEntry(key)
	if (err != nil) { return err }
	if (exists == false){
		// Nothing to delete
		return nil
	}

	inputMapObject.updatingMapMutex.Lock()
	defer inputMapObject.updatingMapMutex.Unlock()

	inputMapObject.memoryMutex.Lock()
	delete(inputMapObject.memoryMap, key)
	inputMapObject.memoryMutex.Unlock()

	mapFolderpath := inputMapObject.mapFolderpath
	mapName := inputMapObject.mapName

	newMap := inputMapObject.memoryMap

	err = overwriteMapFileWithMap(mapFolderpath, mapName, newMap)
	if (err != nil) { return err }

	return nil
}

func (inputMapObject *MyMap) DeleteMap() error{

	if (inputMapObject == nil){
		return errors.New("DeleteMap called when MyMap is not initialized.")
	}

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

	inputMapObject.updatingMapMutex.Lock()
	defer inputMapObject.updatingMapMutex.Unlock()

	emptyMap := make(map[string]string)

	inputMapObject.memoryMutex.Lock()
	inputMapObject.memoryMap = emptyMap
	inputMapObject.memoryMutex.Unlock()

	mapFolderpath := inputMapObject.mapFolderpath
	mapName := inputMapObject.mapName

	err := overwriteMapFileWithMap(mapFolderpath, mapName, emptyMap)
	if (err != nil) { return err }

	return nil
}


func (inputMapObject *MyMap) OverwriteMap(newMap map[string]string) error{

	if (inputMapObject == nil){
		return errors.New("OverwriteMap called when MyMap is not initialized.")
	}

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

	inputMapObject.updatingMapMutex.Lock()
	defer inputMapObject.updatingMapMutex.Unlock()

	inputMapObject.memoryMutex.Lock()
	inputMapObject.memoryMap = newMap
	inputMapObject.memoryMutex.Unlock()

	mapFolderpath := inputMapObject.mapFolderpath
	mapName := inputMapObject.mapName

	err := overwriteMapFileWithMap(mapFolderpath, mapName, newMap)
	if (err != nil) { return err }

	return nil
}


func overwriteMapFileWithMap(mapFolderpath string, mapName string, inputMap map[string]string)error{
	
	fileContents, err := json.MarshalIndent(inputMap, "", "\t")
	if (err != nil) { return err }

	mapFileName := mapName + "Map.json"
	
	err = localFilesystem.CreateOrOverwriteFile(fileContents, mapFolderpath, mapFileName)
	if (err != nil) { return err }

	return nil
}


