// Copyright (c) Acrosync LLC. All rights reserved.
// Free for personal use and commercial trial
// Commercial use requires per-user licenses available from https://duplicacy.com

package duplicacy

import (
	"fmt"
	"strings"
)

type HubicStorage struct {
	StorageBase

	client          *HubicClient
	storageDir      string
	numberOfThreads int
}

// CreateHubicStorage creates an Hubic storage object.
func CreateHubicStorage(tokenFile string, storagePath string, threads int) (storage *HubicStorage, err error) {

	for len(storagePath) > 0 && storagePath[len(storagePath)-1] == '/' {
		storagePath = storagePath[:len(storagePath)-1]
	}

	client, err := NewHubicClient(tokenFile)
	if err != nil {
		return nil, err
	}

	exists, isDir, _, err := client.GetFileInfo(storagePath)
	if err != nil {
		return nil, err
	}

	if !exists {
		return nil, fmt.Errorf("Path '%s' doesn't exist", storagePath)
	}

	if !isDir {
		return nil, fmt.Errorf("Path '%s' is not a directory", storagePath)
	}

	storage = &HubicStorage{
		client:          client,
		storageDir:      storagePath,
		numberOfThreads: threads,
	}

	for _, path := range []string{"chunks", "snapshots"} {
		dir := storagePath + "/" + path
		exists, isDir, _, err := client.GetFileInfo(dir)
		if err != nil {
			return nil, err
		}
		if !exists {
			err = client.CreateDirectory(storagePath + "/" + path)
			if err != nil {
				return nil, err
			}
		} else if !isDir {
			return nil, fmt.Errorf("%s is not a directory", dir)
		}
	}

	storage.DerivedStorage = storage
	storage.SetDefaultNestingLevels([]int{0}, 0)
	return storage, nil
}

// ListFiles return the list of files and subdirectories under 'dir' (non-recursively)
func (storage *HubicStorage) ListFiles(threadIndex int, dir string) ([]string, []int64, error) {
	for len(dir) > 0 && dir[len(dir)-1] == '/' {
		dir = dir[:len(dir)-1]
	}

	if dir == "snapshots" {
		entries, err := storage.client.ListEntries(storage.storageDir + "/" + dir)
		if err != nil {
			return nil, nil, err
		}

		subDirs := []string{}
		for _, entry := range entries {
			if entry.Type == "application/directory" {
				subDirs = append(subDirs, entry.Name+"/")
			}
		}
		return subDirs, nil, nil
	} else if strings.HasPrefix(dir, "snapshots/") {
		entries, err := storage.client.ListEntries(storage.storageDir + "/" + dir)
		if err != nil {
			return nil, nil, err
		}

		files := []string{}

		for _, entry := range entries {
			if entry.Type == "application/directory" {
				continue
			}
			files = append(files, entry.Name)
		}
		return files, nil, nil
	} else {
		files := []string{}
		sizes := []int64{}
		entries, err := storage.client.ListEntries(storage.storageDir + "/" + dir)
		if err != nil {
			return nil, nil, err
		}

		for _, entry := range entries {
			if entry.Type == "application/directory" {
				files = append(files, entry.Name+"/")
				sizes = append(sizes, 0)
			} else {
				files = append(files, entry.Name)
				sizes = append(sizes, entry.Size)
			}
		}
		return files, sizes, nil
	}

}

// DeleteFile deletes the file or directory at 'filePath'.
func (storage *HubicStorage) DeleteFile(threadIndex int, filePath string) (err error) {
	err = storage.client.DeleteFile(storage.storageDir + "/" + filePath)
	if e, ok := err.(HubicError); ok && e.Status == 404 {
		LOG_DEBUG("HUBIC_DELETE", "Ignore 404 error")
		return nil
	}
	return err
}

// MoveFile renames the file.
func (storage *HubicStorage) MoveFile(threadIndex int, from string, to string) (err error) {
	fromPath := storage.storageDir + "/" + from
	toPath := storage.storageDir + "/" + to

	return storage.client.MoveFile(fromPath, toPath)
}

// CreateDirectory creates a new directory.
func (storage *HubicStorage) CreateDirectory(threadIndex int, dir string) (err error) {
	for len(dir) > 0 && dir[len(dir)-1] == '/' {
		dir = dir[:len(dir)-1]
	}

	return storage.client.CreateDirectory(storage.storageDir + "/" + dir)
}

// GetFileInfo returns the information about the file or directory at 'filePath'.
func (storage *HubicStorage) GetFileInfo(threadIndex int, filePath string) (exist bool, isDir bool, size int64, err error) {

	for len(filePath) > 0 && filePath[len(filePath)-1] == '/' {
		filePath = filePath[:len(filePath)-1]
	}
	return storage.client.GetFileInfo(storage.storageDir + "/" + filePath)
}

// DownloadFile reads the file at 'filePath' into the chunk.
func (storage *HubicStorage) DownloadFile(threadIndex int, filePath string, chunk *Chunk) (err error) {
	readCloser, _, err := storage.client.DownloadFile(storage.storageDir + "/" + filePath)
	if err != nil {
		return err
	}

	defer readCloser.Close()

	_, err = RateLimitedCopy(chunk, readCloser, storage.DownloadRateLimit/storage.numberOfThreads)
	return err
}

// UploadFile writes 'content' to the file at 'filePath'.
func (storage *HubicStorage) UploadFile(threadIndex int, filePath string, content []byte) (err error) {
	return storage.client.UploadFile(storage.storageDir+"/"+filePath, content, storage.UploadRateLimit/storage.numberOfThreads)
}

// If a local snapshot cache is needed for the storage to avoid downloading/uploading chunks too often when
// managing snapshots.
func (storage *HubicStorage) IsCacheNeeded() bool { return true }

// If the 'MoveFile' method is implemented.
func (storage *HubicStorage) IsMoveFileImplemented() bool { return true }

// If the storage can guarantee strong consistency.
func (storage *HubicStorage) IsStrongConsistent() bool { return false }

// If the storage supports fast listing of files names.
func (storage *HubicStorage) IsFastListing() bool { return true }

// Enable the test mode.
func (storage *HubicStorage) EnableTestMode() {
	storage.client.TestMode = true
}
