/*
File Name:  Merkle.go
Copyright:  2021 Peernet Foundation s.r.o.
Author:     Peter Kleissner
*/

package warehouse

import (
	"errors"
	"io"
	"os"
	"path/filepath"

	"github.com/PeernetOfficial/core/merkle"
)

// Merkle companion files are created to store the entire merkle tree for files that are bigger than the minimum fragment size.
const merkleCompanionExt = ".merkle"

// MerkleFileExists checks if the merkle companion file exists. It returns StatusInvalidHash, StatusFileNotFound, or StatusOK.
func (wh *Warehouse) MerkleFileExists(hash []byte) (path string, fileInfo os.FileInfo, status int, err error) {
	hashA, err := ValidateHash(hash)
	if err != nil {
		return "", nil, StatusInvalidHash, err
	}

	a, b := buildPath(wh.Directory, hashA)
	path = filepath.Join(a, b) + merkleCompanionExt

	if fileInfo, err := os.Stat(path); err == nil {
		// file exists
		return path, fileInfo, StatusOK, nil
	}

	return "", nil, StatusFileNotFound, os.ErrNotExist
}

// createMerkleCompanionFile creates a merkle companion file. If the merkle companion file already exists, it is overwritten.
// dataFilePath is the full path to the data file in the warehouse.
func (wh *Warehouse) createMerkleCompanionFile(dataFilePath string) (status int, err error) {
	// open the data file
	dataFile, err := os.Open(dataFilePath)
	if err != nil && os.IsNotExist(err) {
		return StatusFileNotFound, err
	} else if err != nil {
		return StatusErrorOpenFile, err
	}
	defer dataFile.Close()

	var fileSize uint64
	stat, err := dataFile.Stat()
	if err != nil {
		return StatusErrorOpenFile, err
	}
	fileSize = uint64(stat.Size())

	// Merkle files are only created if merkle trees are actually used, which means the file must be bigger than the minimum fragment size.
	// Otherwise the merkle root hash will be just the file hash and the merkle overhead provides no advantage whatsoever.
	if fileSize <= merkle.MinimumFragmentSize {
		return StatusOK, nil
	}

	// Create a new merkle file. If one exists, overwrite.
	merkleFile := dataFilePath + merkleCompanionExt

	fileM, err := os.OpenFile(merkleFile, os.O_WRONLY|os.O_CREATE, 0666) // 666 = All uses can read/write
	if err != nil {
		return StatusErrorCreateTarget, err
	}
	defer fileM.Close()

	// create the merkle tree and write it to the companion file
	fragmentSize := merkle.CalculateFragmentSize(fileSize)
	tree, err := merkle.NewMerkleTree(fileSize, fragmentSize, dataFile)
	if err != nil {
		return StatusErrorCreateMerkle, err
	}

	fileM.Write(tree.Export())

	return StatusOK, nil
}

// ReadMerkleTree reads the merkle tree from the companion file associated with the hash.
// It is the callers responsibility to first check if a merkle tree file is to be expected (files smaller or equal than the minimum fragment size do not use a merkle tree).
func (wh *Warehouse) ReadMerkleTree(hash []byte, headerOnly bool) (tree *merkle.MerkleTree, status int, err error) {
	path, _, status, err := wh.MerkleFileExists(hash)
	if status != StatusOK { // file does not exist or invalid hash
		return nil, status, err
	}

	fileM, err := os.OpenFile(path, os.O_RDONLY, 0)
	if err != nil {
		return nil, StatusErrorOpenFile, err
	}
	defer fileM.Close()

	if headerOnly {
		data := make([]byte, merkle.MerkleTreeFileHeaderSize)
		if _, err = io.ReadFull(fileM, data); err != nil {
			return nil, StatusErrorReadFile, err
		}

		if tree = merkle.ReadMerkleTreeHeader(data); tree == nil {
			return nil, StatusErrorMerkleTreeFile, errors.New("invalid merkle tree file header")
		}
	} else {
		data, err := io.ReadAll(fileM)
		if err != nil {
			return nil, StatusErrorReadFile, err
		}

		if tree = merkle.ImportMerkleTree(data); tree == nil {
			return nil, StatusErrorMerkleTreeFile, errors.New("invalid merkle tree file header")
		}
	}

	return tree, StatusOK, nil
}
