/*
 * Copyright (C) 2024  Puter Technologies Inc.
 *
 * This file is part of puter-fuse.
 *
 * puter-fuse is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package engine

import (
	"path/filepath"
	"sync"

	"github.com/HeyPuter/puter-fuse/putersdk"
	"github.com/HeyPuter/puter-fuse/services"
)

type NodeType int

const (
	Dir NodeType = iota
	File
	Symlink
)

type NodeInfo struct {
	Path string
	Name string
	Type NodeType
	Size uint64
}

type PendingNodeService struct {
	LookupTablePath   map[string]*NodeInfo
	LookupTableParent map[string][]*NodeInfo

	// locks
	LookupTablePathLock   sync.RWMutex
	LookupTableParentLock sync.RWMutex

	// services
	services services.IServiceContainer
}

func (svc *PendingNodeService) Init(services services.IServiceContainer) {
	svc.LookupTablePath = map[string]*NodeInfo{}
	svc.LookupTableParent = map[string][]*NodeInfo{}
	svc.services = services
}

func (svc *PendingNodeService) Link(parent, name string, typ NodeType) *NodeInfo {
	// normalize parent path
	if parent[len(parent)-1] == '/' {
		parent = parent[:len(parent)-1]
	}

	nodeInfo := &NodeInfo{
		Path: filepath.Join(parent, name),
		Name: name,
		Type: typ,
	}

	// add to path lookup table
	path := filepath.Join(parent, name)
	svc.LookupTablePathLock.Lock()
	svc.LookupTablePath[path] = nodeInfo
	svc.LookupTablePathLock.Unlock()

	// add to parent lookup table
	svc.LookupTableParentLock.Lock()
	svc.LookupTableParent[parent] = append(svc.LookupTableParent[parent], nodeInfo)
	svc.LookupTableParentLock.Unlock()

	return nodeInfo
}

func (svc *PendingNodeService) GetNodeInfo(path string) *NodeInfo {
	svc.LookupTablePathLock.RLock()
	nodeInfo, _ := svc.LookupTablePath[path]
	svc.LookupTablePathLock.RUnlock()
	return nodeInfo
}

// The caller of this method must aquire a lock
func (svc *PendingNodeService) SetFileData(path string, data []byte) int {
	svc_wfcache := svc.services.Get("wfcache").(*WholeFileCacheService)
	svc.LookupTablePathLock.Lock()
	ver := svc_wfcache.SetFileData(path, data)
	nodeInfo := svc.LookupTablePath[path]
	nodeInfo.Size = uint64(len(data))
	svc.LookupTablePath[path] = nodeInfo
	svc.LookupTablePathLock.Unlock()
	return ver
}

func (svc *PendingNodeService) Forget(parent, name string) {
	// normalize parent path
	if parent[len(parent)-1] == '/' {
		parent = parent[:len(parent)-1]
	}

	// remove from path lookup table
	path := filepath.Join(parent, name)
	svc.LookupTablePathLock.Lock()
	delete(svc.LookupTablePath, path)
	svc.LookupTablePathLock.Unlock()

	// remove from parent lookup table
	svc.LookupTableParentLock.Lock()
	for i, node := range svc.LookupTableParent[parent] {
		if node.Path == path {
			svc.LookupTableParent[parent] = append(svc.LookupTableParent[parent][:i], svc.LookupTableParent[parent][i+1:]...)
			break
		}
	}
	svc.LookupTableParentLock.Unlock()
}

func (svc *PendingNodeService) GetChildren(parent string) []*NodeInfo {
	svc.LookupTableParentLock.RLock()
	children := svc.LookupTableParent[parent]
	svc.LookupTableParentLock.RUnlock()
	return children
}

func NodeInfoToArtificialCloudItem(nodeInfo *NodeInfo) putersdk.CloudItem {
	return putersdk.CloudItem{
		IsPending: true,
		Name:      nodeInfo.Name,
		Path:      nodeInfo.Path,
		IsDir:     nodeInfo.Type == Dir,
		Size:      nodeInfo.Size,
		// TODO: both of these won't be used once Local UIDs are used
		RemoteUID: "pending://" + nodeInfo.Path,
		Id:        "pending://" + nodeInfo.Path,
	}
}
