/**
 * Copyright (c) Whales Corp.
 * All Rights Reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import { BitString } from "../BitString";
import { CellType } from "../CellType";
import { Cell } from "../Cell";
import { LevelMask } from "./LevelMask";
import { ExoticPruned, exoticPruned } from "./exoticPruned";
import { exoticMerkleProof } from "./exoticMerkleProof";
import { getRepr } from "./descriptor";
import { sha256_sync } from "@ton/crypto";
import { exoticMerkleUpdate } from "./exoticMerkleUpdate";
import { exoticLibrary } from "./exoticLibrary";

//
// This function replicates unknown logic of resolving cell data
// https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/vm/cells/DataCell.cpp#L214
//
export function wonderCalculator(
    type: CellType,
    bits: BitString,
    refs: Cell[],
): { mask: LevelMask; hashes: Buffer[]; depths: number[] } {
    //
    // Resolving level mask
    //

    let levelMask: LevelMask;
    let pruned: ExoticPruned | null = null;
    if (type === CellType.Ordinary) {
        let mask = 0;
        for (let r of refs) {
            mask = mask | r.mask.value;
        }
        levelMask = new LevelMask(mask);
    } else if (type === CellType.PrunedBranch) {
        // Parse pruned
        pruned = exoticPruned(bits, refs);

        // Load level
        levelMask = new LevelMask(pruned.mask);
    } else if (type === CellType.MerkleProof) {
        // Parse proof
        let loaded = exoticMerkleProof(bits, refs);

        // Load level
        levelMask = new LevelMask(refs[0].mask.value >> 1);
    } else if (type === CellType.MerkleUpdate) {
        // Parse update
        let loaded = exoticMerkleUpdate(bits, refs);

        // Load level
        levelMask = new LevelMask(
            (refs[0].mask.value | refs[1].mask.value) >> 1,
        );
    } else if (type === CellType.Library) {
        // Parse library
        let loaded = exoticLibrary(bits, refs);

        // Load level
        levelMask = new LevelMask();
    } else {
        throw new Error("Unsupported exotic type");
    }

    //
    // Calculate hashes and depths
    // NOTE: https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/vm/cells/DataCell.cpp#L214
    //

    let depths: number[] = [];
    let hashes: Buffer[] = [];

    let hashCount = type === CellType.PrunedBranch ? 1 : levelMask.hashCount;
    let totalHashCount = levelMask.hashCount;
    let hashIOffset = totalHashCount - hashCount;
    for (let levelI = 0, hashI = 0; levelI <= levelMask.level; levelI++) {
        if (!levelMask.isSignificant(levelI)) {
            continue;
        }

        if (hashI < hashIOffset) {
            hashI++;
            continue;
        }

        //
        // Bits
        //

        let currentBits: BitString;
        if (hashI === hashIOffset) {
            if (!(levelI === 0 || type === CellType.PrunedBranch)) {
                throw Error("Invalid");
            }
            currentBits = bits;
        } else {
            if (!(levelI !== 0 && type !== CellType.PrunedBranch)) {
                throw Error("Invalid: " + levelI + ", " + type);
            }
            currentBits = new BitString(
                hashes[hashI - hashIOffset - 1],
                0,
                256,
            );
        }

        //
        // Depth
        //

        let currentDepth = 0;
        for (let c of refs) {
            let childDepth: number;
            if (type == CellType.MerkleProof || type == CellType.MerkleUpdate) {
                childDepth = c.depth(levelI + 1);
            } else {
                childDepth = c.depth(levelI);
            }
            currentDepth = Math.max(currentDepth, childDepth);
        }
        if (refs.length > 0) {
            currentDepth++;
        }

        //
        // Hash
        //

        let repr = getRepr(
            bits,
            currentBits,
            refs,
            levelI,
            levelMask.apply(levelI).value,
            type,
        );
        let hash = sha256_sync(repr);

        //
        // Persist next
        //

        let destI = hashI - hashIOffset;
        depths[destI] = currentDepth;
        hashes[destI] = hash;

        //
        // Next
        //

        hashI++;
    }

    //
    // Calculate hash and depth for all levels
    //

    let resolvedHashes: Buffer[] = [];
    let resolvedDepths: number[] = [];
    if (pruned) {
        for (let i = 0; i < 4; i++) {
            const { hashIndex } = levelMask.apply(i);
            const { hashIndex: thisHashIndex } = levelMask;
            if (hashIndex !== thisHashIndex) {
                resolvedHashes.push(pruned.pruned[hashIndex].hash);
                resolvedDepths.push(pruned.pruned[hashIndex].depth);
            } else {
                resolvedHashes.push(hashes[0]);
                resolvedDepths.push(depths[0]);
            }
        }
    } else {
        for (let i = 0; i < 4; i++) {
            resolvedHashes.push(hashes[levelMask.apply(i).hashIndex]);
            resolvedDepths.push(depths[levelMask.apply(i).hashIndex]);
        }
    }

    //
    // Result
    //

    return {
        mask: levelMask,
        hashes: resolvedHashes,
        depths: resolvedDepths,
    };
}
