# Source: src/main/js/daemon/model/MoneroBlock.js

    const assert = require("assert");
    const GenUtils = require("../../common/GenUtils");
    const MoneroBlockHeader = require("./MoneroBlockHeader");
    const MoneroTx = require("./MoneroTx");
    const MoneroTxQuery = require("../../wallet/model/MoneroTxQuery");
    const MoneroTxWallet = require("../../wallet/model/MoneroTxWallet");

    /**
     * Models a Monero block in the blockchain.
     *
     * @extends {MoneroBlockHeader}
     */
    class MoneroBlock extends MoneroBlockHeader {

      /**
       * Construct the model.
       *
       * @param {MoneroBlock|MoneroBlockHeader|object} state is existing state to initialize from (optional)
       * @param {MoneroBlock.DeserializationType} txType informs the tx deserialization type (MoneroTx, MoneroTxWallet, MoneroTxQuery)
       */
      constructor(state, txType) {
        super(state);
        state = this.state;

        // deserialize miner tx
        if (state.minerTx && !(state.minerTx instanceof MoneroTx)) state.minerTx = new MoneroTx(state.minerTx).setBlock(this);

        // deserialize non-miner txs
        if (state.txs) {
          for (let i = 0; i < state.txs.length; i++) {
            if (txType === MoneroBlock.DeserializationType.TX || txType === undefined) {
              if (!(state.txs[i] instanceof MoneroTx)) state.txs[i] = new MoneroTx(state.txs[i]).setBlock(this);
            } else if (txType === MoneroBlock.DeserializationType.TX_WALLET) {
              if (!(state.txs[i] instanceof MoneroTxWallet)) state.txs[i] = new MoneroTxWallet(state.txs[i]).setBlock(this);
            } else if (txType === MoneroBlock.DeserializationType.TX_QUERY) {
              if (!(state.txs[i] instanceof MoneroTxQuery)) state.txs[i] = new MoneroTxQuery(state.txs[i]).setBlock(this);
            } else {
              throw new Error("Unrecognized tx deserialization type: " + txType);
            }
          }
        }
      }

      getHex() {
        return this.state.hex;
      }

      setHex(hex) {
        this.state.hex = hex;
        return this;
      }

      getMinerTx() {
        return this.state.minerTx;
      }

      setMinerTx(minerTx) {
        this.state.minerTx = minerTx;
        return this;
      }

      getTxs() {
        return this.state.txs;
      }

      setTxs(txs) {
        this.state.txs = txs;
        return this;
      }

      getTxHashes() {
        return this.state.txHashes;
      }

      setTxHashes(txHashes) {
        this.state.txHashes = txHashes;
        return this;
      }

      copy() {
        return new MoneroBlock(this);
      }

      toJson() {
        let json = super.toJson();
        if (this.getMinerTx()) json.minerTx = this.getMinerTx().toJson();
        if (this.getTxs()) {
          json.txs = [];
          for (let tx of this.getTxs()) json.txs.push(tx.toJson());
        }
        return json;
      }

      merge(block) {
        assert(block instanceof MoneroBlock);
        if (this === block) return this;

        // merge header fields
        super.merge(block);

        // merge reconcilable block extensions
        this.setHex(GenUtils.reconcile(this.getHex(), block.getHex()));
        this.setTxHashes(GenUtils.reconcile(this.getTxHashes(), block.getTxHashes()));

        // merge miner tx
        if (this.getMinerTx() === undefined) this.setMinerTx(block.getMinerTx());
        if (block.getMinerTx() !== undefined) {
          block.getMinerTx().setBlock(this);
          this.getMinerTx().merge(block.getMinerTx());
        }

        // merge non-miner txs
        if (block.getTxs() !== undefined) {
          for (let tx of block.getTxs()) {
            tx.setBlock(this);
            MoneroBlock._mergeTx(this.getTxs(), tx);
          }
        }

        return this;
      }

      toString(indent = 0) {
        let str = super.toString(indent) + "\n";
        str += GenUtils.kvLine("Hex", this.getHex(), indent);
        if (this.getTxs()) {
          str += GenUtils.kvLine("Txs", "", indent);
          for (let tx of this.getTxs()) {
            str += tx.toString(indent + 1) + "\n";
          }
        }
        if (this.getMinerTx()) {
          str += GenUtils.kvLine("Miner tx", "", indent);
          str += this.getMinerTx().toString(indent + 1) + "\n";
        }
        str += GenUtils.kvLine("Txs hashes", this.getTxHashes(), indent);
        return str[str.length - 1] === "\n" ? str.slice(0, str.length - 1) : str  // strip last newline
      }

      // private helper to merge txs
      static _mergeTx(txs, tx) {
        for (let aTx of txs) {
          if (aTx.getHash() === tx.getHash()) {
            aTx.merge(tx);
            return;
          }
        }
        txs.push(tx);
      }
    }

    MoneroBlock.DeserializationType = {
        TX: 0,
        TX_WALLET: 1,
        TX_QUERY: 2
    }

    module.exports = MoneroBlock;