// Copyright (C) 2022 Opsmate, Inc.
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License, v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// This software is distributed WITHOUT A WARRANTY OF ANY KIND.
// See the Mozilla Public License for details.

package merkletree

import (
	"encoding/json"
	"fmt"
	"math/bits"
	"slices"
)

type CollapsedTree struct {
	nodes []Hash
	size  uint64
}

func calculateNumNodes(size uint64) int {
	return bits.OnesCount64(size)
}

func EmptyCollapsedTree() *CollapsedTree {
	return &CollapsedTree{nodes: []Hash{}, size: 0}
}

func NewCollapsedTree(nodes []Hash, size uint64) (*CollapsedTree, error) {
	if len(nodes) != calculateNumNodes(size) {
		return nil, fmt.Errorf("nodes has wrong length (should be %d, not %d)", calculateNumNodes(size), len(nodes))
	}
	return &CollapsedTree{nodes: nodes, size: size}, nil
}

func CloneCollapsedTree(source *CollapsedTree) *CollapsedTree {
	nodes := make([]Hash, len(source.nodes))
	copy(nodes, source.nodes)
	return &CollapsedTree{nodes: nodes, size: source.size}
}

func (tree CollapsedTree) Equal(other CollapsedTree) bool {
	return tree.size == other.size && slices.Equal(tree.nodes, other.nodes)
}

func (tree *CollapsedTree) Add(hash Hash) {
	tree.nodes = append(tree.nodes, hash)
	tree.size++
	tree.collapse()
}

func (tree *CollapsedTree) Append(other *CollapsedTree) error {
	maxSize := uint64(1) << bits.TrailingZeros64(tree.size)
	if other.size > maxSize {
		return fmt.Errorf("tree of size %d is too large to append to a tree of size %d (maximum size is %d)", other.size, tree.size, maxSize)
	}

	tree.nodes = append(tree.nodes, other.nodes...)
	tree.size += other.size
	tree.collapse()
	return nil
}

func (tree *CollapsedTree) collapse() {
	numNodes := calculateNumNodes(tree.size)
	for len(tree.nodes) > numNodes {
		left, right := tree.nodes[len(tree.nodes)-2], tree.nodes[len(tree.nodes)-1]
		tree.nodes = tree.nodes[:len(tree.nodes)-2]
		tree.nodes = append(tree.nodes, HashChildren(left, right))
	}
}

func (tree *CollapsedTree) CalculateRoot() Hash {
	if len(tree.nodes) == 0 {
		return HashNothing()
	}
	i := len(tree.nodes) - 1
	hash := tree.nodes[i]
	for i > 0 {
		i -= 1
		hash = HashChildren(tree.nodes[i], hash)
	}
	return hash
}

func (tree *CollapsedTree) Size() uint64 {
	return tree.size
}

func (tree CollapsedTree) MarshalJSON() ([]byte, error) {
	return json.Marshal(map[string]interface{}{
		"nodes": tree.nodes,
		"size":  tree.size,
	})
}

func (tree *CollapsedTree) UnmarshalJSON(b []byte) error {
	var rawTree struct {
		Nodes []Hash `json:"nodes"`
		Size  uint64 `json:"size"`
	}
	if err := json.Unmarshal(b, &rawTree); err != nil {
		return fmt.Errorf("error unmarshalling Collapsed Merkle Tree: %w", err)
	}
	if len(rawTree.Nodes) != calculateNumNodes(rawTree.Size) {
		return fmt.Errorf("error unmarshalling Collapsed Merkle Tree: nodes has wrong length (should be %d, not %d)", calculateNumNodes(rawTree.Size), len(rawTree.Nodes))
	}
	tree.size = rawTree.Size
	tree.nodes = rawTree.Nodes
	return nil
}
