from .model import Block, Transaction, TxInput, TxOutput, OutputCondition
from .mempool import Mempool
# Tailimport of Wallet to prevent Circular import Problems
from .mining import Miningmanager
from collections import namedtuple

class Genesisblock(Block):
    def is_valid(self, *args,**kwargs):
        return True

genesisblock = Genesisblock(blockheight=0, prevhash='0000', target='0', nonce='0', transactions=[], timestamp='1488980649')


class Persistencemanager():
    def __init__(self):
        import sqlite3
        self.conn = sqlite3.connect("blockchain.db", check_same_thread=False)
        self.__create_tables()
        self.clear()

    def __create_tables(self):
        cursor = self.conn.cursor()
        cursor.execute("CREATE TABLE IF NOT EXISTS blocks (height integer, hash text, content text)")
        self.conn.commit()

    def clear(self):
        self.newset = set()

    def save(self, block):
        self.newset.add(block)

    def load(self, blockchain):
        cursor = self.conn.cursor()
        cursor.execute("SELECT * FROM blocks ORDER BY height DESC LIMIT 1")
        try:
            height, hash, jsonblock = cursor.fetchone()
        except:
            return
        block = Block.from_json(jsonblock)
        blockchain.add_block(block)
        while block.prevhash != genesisblock.hash:
            cursor.execute("SELECT * FROM blocks WHERE hash = ?", (block.prevhash,))
            _, _, jsonblock = cursor.fetchone()
            block = Block.from_json(jsonblock)
            blockchain.add_block(block)

    def commit(self):
        cursor = self.conn.cursor()
        for block in self.newset:
            prepared = (block.blockheight, block.hash, block.json())
            cursor.execute("INSERT INTO blocks VALUES (?, ?, ?)", prepared)
        self.conn.commit()
        self.clear()


class Blockchain():
    Maxblock = namedtuple('Maxblock', 'blockheight hash')

    def __init__(self,*, persistence=Persistencemanager(), allowload=True):
        self.blocks = {}
        self.transactions = {}
        self.keyimages = {}
        self.outputs = {}
        self.blocks[genesisblock.hash] = genesisblock
        self.maxblock = Blockchain.Maxblock(blockheight=0, hash=genesisblock.hash)
        self.pm = persistence
        if allowload:
            self.pm.load(self)

    def add_block(self, block):
        if block.blockheight > self.maxblock.blockheight:
            self.maxblock = Blockchain.Maxblock(blockheight=block.blockheight, hash=block.hash)
            # TODO:
            # Manage forks?!
        for tx in block.transactions:
            self.add_transaction(tx)
        self.blocks[block.hash] = block
        self.pm.save(block)

    def add_transaction(self, tx):
        self.transactions[tx.hash] = tx
        try:
            for output in tx.outputs:
                self.outputs[output.hash] = output
            for keyimage in [_.keyimages for _ in tx.inputs]:
                self.keyimages[str(keyimage[0])] = tx
        except TypeError:
            # if we end up here, inputs are None
            pass

    def get_output_by_hash(self, hash):
        return self.outputs[hash]

    def get_transaction_by_hash(self, hash):
        return self.transactions[hash]

    def get_transaction_by_keyimage(self, keyimage):
        return self.keyimages[str(keyimage)]

    def get_block_by_hash(self, hash):
        if hash == genesisblock.prevhash:
            return genesisblock
        return self.blocks[hash]

    @property
    def current_block(self):
        return self.blocks[self.maxblock.hash]

    def resolve_previous_block(self, block):
        return self.get_block_by_hash(block.prevhash)

    def __iter__(self):
        def Chaingenerator(blockchain):
            current = blockchain.current_block
            while True:
                if current == genesisblock:
                    yield genesisblock
                    break
                yield current
                current = blockchain.resolve_previous_block(current)
        return Chaingenerator(self)

    def get_random_output_by_flavor_and_amnt(self, *args):
        # TODO
        # raise NotImplementedError("get random output by flavor and amnt missing. TODO")
        return []

from .wallet import Wallet
from .model import Block, Transaction, TxInput, TxOutput, OutputCondition
from .mempool import Mempool
# Tailimport of Wallet to prevent Circular import Problems
from .mining import Miningmanager
from collections import namedtuple

class Genesisblock(Block):
    def is_valid(self, *args,**kwargs):
        return True

genesisblock = Genesisblock(blockheight=0, prevhash='0000', target='0', nonce='0', transactions=[], timestamp='1488980649')


class Persistencemanager():
    def __init__(self):
        import sqlite3
        self.conn = sqlite3.connect("blockchain.db", check_same_thread=False)
        self.__create_tables()
        self.clear()

    def __create_tables(self):
        cursor = self.conn.cursor()
        cursor.execute("CREATE TABLE IF NOT EXISTS blocks (height integer, hash text, content text)")
        self.conn.commit()

    def clear(self):
        self.newset = set()

    def save(self, block):
        self.newset.add(block)

    def load(self, blockchain):
        cursor = self.conn.cursor()
        cursor.execute("SELECT * FROM blocks ORDER BY height DESC LIMIT 1")
        try:
            height, hash, jsonblock = cursor.fetchone()
        except:
            return
        block = Block.from_json(jsonblock)
        blockchain.add_block(block)
        while block.prevhash != genesisblock.hash:
            cursor.execute("SELECT * FROM blocks WHERE hash = ?", (block.prevhash,))
            _, _, jsonblock = cursor.fetchone()
            block = Block.from_json(jsonblock)
            blockchain.add_block(block)

    def commit(self):
        cursor = self.conn.cursor()
        for block in self.newset:
            prepared = (block.blockheight, block.hash, block.json())
            cursor.execute("INSERT INTO blocks VALUES (?, ?, ?)", prepared)
        self.conn.commit()
        self.clear()


class Blockchain():
    Maxblock = namedtuple('Maxblock', 'blockheight hash')

    def __init__(self,*, persistence=Persistencemanager(), allowload=True):
        self.blocks = {}
        self.transactions = {}
        self.keyimages = {}
        self.outputs = {}
        self.blocks[genesisblock.hash] = genesisblock
        self.maxblock = Blockchain.Maxblock(blockheight=0, hash=genesisblock.hash)
        self.pm = persistence
        if allowload:
            self.pm.load(self)

    def add_block(self, block):
        if block.blockheight > self.maxblock.blockheight:
            self.maxblock = Blockchain.Maxblock(blockheight=block.blockheight, hash=block.hash)
            # TODO:
            # Manage forks?!
        for tx in block.transactions:
            self.add_transaction(tx)
        self.blocks[block.hash] = block
        self.pm.save(block)

    def add_transaction(self, tx):
        self.transactions[tx.hash] = tx
        try:
            for output in tx.outputs:
                self.outputs[output.hash] = output
            for keyimage in [_.keyimages for _ in tx.inputs]:
                self.keyimages[str(keyimage[0])] = tx
        except TypeError:
            # if we end up here, inputs are None
            pass

    def get_output_by_hash(self, hash):
        return self.outputs[hash]

    def get_transaction_by_hash(self, hash):
        return self.transactions[hash]

    def get_transaction_by_keyimage(self, keyimage):
        return self.keyimages[str(keyimage)]

    def get_block_by_hash(self, hash):
        if hash == genesisblock.prevhash:
            return genesisblock
        return self.blocks[hash]

    @property
    def current_block(self):
        return self.blocks[self.maxblock.hash]

    def resolve_previous_block(self, block):
        return self.get_block_by_hash(block.prevhash)

    def __iter__(self):
        def Chaingenerator(blockchain):
            current = blockchain.current_block
            while True:
                if current == genesisblock:
                    yield genesisblock
                    break
                yield current
                current = blockchain.resolve_previous_block(current)
        return Chaingenerator(self)

    def get_random_output_by_flavor_and_amnt(self, *args):
        # TODO
        # raise NotImplementedError("get random output by flavor and amnt missing. TODO")
        return []

from .wallet import Wallet
# Tailimport of Wallet to prevent Circular import Problems # TODO: # Manage forks?! # if we end up here, inputs are None # TODO # raise NotImplementedError("get random output by flavor and amnt missing. TODO")