
// myList provides functions to create and manage user string lists
// Lists are concurrency safe, and are stored in-memory and on disk.
// Examples of lists include matches, viewed hosts, and viewed moderators

package myList

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

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

//TODO: We might want to create a new file, rename, delete old file, and rename new file, so we can better protect against losing power or computer crashing and data being lost?

func InitializeMyListsFolder()error{

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

	myListsFolderpath := filepath.Join(userDirectory, "MyLists")

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

	return nil
}

type MyList struct{

	// Name of list. Name of file is derived from this name
	listName string

	// Folderpath of the list file.
	listFolderpath string

	// We lock this whenever we are updating the memory list and list file
	updatingListMutex sync.Mutex

	// We lock this when we edit the memoryList
	// memoryMutex only needs to be RLock/RUnlocked by GetList, because updatingListMutex is locked for all other reads
	memoryMutex sync.RWMutex

	// The list stored in memory
	memoryList []string
}

func CreateNewList(newListName string)(*MyList, error){

	if (newListName == ""){
		return nil, errors.New("CreateNewList called with empty myList name.")
	}

	var newList MyList

	newList.listName = newListName

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

	listFolderpath := filepath.Join(userDirectory, "MyLists")

	newList.listFolderpath = listFolderpath

	getListFromFile := func()([]string, error){

		listFilepath := filepath.Join(listFolderpath, newListName + "List.json")
		
		fileExists, fileBytes, err := localFilesystem.GetFileContents(listFilepath)
		if (err != nil) { return nil, err }
		if (fileExists == false || len(fileBytes) == 0){
			emptyList := make([]string, 0)
			return emptyList, nil
		}

		var currentList []string

		err = json.Unmarshal(fileBytes, &currentList)
		if (err != nil){ return nil, err }

		return currentList, nil
	}

	listFromFile, err := getListFromFile()
	if (err != nil) { return nil, err }

	newList.memoryList = listFromFile

	return &newList, nil
}

func (listObject *MyList) GetList()([]string, error){

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

	listObject.memoryMutex.RLock()

	currentList := listObject.memoryList
	listCopy := slices.Clone(currentList)

	listObject.memoryMutex.RUnlock()

	return listCopy, nil
}

func (listObject *MyList) AddListItem(newItem string)error{
	
	if (listObject == nil){
		return errors.New("AddListItem called when MyList is not initialized.")
	}

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

	listObject.updatingListMutex.Lock()
	defer listObject.updatingListMutex.Unlock()

	listObject.memoryMutex.Lock()

	currentList := listObject.memoryList
	currentList = append(currentList, newItem)
	listObject.memoryList = currentList

	listObject.memoryMutex.Unlock()

	listFolderpath := listObject.listFolderpath
	listName := listObject.listName

	err := overwriteListFileWithList(listFolderpath, listName, currentList)
	if (err != nil) { return err }
	
	return nil
}

func (listObject *MyList) DeleteListItem(item string)error{

	if (listObject == nil){
		return errors.New("DeleteListItem called when MyList is not initialized.")
	}

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

	listObject.updatingListMutex.Lock()
	defer listObject.updatingListMutex.Unlock()

	listObject.memoryMutex.Lock()

	currentList := listObject.memoryList

	newList, anyDeleted := helpers.DeleteAllMatchingItemsFromList(currentList, item)
	if (anyDeleted == false){
		listObject.memoryMutex.Unlock()
		return nil
	}

	listObject.memoryList = newList
	listObject.memoryMutex.Unlock()

	listFolderpath := listObject.listFolderpath
	listName := listObject.listName

	err := overwriteListFileWithList(listFolderpath, listName, newList)
	if (err != nil) { return err }
		
	return nil
}

func (listObject *MyList) DeleteList()error{

	if (listObject == nil){
		return errors.New("DeleteList called when MyList is not initialized.")
	}

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

	listObject.updatingListMutex.Lock()
	defer listObject.updatingListMutex.Unlock()

	emptyList := make([]string, 0)

	listObject.memoryMutex.Lock()
	listObject.memoryList = emptyList
	listObject.memoryMutex.Unlock()

	listFolderpath := listObject.listFolderpath
	listName := listObject.listName

	err := overwriteListFileWithList(listFolderpath, listName, emptyList)
	if (err != nil) { return err }

	return nil
}

// Note: Input list is not copied
func (listObject *MyList) OverwriteList(newList []string) error{

	if (listObject == nil){
		return errors.New("OverwriteList called when MyList is not initialized.")
	}

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

	listObject.updatingListMutex.Lock()
	defer listObject.updatingListMutex.Unlock()

	listObject.memoryMutex.Lock()
	listObject.memoryList = newList
	listObject.memoryMutex.Unlock()

	listFolderpath := listObject.listFolderpath
	listName := listObject.listName
	
	err := overwriteListFileWithList(listFolderpath, listName, newList)
	if (err != nil) { return err }

	return nil
}


func overwriteListFileWithList(listFolderpath string, listName string, inputList []string)error{

	newFileBytes, err := json.MarshalIndent(inputList, "", "\t")
	if (err != nil) { return err }

	listFileName := listName + "List.json"

	err = localFilesystem.CreateOrOverwriteFile(newFileBytes, listFolderpath, listFileName)
	if (err != nil) { return err }

	return nil
}



