//
//  blocksci.cpp
//  BlockReader
//
//  Created by Harry Kalodner on 1/14/17.
//  Copyright Â© 2017 Harry Kalodner. All rights reserved.
//

#include "sequence.hpp"
#include "caster_py.hpp"
#include "blocksci_range_create.hpp"
#include "proxy.hpp"
#include "proxy_create.hpp"
#include "python_proxies.hpp"
#include "sequence_py.hpp"

#include "chain/blockchain_py.hpp"
#include "chain/input/input_py.hpp"
#include "chain/output/output_py.hpp"
#include "chain/tx/tx_py.hpp"
#include "chain/block/block_py.hpp"

#include "cluster/cluster/cluster_py.hpp"
#include "cluster/tagged_cluster/tagged_cluster_py.hpp"
#include "cluster/tagged_address/tagged_address_py.hpp"

#include "scripts/address_py.hpp"
#include "scripts/equiv_address/equiv_address_py.hpp"
#include "scripts/pubkey/pubkey_py.hpp"
#include "scripts/multisig/multisig_py.hpp"
#include "scripts/scripthash/scripthash_py.hpp"
#include "scripts/nulldata/nulldata_py.hpp"
#include "scripts/nonstandard/nonstandard_py.hpp"
#include "scripts/witness_unknown/witness_unknown_py.hpp"

#include <blocksci/chain/blockchain.hpp>
#include <blocksci/cluster/cluster.hpp>
#include <blocksci/address/equiv_address.hpp>

#include <pybind11/functional.h>

namespace py = pybind11;

using namespace blocksci;

void init_blockchain(py::module &m);
void init_heuristics(py::module &m);

template <typename Class>
void addSelfProxy(Class &cl) {
    using T = typename Class::type;
    cl.def_property_readonly_static("_self_proxy", [](pybind11::object &) -> Proxy<T> {
        return makeSimpleProxy<T>();
    });
}

template <typename T>
void addAnyInit(py::class_<std::any> &cl) {
    cl
    .def(py::init([](T &val) -> std::any { return val; }))
    .def(py::init([](ranges::optional<T> &val) -> std::any { return val; }))
    .def(py::init([](Iterator<T> &val) -> std::any {
        return val.rng;
    }))
    .def(py::init([](Range<T> &val) -> std::any {
        return val.rng;
    }))
    ;

    pybind11::implicitly_convertible<T, std::any>();
    pybind11::implicitly_convertible<ranges::optional<T>, std::any>();
    pybind11::implicitly_convertible<Iterator<T>, std::any>();
    pybind11::implicitly_convertible<Range<T>, std::any>();
}

PYBIND11_MODULE(_blocksci, m) {
    m.attr("__name__") = PYBIND11_STR_TYPE("blocksci");

    TypenameLookup nameLookup;

    py::class_<Blockchain> blockchainCl(m, "Blockchain", "Class representing the blockchain. This class is constructed by passing it a string representing a file path to your BlockSci data files generated by blocksci_parser", py::dynamic_attr());
    py::class_<uint160> uint160Cl(m, "uint160");
    py::class_<uint256> uint256Cl(m, "uint256");
    py::class_<Block> blockCl(m, "Block", "Class representing a block in the blockchain");
    py::class_<Transaction> txCl(m, "Tx", "Class representing a transaction in a block");
    py::class_<Input> inputCl(m, "Input", "Class representing a transaction input");
    py::class_<Output> outputCl(m, "Output", "Class representing a transaction output");
    py::class_<EquivAddress> equivAddressCl(m, "EquivAddress", "A set of equivalent addresses");
    py::class_<ScriptBase> addressCl(m, "Address", "Represents an abstract address object which uniquely identifies a given address");
    py::class_<script::Pubkey> pubkeyAddressCl(m, "PubkeyAddress", addressCl, "Extra data about pay to pubkey address");
    py::class_<script::PubkeyHash> pubkeyHashAddressCl(m, "PubkeyHashAddress", addressCl, "Extra data about pay to pubkey address");
    py::class_<script::WitnessPubkeyHash> witnessPubkeyHashAddressCl(m, "WitnessPubkeyHashAddress", addressCl, "Extra data about pay to pubkey address");
    py::class_<script::MultisigPubkey> multisigPubkeyCl(m, "MultisigPubkey", addressCl, "Extra data about a pubkey inside a multisig address");
    py::class_<script::ScriptHash> scriptHashCl(m, "ScriptHashAddress", addressCl, "Extra data about pay to script hash address");
    py::class_<script::WitnessScriptHash> witnessScriptHashCl(m, "WitnessScriptHashAddress", addressCl, "Extra data about pay to script hash address");
    py::class_<script::Multisig> multisigCl(m, "MultisigAddress", addressCl, "Extra data about multi-signature address");
    py::class_<script::Nonstandard> nonstandardCl(m, "NonStandardAddress", addressCl, "Extra data about non-standard address");
    py::class_<script::OpReturn> opReturnCl(m, "OpReturn", addressCl, "Extra data about op_return address");
    py::class_<script::WitnessUnknown> witnessUnknownCl(m, "WitnessUnknownAddress", addressCl, "Data about an unknown witness address");

    py::class_<std::any> anyCl(m, "Any", "A generic class representing a BlockSci type");

    auto clusterMod = m.def_submodule("cluster");
    py::class_<Cluster> clusterCl(clusterMod, "Cluster", "Class representing a cluster");
    py::class_<TaggedCluster> taggedClusterCl(clusterMod, "TaggedCluster");
    py::class_<TaggedAddress> taggedAddressCl(clusterMod, "TaggedAddress");

    py::class_<GenericIterator> genericIteratorCl(m, "GenericIterator", "Class representing any blocksci iterator");
    py::class_<GenericRange, GenericIterator> genericRangeCl(m, "GenericRange", "Class representing any blocksci range");
    py::class_<GenericAddressIterator, GenericIterator> genericAddressIteratorCl(m, "GenericAddressIterator", "Class representing any Address iterator");
    py::class_<GenericAddressRange, GenericRange> genericAddressRangeCl(m, "GenericAddressRange", "Class representing any Address range");


    RangeClasses<Block> blockRangeCls(createRangeClasses<Block>(m));
    RangeClasses<Transaction> txRangeCls(createRangeClasses<Transaction>(m));
    RangeClasses<Input> inputRangeCls(createRangeClasses<Input>(m));
    RangeClasses<Output> outputRangeCls(createRangeClasses<Output>(m));
    RangeClasses<AnyScript> addressRangeCls(createAddressRangeClasses<AnyScript>(m));
    RangeClasses<EquivAddress> equivAddressRangeCls(createRangeClasses<EquivAddress>(m));
    RangeClasses<script::Pubkey> pubkeyRangeCls(createAddressRangeClasses<script::Pubkey>(m));
    RangeClasses<script::PubkeyHash> pubkeyHashRangeCls(createAddressRangeClasses<script::PubkeyHash>(m));
    RangeClasses<script::WitnessPubkeyHash> witnessPubkeyHashRangeCls(createAddressRangeClasses<script::WitnessPubkeyHash>(m));
    RangeClasses<script::MultisigPubkey> multisigPubkeyRangeCls(createAddressRangeClasses<script::MultisigPubkey>(m));
    RangeClasses<script::Multisig> multisigRangeCls(createAddressRangeClasses<script::Multisig>(m));
    RangeClasses<script::ScriptHash> scripthashRangeCls(createAddressRangeClasses<script::ScriptHash>(m));
    RangeClasses<script::WitnessScriptHash> witnessScripthashRangeCls(createAddressRangeClasses<script::WitnessScriptHash>(m));
    RangeClasses<script::OpReturn> nulldataRangeCls(createAddressRangeClasses<script::OpReturn>(m));
    RangeClasses<script::Nonstandard> nonstandardRangeCls(createAddressRangeClasses<script::Nonstandard>(m));
    RangeClasses<script::WitnessUnknown> witnessUnknownRangeCls(createAddressRangeClasses<script::WitnessUnknown>(m));
    
    RangeClasses<Cluster> clusterRangeCls(createRangeClasses<Cluster>(clusterMod));
    RangeClasses<TaggedCluster> taggedClusterRangeCls(createRangeClasses<TaggedCluster>(clusterMod));
    RangeClasses<TaggedAddress> taggedAddressRangeCls(createRangeClasses<TaggedAddress>(clusterMod));

    setupProxies(m);

    addAnyInit<script::Pubkey>(anyCl);
    addAnyInit<script::PubkeyHash>(anyCl);
    addAnyInit<script::WitnessPubkeyHash>(anyCl);
    addAnyInit<script::MultisigPubkey>(anyCl);
    addAnyInit<script::Multisig>(anyCl);
    addAnyInit<script::ScriptHash>(anyCl);
    addAnyInit<script::WitnessScriptHash>(anyCl);
    addAnyInit<script::OpReturn>(anyCl);
    addAnyInit<script::Nonstandard>(anyCl);
    addAnyInit<script::WitnessUnknown>(anyCl);

    addAnyInit<Block>(anyCl);
    addAnyInit<Transaction>(anyCl);
    addAnyInit<Input>(anyCl);
    addAnyInit<Output>(anyCl);
    addAnyInit<EquivAddress>(anyCl);
    addAnyInit<AnyScript>(anyCl);

    addAnyInit<Cluster>(anyCl);
    addAnyInit<TaggedCluster>(anyCl);
    addAnyInit<TaggedAddress>(anyCl);

    addSelfProxy(uint160Cl);
    addSelfProxy(uint256Cl);
    addSelfProxy(blockCl);
    addSelfProxy(txCl);
    addSelfProxy(inputCl);
    addSelfProxy(outputCl);
    addSelfProxy(equivAddressCl);
    
    addSelfProxy(pubkeyAddressCl);
    addSelfProxy(pubkeyHashAddressCl);
    addSelfProxy(witnessPubkeyHashAddressCl);
    addSelfProxy(multisigPubkeyCl);
    addSelfProxy(scriptHashCl);
    addSelfProxy(witnessScriptHashCl);
    addSelfProxy(multisigCl);
    addSelfProxy(nonstandardCl);
    addSelfProxy(opReturnCl);
    addSelfProxy(witnessUnknownCl);


    addSelfProxy(clusterCl);
    addSelfProxy(taggedClusterCl);
    addSelfProxy(taggedAddressCl);

    init_address_type(m);
    init_heuristics(m);
    init_data_access(m);
    init_blockchain(blockchainCl);
    init_uint160(uint160Cl);
    init_uint256(uint256Cl);
    {
        init_equiv_address(equivAddressCl);
        addEquivAddressRangeMethods(equivAddressRangeCls);
    }
    {
        init_block(blockCl);
        addBlockRangeMethods(blockRangeCls);
    }
    {
        init_tx(txCl);
        addTxRangeMethods(txRangeCls);
    }
    {
        init_input(inputCl);
        addInputRangeMethods(inputRangeCls);
    }
    {
        init_output(outputCl);
        addOutputRangeMethods(outputRangeCls);
    }
    {
        {   
            addAddressRangeMethods(addressRangeCls);
        }
        {
            init_pubkey(pubkeyAddressCl);
            addPubkeyRangeMethods(pubkeyRangeCls);
        }
        {
            init_pubkeyhash(pubkeyHashAddressCl);
            addPubkeyHashRangeMethods(pubkeyHashRangeCls);
        }
        {
            init_witness_pubkeyhash(witnessPubkeyHashAddressCl);
            addWitnessPubkeyHashRangeMethods(witnessPubkeyHashRangeCls);\
        }
        {
            init_multisig_pubkey(multisigPubkeyCl);
            addMultisigPubkeyRangeMethods(multisigPubkeyRangeCls);
        }
        {
            init_multisig(multisigCl);
            addMultisigRangeMethods(multisigRangeCls);
        }
        {
            init_scripthash(scriptHashCl);
            addScriptHashRangeMethods(scripthashRangeCls);
        }
        {
            init_witness_scripthash(witnessScriptHashCl);
            addWitnessScriptHashRangeMethods(witnessScripthashRangeCls);
        }
        {
            init_nulldata(opReturnCl);
            addNulldataRangeMethods(nulldataRangeCls);
        }
        {
            init_nonstandard(nonstandardCl);
            addNonstandardRangeMethods(nonstandardRangeCls);
        }
        {
            init_witness_unknown(witnessUnknownCl);
            addWitnessUnknownRangeMethods(witnessUnknownRangeCls);
        }
    }
    {
        init_cluster_manager(clusterMod);
        {
            init_cluster(clusterCl);
            addClusterRangeMethods(clusterRangeCls);
        }
        {
            init_tagged_cluster(taggedClusterCl);
            addTaggedClusterRangeMethods(taggedClusterRangeCls);
        }
        {
            init_tagged_address(taggedAddressCl);
            addTaggedAddressRangeMethods(taggedAddressRangeCls);
        }
    }
    init_address(addressCl);

    addCommonRangeMethods(genericRangeCl);
    addCommonIteratorMethods(genericIteratorCl);
}
