import psycopg2
from datetime import datetime
from binascii import hexlify
from psycopg2.extras import RealDictConnection
from config import config
from collections import *

def tobin(x):
	return bytearray(x.decode('hex'))

class Db:
	def __init__(self, retDict=False):
		if retDict:
			self.conn = RealDictConnection(config.connection_string)
		else:
			self.conn = psycopg2.connect(config.connection_string)
			self.createTables()
	
	def commit(self):
		self.conn.commit()

	def accounts(self, fun):
		cur = self.conn.cursor()
		cur.execute("SELECT * FROM accounts")
		for r in cur:
			fun(r)
		cur.close()

	def delegations(self, fun):
		cur = self.conn.cursor()
		cur.execute("SELECT block_height,signer_id,remote_id,mode FROM delegates")
		for r in cur:
			fun({'block_height':r[0],'signer_id':r[1],'remote_id':r[2],'mode':r[3]})
		cur.close()

	def createNamespaceTables(self, cur):
		cur.execute("""
			CREATE TABLE IF NOT EXISTS namespaces
			(id BIGINT DEFAULT nextval('common_transactions_seq_id') PRIMARY KEY,
			 block_height BIGINT REFERENCES blocks(height),
			 hash bytea NOT NULL UNIQUE,
			 timestamp varchar NOT NULL,
			 timestamp_unix BIGINT NOT NULL,
			 timestamp_nem BIGINT NOT NULL,
			 signer_id BIGINT REFERENCES accounts(id),
			 signature bytea,
			 deadline BIGINT NOT NULL,

			 fee BIGINT NOT NULL,
			 rental_sink BIGINT REFERENCES accounts(id),
			 rental_fee BIGINT NOT NULL,
			 parent_ns BIGINT,
			 namespace_name VARCHAR(148),
			 namespace_part VARCHAR(66)
			)
			""") 
		cur.execute("""
			CREATE TABLE IF NOT EXISTS mosaics
			(id BIGINT DEFAULT nextval('common_transactions_seq_id') PRIMARY KEY,
			 block_height BIGINT REFERENCES blocks(height),
			 hash bytea NOT NULL UNIQUE,
			 timestamp varchar NOT NULL,
			 timestamp_unix BIGINT NOT NULL,
			 timestamp_nem BIGINT NOT NULL,
			 signer_id BIGINT REFERENCES accounts(id),
			 signature bytea,
			 deadline BIGINT NOT NULL,

			 fee BIGINT NOT NULL,
			 creation_sink BIGINT REFERENCES accounts(id),
			 creation_fee BIGINT NOT NULL,
			 parent_ns BIGINT REFERENCES namespaces(id),
			 mosaic_name VARCHAR(34),
			 mosaic_fqdn VARCHAR(180),
			 mosaic_description VARCHAR(516)
			)
			""")

		cur.execute("""
			CREATE TABLE IF NOT EXISTS mosaic_levys
			(id BIGSERIAL PRIMARY KEY,
			 block_height BIGINT REFERENCES blocks(height),
			 mosaic_id BIGINT REFERENCES mosaics(id),

			 type INT NOT NULL,
			 recipient_id BIGINT REFERENCES accounts(id),
			 fee_mosaic_id BIGINT REFERENCES mosaics(id),
			 fee BIGINT NOT NULL
			)""")
		cur.execute("""
			CREATE TABLE IF NOT EXISTS mosaic_properties
			(id BIGSERIAL PRIMARY KEY,
			 block_height BIGINT REFERENCES blocks(height),
			 mosaic_id BIGINT REFERENCES mosaics(id),

			 name VARCHAR(64),
			 value VARCHAR(64)
			)""")
		cur.execute("""
			CREATE TABLE IF NOT EXISTS transfer_attachments
			(id BIGSERIAL PRIMARY KEY,
			 block_height BIGINT REFERENCES blocks(height),
			 transfer_id BIGINT REFERENCES transfers(id),

			 type INT REFERENCES inouts_type(id),
			 mosaic_id BIGINT REFERENCES mosaics(id),
			 quantity BIGINT NOT NULL
			)""")
		cur.execute("""
			CREATE TABLE IF NOT EXISTS mosaic_inouts
			(id BIGSERIAL PRIMARY KEY,
			 account_id BIGINT REFERENCES accounts(id),
			 block_height BIGINT REFERENCES blocks(height),
			 mosaic_id BIGINT REFERENCES mosaics(id),

			 type INT REFERENCES inouts_type(id),
			 tx_id BIGSERIAL,
			 quantity BIGINT
			)
			""")
		
		cur.execute("""
			CREATE TABLE IF NOT EXISTS mosaic_amounts
			(id BIGSERIAL PRIMARY KEY,
			 account_id BIGINT REFERENCES accounts(id),
			 block_height BIGINT REFERENCES blocks(height),
			 mosaic_id BIGINT REFERENCES mosaics(id),
			 amount BIGINT
			)
			""")

		cur.execute("""
			CREATE TABLE IF NOT EXISTS mosaic_state_supply
			(id BIGSERIAL PRIMARY KEY,
			 block_height BIGINT REFERENCES blocks(height),
			 mosaic_id BIGINT REFERENCES mosaics(id),

			 quantity BIGINT
			)""")

		# block heights indexes
		cur.execute("CREATE INDEX namespaces_blockheight_idx ON namespaces(block_height)");
		cur.execute("CREATE INDEX mosaics_blockheight_idx ON mosaics(block_height)");
		cur.execute('CREATE INDEX mosaic_levys_blockheight_idx ON mosaic_levys(block_height)')
		cur.execute('CREATE INDEX mosaic_properties_blockheight_idx ON mosaic_properties(block_height)')
		cur.execute('CREATE INDEX mosaic_inouts_blockheight_idx ON mosaic_inouts(block_height)')
		cur.execute('CREATE INDEX mosaic_amounts_blockheight_idx ON mosaic_amounts(block_height)')
		cur.execute('CREATE INDEX mosaic_state_supply_blockheight_idx ON mosaic_state_supply(block_height)')

		cur.execute("CREATE INDEX mosaic_levys_mosaic_idx ON mosaic_levys(mosaic_id)");
		cur.execute("CREATE INDEX mosaic_properties_mosaic_idx ON mosaic_properties(mosaic_id)");
		cur.execute("CREATE INDEX transfer_attachments_blockheight_idx ON transfer_attachments(block_height)");
		cur.execute('CREATE INDEX inouts_blockheight_idx ON inouts(block_height)')
		cur.execute('CREATE INDEX inouts_account_id_idx ON inouts(account_id)')
		cur.execute('CREATE INDEX inouts_account_id_3_idx ON inouts(account_id) WHERE type = 3')
		cur.execute('CREATE INDEX harvests_blockheight_idx ON harvests(block_height)')
		cur.execute('CREATE INDEX harvests_account_id_idx ON harvests(account_id)')


		cur.execute("CREATE INDEX transfer_attachments_mosaic_idx ON transfer_attachments(mosaic_id)");
		cur.execute("CREATE INDEX transfer_attachments_transfer_idx ON transfer_attachments(transfer_id)");

		cur.execute("CREATE INDEX mosaic_inouts_type_idx on mosaic_inouts(type)");

		cur.execute("select * from accounts where printablekey = '%s'" % config.nemesis);
		nId = cur.fetchone()[0]
		NA = bytearray('n/a')
		cur.execute("INSERT INTO namespaces (block_height, hash, timestamp, timestamp_unix, timestamp_nem, signer_id, signature, deadline, fee, rental_sink, rental_fee, parent_ns, namespace_name, namespace_part) VALUES (%s,%s, %s,%s,%s,%s,%s,%s, %s,%s,%s,%s,%s,%s) RETURNING id", (1,NA, '2015-03-29 00:06:25',1427587585,0,nId,NA,0, 0,nId,0,None,"nem","nem"))
		nsId = cur.fetchone()[0]
		print "NEM namespace ID: ", nsId

		cur.execute("INSERT INTO mosaics (block_height, hash, timestamp, timestamp_unix, timestamp_nem, signer_id, signature, deadline, fee, creation_sink, creation_fee, parent_ns, mosaic_name, mosaic_fqdn, mosaic_description) VALUES (%s,%s, %s,%s,%s,%s,%s,%s, %s,%s,%s, %s,%s,%s,%s) RETURNING id", (1,NA, '2015-03-29 00:06:25',1427587585,0,nId,NA,0,  0,nId,0, nsId,"nem.xem","nem.xem", "Mosaic representing XEM"))
		msId = cur.fetchone()[0]
		print "NEM.XEM mosaic ID: ", msId

		cur.execute("INSERT INTO mosaic_state_supply (block_height, mosaic_id, quantity) VALUES (1, %s, 8999999999000000)", (msId,))
			
		cur.executemany("INSERT INTO mosaic_properties (block_height,mosaic_id,name,value) VALUES (%s,%s, %s,%s)",
			((1,msId, 'divisibility', "6"),
			(1,msId, 'initialSupply', "8999999999"),
			(1,msId, 'mutableSupply', "0"),
			(1,msId, 'transferable', "0")))

		cur.execute("INSERT INTO inouts_type VALUES (%s,%s)", (11, 'levy incoming'))
		cur.execute("INSERT INTO inouts_type VALUES (%s,%s)", (12, 'levy outgoing'))
		cur.execute("INSERT INTO inouts_type VALUES (%s,%s)", (14, 'levy incoming multisig'))
		cur.execute("INSERT INTO inouts_type VALUES (%s,%s)", (15, 'levy outgoing multisig'))


	def createSupplies(self, cur):
		cur.execute("""
			CREATE TABLE IF NOT EXISTS mosaic_supplies
			(id BIGINT DEFAULT nextval('common_transactions_seq_id') PRIMARY KEY,
			 block_height BIGINT REFERENCES blocks(height),
			 hash bytea NOT NULL UNIQUE,
			 timestamp varchar NOT NULL,
			 timestamp_unix BIGINT NOT NULL,
			 timestamp_nem BIGINT NOT NULL,
			 signer_id BIGINT REFERENCES accounts(id),
			 signature bytea,
			 deadline BIGINT NOT NULL,
			 fee BIGINT NOT NULL,

			 mosaic_id BIGINT REFERENCES mosaics(id),
			 supply_type INT NOT NULL,
			 delta BIGINT NOT NULL
			)""")

		cur.execute("CREATE INDEX mosaic_supplies_blockheight_idx ON mosaic_supplies(block_height)");

	def createTables(self):
		cur = self.conn.cursor()
		cur.execute("""
			SELECT 0 FROM pg_class where relname = 'common_transactions_seq_id'
			""")
		result = cur.fetchone()
		if result is None:
			cur.execute(" CREATE SEQUENCE common_transactions_seq_id;")

		cur.execute("""
			CREATE TABLE IF NOT EXISTS accounts
			(id BIGSERIAL PRIMARY KEY,
			 printablekey varchar UNIQUE,
			 publickey bytea)
			""")
		cur.execute("""
			CREATE TABLE IF NOT EXISTS blocks
			(height BIGINT PRIMARY KEY,
			 hash bytea NOT NULL,
			 timestamp varchar NOT NULL,
			 timestamp_unix BIGINT NOT NULL,
			 timestamp_nem BIGINT NOT NULL,
			 signer_id bigint REFERENCES accounts(id),
			 signature bytea NOT NULL,
			 type int NOT NULL,
			 difficulty bigint NOT NULL,
			 tx_count int NOT NULL,
			 fees bigint NOT NULL
			)
			""")
		cur.execute("""
			CREATE TABLE IF NOT EXISTS transfers
			(id BIGINT DEFAULT nextval('common_transactions_seq_id') PRIMARY KEY,
			 block_height BIGINT REFERENCES blocks(height),
			 hash bytea NOT NULL UNIQUE,
			 timestamp varchar NOT NULL,
			 timestamp_unix BIGINT NOT NULL,
			 timestamp_nem BIGINT NOT NULL,
			 signer_id BIGINT REFERENCES accounts(id),
			 signature bytea,
			 deadline BIGINT NOT NULL,

			 recipient_id BIGINT REFERENCES accounts(id),
			 amount BIGINT NOT NULL,
			 fee BIGINT NOT NULL,
			 message_type int,
			 message_data bytea
			 )
			""")
		cur.execute("""
			CREATE TABLE IF NOT EXISTS modifications
			(id BIGINT DEFAULT nextval('common_transactions_seq_id') PRIMARY KEY,
			 block_height BIGINT REFERENCES blocks(height),
			 hash bytea NOT NULL UNIQUE,
			 timestamp varchar NOT NULL,
			 timestamp_unix BIGINT NOT NULL,
			 timestamp_nem BIGINT NOT NULL,
			 signer_id BIGINT REFERENCES accounts(id),
			 signature bytea,
			 deadline BIGINT NOT NULL,

			 fee BIGINT NOT NULL,
			 min_cosignatories INT)
			""")

		cur.execute("""
			CREATE TABLE IF NOT EXISTS modification_entries
			(id BIGSERIAL PRIMARY KEY,
			 modification_id BIGINT REFERENCES modifications(id),
			 type int NOT NULL,
			 cosignatory_id BIGINT REFERENCES accounts(id)
			)
			""")

		cur.execute("""
			CREATE TABLE IF NOT EXISTS delegates
			(id BIGINT DEFAULT nextval('common_transactions_seq_id') PRIMARY KEY,
			 block_height BIGINT REFERENCES blocks(height),
			 hash bytea NOT NULL UNIQUE,
			 timestamp varchar NOT NULL,
			 timestamp_unix BIGINT NOT NULL,
			 timestamp_nem BIGINT NOT NULL,
			 signer_id BIGINT REFERENCES accounts(id),
			 signature bytea,
			 deadline BIGINT NOT NULL,

			 remote_id BIGINT REFERENCES accounts(id),
			 fee BIGINT NOT NULL,
			 mode int NOT NULL
			)
			""")

		cur.execute("""
			CREATE TABLE IF NOT EXISTS multisigs
			(id BIGINT DEFAULT nextval('common_transactions_seq_id') PRIMARY KEY,
			 block_height BIGINT REFERENCES blocks(height),
			 hash bytea NOT NULL UNIQUE,
			 timestamp varchar NOT NULL,
			 timestamp_unix BIGINT NOT NULL,
			 timestamp_nem BIGINT NOT NULL,
			 signer_id BIGINT REFERENCES accounts(id),
			 signature bytea,
			 deadline BIGINT NOT NULL,

			 fee BIGINT NOT NULL,
			 total_fees BIGINT NOT NULL,
			 signatures_count INT NOT NULL,
			 inner_id BIGINT NOT NULL,
			 inner_type INT NOT NULL
			)
			""")

		# we don't need common seq id in this one
		cur.execute("""
			CREATE TABLE IF NOT EXISTS signatures
			(id BIGSERIAL PRIMARY KEY,
			 block_height BIGINT REFERENCES blocks(height),
			 hash bytea NOT NULL,
			 timestamp varchar NOT NULL,
			 timestamp_unix BIGINT NOT NULL,
			 timestamp_nem BIGINT NOT NULL,
			 signer_id BIGINT REFERENCES accounts(id),
			 signature bytea,
			 deadline BIGINT NOT NULL,

			 fee BIGINT NOT NULL,
			 multisig_id BIGINT REFERENCES multisigs(id)
			)
			""")
		cur.execute("""
			CREATE TABLE IF NOT EXISTS inouts_type
			(id INT PRIMARY KEY,
			 name VARCHAR NOT NULL
			)
			""")
		cur.execute("SELECT count(id) FROM inouts_type");
		ret = cur.fetchone()[0]
		if ret == 0:
			cur.execute("INSERT INTO inouts_type VALUES (%s,%s)", (1, 'incoming'))
			cur.execute("INSERT INTO inouts_type VALUES (%s,%s)", (2, 'outgoing + fees'))
			cur.execute("INSERT INTO inouts_type VALUES (%s,%s)", (3, 'harvesting'))
			cur.execute("INSERT INTO inouts_type VALUES (%s,%s)", (4, 'incoming multisig'))
			cur.execute("INSERT INTO inouts_type VALUES (%s,%s)", (5, 'outgoing multisig + fees'))

		cur.execute("""
			CREATE TABLE IF NOT EXISTS inouts
			(id BIGSERIAL PRIMARY KEY,
			 account_id BIGINT REFERENCES accounts(id),
			 block_height BIGINT REFERENCES blocks(height),
			 type INT REFERENCES inouts_type(id),
			 tx_id BIGSERIAL,
			 amount BIGINT
			)
			""")
		if ret == 0:
			cur.execute("CREATE INDEX inouts_type_idx on inouts(type)");	
			cur.execute("CREATE INDEX transfers_blockheight_idx ON transfers(block_height)");
			cur.execute("CREATE INDEX modifications_blockheight_idx ON modifications(block_height)");
			cur.execute("CREATE INDEX delegates_blockheight_idx ON delegates(block_height)");
			cur.execute("CREATE INDEX multisigs_blockheight_idx ON multisigs(block_height)");
			cur.execute("CREATE INDEX signatures_blockheight_idx ON signatures(block_height)");

		cur.execute("""
			CREATE TABLE IF NOT EXISTS harvests
			(id BIGSERIAL PRIMARY KEY,
			 account_id BIGINT REFERENCES accounts(id),
			 block_height BIGINT REFERENCES blocks(height)
			)
			""")
		
		cur.execute("select exists(select * from information_schema.tables where table_name=%s)", ('namespaces',))
		hasNamespaces = cur.fetchone()[0]

		if not hasNamespaces:
			self.createNamespaceTables(cur)

		cur.execute("select exists(select * from information_schema.tables where table_name=%s)", ('mosaic_supplies',))
		hasSupplies = cur.fetchone()[0]
		if not hasSupplies:
			self.createSupplies(cur)

		cur.close()
		self.conn.commit()

	def _addHarvested(self, cur, account_id, height):
		sql = "INSERT INTO harvests (account_id,block_height) VALUES (%s,%s)";
		obj = (account_id, height)
		cur.execute(sql, obj)

	def _addInout(self, cur, account_id, height, inout_type, tx_id, amount):
		sql = "INSERT INTO inouts (account_id,block_height,type,tx_id,amount) VALUES (%s,%s,%s,%s,%s)";
		obj = (account_id, height, inout_type, tx_id, amount)
		cur.execute(sql, obj)

	def _clearMosInouts(self, cur, mosDbId):
		sql = "DELETE FROM mosaic_inouts where mosaic_id=%s"
		cur.execute(sql, (mosDbId,))

		sql = "DELETE FROM mosaic_amounts WHERE mosaic_id=%s"
		cur.execute(sql, (mosDbId,))

	def _getPreviousAmount(self, cur, mosDbId, account_id):
		sql = "SELECT amount,block_height,id FROM mosaic_amounts WHERE mosaic_id=%s AND account_id=%s ORDER BY block_height DESC LIMIT 1"
		obj = (mosDbId, account_id)
		cur.execute(sql, obj)
		return cur.fetchone()

	def _addMosInout(self, cur, mosDbId, account_id, height, inout_type, tx_id, amount):
		sql = "INSERT INTO mosaic_inouts (account_id,block_height,mosaic_id, type,tx_id,quantity) VALUES (%s,%s,%s, %s,%s,%s)";
		obj = (account_id, height, mosDbId, inout_type, tx_id, amount)
		cur.execute(sql, obj)

		ret = self._getPreviousAmount(cur, mosDbId, account_id)
		val = ret[0] if ret else 0
		if inout_type in [1, 1+3, 11, 11+3]:
			val += amount
		elif inout_type in [2, 2+3, 12, 12+3]:
			val -= amount
		else:
			print inout_type
			raise 1

		# nembex is not running postgres 9.5, so we can't take advantage of UPSERT
		# but since update is running from a single process we're fine
		if ret and ret[1] == height:
			#print "need to update instead of insert",ret[2]
			sql = "UPDATE  mosaic_amounts SET amount=%s WHERE id=%s"
			obj = (val, ret[2])
		else:
			sql = "INSERT INTO mosaic_amounts (account_id,block_height,mosaic_id, amount) VALUES (%s,%s,%s, %s)"
			obj = (account_id, height, mosDbId, val)
		cur.execute(sql, obj)

	def addInout(self, account_id, height, inout_type, tx_id, amount):
		cur = self.conn.cursor()
		if amount > 0:
			self._addInout(cur, account_id, height, inout_type, tx_id, amount)
		self._addHarvested(cur, account_id, height)
		cur.close()

	@staticmethod
	def _getMosaicFqdn(mosaic):
		mId = mosaic['mosaicId']
		return mId['namespaceId'] + '.' + mId['name']

	def addInouts(self, block, txes):
		def inoutTransfer(cur, height, tx, txId, fee, multi):
			v = tx['version'] & 0xffffff
			srcId = tx['signer_id']
			dstId = tx['recipient_id']

			if v == 1 or ('mosaics' not in tx):
				if srcId == dstId:
					self._addInout(cur, srcId, height, 2+multi, txId, fee)
				else:
					self._addInout(cur, srcId, height, 2+multi, txId, tx['amount']+fee)
					self._addInout(cur, dstId, height, 1+multi, txId, tx['amount'])
			else:
				# amount doesn't mean anything, we need to process attachments
				# to check if there is nem.xem, multiply it
				print tx
				amount = tx['amount'] / 1000000
				qs = defaultdict(long)
				for mosaic in tx['mosaics']:
					mosName = Db._getMosaicFqdn(mosaic)
					qs[mosName] += mosaic['quantity']
				
				locdb = Db(True)
				loccur = locdb.conn.cursor()
				for mosFqdn,_v in qs.iteritems():
					v = _v*amount
					if mosFqdn == 'nem.xem':
						if srcId == dstId:
							self._addInout(cur, srcId, height, 2+multi, txId, fee)
						else:
							self._addInout(cur, srcId, height, 2+multi, txId, v+fee)
							self._addInout(cur, dstId, height, 1+multi, txId, v)
					else:
						mosaic = locdb._getMosaic(loccur, 'mosaic_fqdn', mosFqdn)
						mosId = mosaic['id']
						self._addMosInout(cur, mosId, srcId, height, 2+multi, txId, v)
						self._addMosInout(cur, mosId, dstId, height, 1+multi, txId, v)
						if mosaic['levy']:
							mosId = mosaic['levy']['fee_mosaic']['id']
							dstId = mosaic['levy']['recipient_id']
							levyFee = self._calculateLevy(mosaic['levy']['type'], amount, _v, mosaic['levy']['fee'])
							#print "LEVY:"
							#del mosaic['levy']['fee_mosaic']
							#print mosaic['levy']
							self._addMosInout(cur, mosId, srcId, height, 12+multi, txId, levyFee)
							self._addMosInout(cur, mosId, dstId, height, 11+multi, txId, levyFee)
				loccur.close()

		def inoutFee(cur, height, tx, txId, fee, multi):
			srcId = tx['signer_id']
			self._addInout(cur, srcId, height, 2+multi, txId, fee)

		def inoutSink(cur, height, tx, txId, fee, multi):
			srcId = tx['signer_id']
			dstId = tx['rentalFeeSink_id']

			self._addInout(cur, srcId, height, 2+multi, txId, tx['rentalFee']+fee)
			self._addInout(cur, dstId, height, 1+multi, txId, tx['rentalFee'])

		def getPropsMap(tx):
			_props = {}
			for prop in tx['properties']:
				_props[ prop['name'] ] = prop['value']
			_props['divisibility'] = int(_props['divisibility'], 10)
			_props['initialSupply'] = int(_props['initialSupply'], 10)
			return _props

		def supplyToValue(props, supply):
			mul = 10 ** props['divisibility']
			return supply * mul

		def inoutSinkMosaic(cur, height, tx, txId, fee, multi):
			srcId = tx['signer_id']
			dstId = tx['creationFeeSink_id']

			self._addInout(cur, srcId, height, 2+multi, txId, tx['creationFee']+fee)
			self._addInout(cur, dstId, height, 1+multi, txId, tx['creationFee'])

			#print "----"
			#print tx
			#print "----"
			self._clearMosInouts(cur, tx['id'])
			_props = getPropsMap(tx['mosaicDefinition'])
			v = supplyToValue(_props, _props['initialSupply'])
			self._addMosInout(cur, tx['id'], srcId, height, 1+multi, txId, v)

		def inoutSupply(cur, height, tx, txId, fee, multi):
			srcId = tx['signer_id']

			inoutFee(cur, height, tx, txId, fee, multi)

			locdb = Db(True)
			loccur = locdb.conn.cursor()
			mosFqdn = Db._getMosaicFqdn(tx)
			mosaic = locdb._getMosaic(loccur, 'mosaic_fqdn', mosFqdn)
			loccur.close()
			
			#print mosaic
			_props = getPropsMap(mosaic)
			v = supplyToValue(_props, tx['delta'])
			#print v

			if tx['supplyType'] == 1:
				self._addMosInout(cur, mosaic['id'], srcId, height, 1+multi, txId, v)
			else:
				self._addMosInout(cur, mosaic['id'], srcId, height, 2+multi, txId, v)

		cur = self.conn.cursor()
		handlers = {
			257: inoutTransfer # transfer
			, 2049: inoutFee   # importance
			, 4097: inoutFee   # multisig
			, 8193: inoutSink  # namespace
			, 16385: inoutSinkMosaic # mosic creation
			, 16386: inoutSupply     # mosaicSupply
		}
		blockHeight = block['height']
		for tx in txes:
			#print tx
			txId = tx['id']
			txType = tx['type']
			fee = tx['fee']
			multi = 0
			# handle multisig
			if txType == 4100:
				fee = tx['total_fee']
				txType = tx['otherTrans']['type']
				tx = tx['otherTrans']
				multi = 3
			txid = handlers[txType](cur, blockHeight, tx, txId, fee, multi)
		
		cur.close()
			

	def _addMultisig(self, cur, block, tx):
		handlers = {
			    257: self._addTransfer
			,  2049: self._addDelegated
			,  4097: self._addAggregateModification
			,  8193: self._addNamespace
			, 16385: self._addMosaic
			, 16386: self._addMosaicSupply
		}
		#inner tx
		itx = tx['otherTrans']
		if itx['type'] not in handlers:
			print "ITX TYPE", itx['type']
		innerId = handlers[itx['type']](cur, block, itx)
		itx['id'] = innerId
		totalFee = tx['total_fee']

		#print tx
		sql = "INSERT INTO multisigs (block_height,hash,timestamp,timestamp_unix,timestamp_nem, signer_id,signature,deadline, fee,total_fees,signatures_count,inner_id,inner_type) VALUES (%s,%s, %s,%s,%s, %s,%s,%s, %s,%s,%s,%s,%s) RETURNING id";
		obj = (block['height'],
			tobin(tx['hash']),
			tx['timestamp'],
			tx['timestamp_unix'],
			tx['timestamp_nem'],
			tx['signer_id'],
			tobin(tx['signature']),
			tx['deadline'],

			tx['fee'],
			totalFee,
			len(tx['signatures']),
			innerId,
			itx['type']
		)

		#print " [+] adding to db: ", obj,
		cur.execute(sql, obj)
		retId = cur.fetchone()[0]
		sql = "INSERT INTO signatures (block_height,hash,timestamp,timestamp_unix,timestamp_nem, signer_id,signature,deadline, fee,multisig_id) VALUES (%s,%s, %s,%s,%s, %s,%s,%s, %s,%s) RETURNING id";
		for sig in tx['signatures']:
			obj = (block['height'],
				tobin(hexlify("N/A")), #tobin(sig['hash']),
				sig['timestamp'],
				sig['timestamp_unix'],
				sig['timestamp_nem'],
				sig['signer_id'],
				tobin(sig['signature']),
				sig['deadline'],

				sig['fee'],
				retId
			)
			cur.execute(sql,obj)
		#
		return retId

	def _addDelegated(self,cur,block,tx):
		sql = "INSERT INTO delegates (block_height,hash,timestamp,timestamp_unix,timestamp_nem, signer_id,signature,deadline, remote_id,fee,mode) VALUES (%s,%s, %s,%s,%s, %s,%s,%s, %s,%s,%s) RETURNING id";
		obj = (block['height'],
			tobin(tx['hash']),
			tx['timestamp'],
			tx['timestamp_unix'],
			tx['timestamp_nem'],
			tx['signer_id'],
			None if 'signature' not in tx else tobin(tx['signature']),
			tx['deadline'],

			tx['remote_id'],
			tx['fee'],
			tx['mode']
		)
		#print " [+] adding to db: ", obj,
		cur.execute(sql, obj)
		retId = cur.fetchone()[0]
		#print retId
		return retId


	def _addAggregateModification(self, cur, block, tx):
		sql = "INSERT INTO modifications (block_height,hash,timestamp,timestamp_unix,timestamp_nem, signer_id, signature,deadline, fee, min_cosignatories) VALUES (%s,%s, %s,%s,%s, %s,%s,%s, %s, %s) RETURNING id";
		locdb = Db(True)
		ret = locdb.getModification('signer_id', int(tx['signer_id']))
		if ('minCosignatories' in tx) and ('relativeChange' in tx['minCosignatories']):
			relative = tx['minCosignatories']['relativeChange']
			if ret is None:
				print "MODIFICATION NO old, rel is", relative, len(tx['modifications'])
				min_cosignatories = relative 
			else:
				min_cosignatories = ret['min_cosignatories'] + relative
				print "MODIFICATION has old, rel is", relative, " prev ",  ret['min_cosignatories']
		else:
			# old txes...
			min_cosignatories = 0
			print "MODIFICATION OLD", min_cosignatories

		obj = (block['height'],
			tobin(tx['hash']),
			tx['timestamp'],
			tx['timestamp_unix'],
			tx['timestamp_nem'],
			tx['signer_id'],
			None if 'signature' not in tx else tobin(tx['signature']),
			tx['deadline'],
			tx['fee'],
			min_cosignatories
		)
		#print " [+] adding to db: ", obj,
		cur.execute(sql, obj)
		retId = cur.fetchone()[0]
		#print retId
		sql = "INSERT INTO modification_entries (modification_id,type,cosignatory_id) VALUES(%s,%s,%s)"
		for modification in tx['modifications']:
			obj = (retId, modification['modificationType'], modification['cosignatory_id'])
			cur.execute(sql,obj)
		#
		return retId

	def _addNamespace(self, cur, block, tx):
		parent = None
		if tx['parent']:
			locdb = Db(True)
			ret = locdb.getNamespace('namespace_name', tx['parent'])
			parent = ret

		fqdn = (parent['namespace_name'] + '.' if parent else '') + tx['newPart']

		sql = "INSERT INTO namespaces (block_height,hash, timestamp,timestamp_unix,timestamp_nem, signer_id,signature,deadline,fee, rental_sink, rental_fee, parent_ns, namespace_name, namespace_part) VALUES (%s,%s, %s,%s,%s, %s,%s,%s,%s, %s, %s, %s, %s, %s) RETURNING id"
		obj = (block['height'],
			tobin(tx['hash']),
			tx['timestamp'],
			tx['timestamp_unix'],
			tx['timestamp_nem'],
			tx['signer_id'],
			None if 'signature' not in tx else tobin(tx['signature']),
			tx['deadline'],
			tx['fee'],

			tx['rentalFeeSink_id'],
			tx['rentalFee'],
			parent['id'] if parent else None,
			fqdn,
			tx['newPart']
		)
		#print " [+] adding to db: ", obj,
		cur.execute(sql,obj)
		retId = cur.fetchone()[0]
		#print retId
		return retId
	
	def _addMosaic(self, cur, block, tx):
		locdb = Db(True)
		ret = locdb.getNamespace('namespace_name', tx['mosaicDefinition']['id']['namespaceId'])
		parent = ret

		sql = "INSERT INTO mosaics (block_height,hash, timestamp,timestamp_unix,timestamp_nem, signer_id,signature,deadline,fee, creation_sink, creation_fee, parent_ns, mosaic_name, mosaic_fqdn, mosaic_description) VALUES (%s,%s, %s,%s,%s, %s,%s,%s,%s, %s, %s, %s, %s, %s, %s) RETURNING id"
		mosaicFqdn = parent['namespace_name'] + '.' + tx['mosaicDefinition']['id']['name']
		obj = (block['height'],
			tobin(tx['hash']),
			tx['timestamp'],
			tx['timestamp_unix'],
			tx['timestamp_nem'],
			tx['signer_id'],
			None if 'signature' not in tx else tobin(tx['signature']),
			tx['deadline'],
			tx['fee'],

			tx['creationFeeSink_id'],
			tx['creationFee'],
			parent['id'],
			tx['mosaicDefinition']['id']['name'],
			mosaicFqdn,
			tx['mosaicDefinition']['description']
		)
		#print " [+] adding to db: ", obj,
		cur.execute(sql,obj)
		retId = cur.fetchone()[0]
		#print retId
		
		sql = "INSERT INTO mosaic_properties (block_height,mosaic_id,name,value) VALUES(%s,%s,%s,%s)"
		quantity = 0
		for prop in tx['mosaicDefinition']['properties']:
			obj = (block['height'],retId, prop['name'], prop['value'])
			cur.execute(sql,obj)
			
			if prop['name'] == 'initialSupply':
				quantity = int(prop['value'], 10)

		sql = "INSERT INTO mosaic_state_supply (block_height, mosaic_id, quantity) VALUES (%s, %s, %s)"
		obj = (block['height'], retId, quantity)
		cur.execute(sql, obj)

		mosLevy = tx['mosaicDefinition']['levy']
		if 'recipient' in mosLevy:
			levyMosFqdn = Db._getMosaicFqdn(mosLevy)
			if levyMosFqdn == mosaicFqdn:
				levyMosaic = {'id': retId }
			else:
				loccur = locdb.conn.cursor()
				levyMosaic = locdb._getMosaic(loccur, 'mosaic_fqdn', levyMosFqdn)
				loccur.close()
			sql = "INSERT INTO mosaic_levys (block_height,mosaic_id, type,recipient_id,fee_mosaic_id,fee) VALUES(%s,%s, %s,%s,%s,%s)"
			obj = (block['height'],retId, mosLevy['type'],mosLevy['recipient_id'],levyMosaic['id'],mosLevy['fee'])
			cur.execute(sql,obj)

		return retId

	def _getPreviousStateSupply(self, cur, mosDbId):
		sql = "SELECT quantity,block_height,id FROM mosaic_state_supply WHERE mosaic_id=%s ORDER BY block_height DESC LIMIT 1"
		obj = (mosDbId,)
		cur.execute(sql, obj)
		return cur.fetchone()

	def _addMosaicSupply(self, cur, block, tx):
		locdb = Db(True)
		mosFqdn = Db._getMosaicFqdn(tx)
		loccur = locdb.conn.cursor()
		mosaic = locdb._getMosaic(loccur, 'mosaic_fqdn', mosFqdn)
		loccur.close()

		sql = "INSERT INTO mosaic_supplies (block_height,hash, timestamp,timestamp_unix,timestamp_nem, signer_id,signature,deadline,fee, mosaic_id, supply_type, delta) VALUES (%s,%s, %s,%s,%s, %s,%s,%s,%s, %s,%s,%s) RETURNING id"
		obj = (block['height'],
			tobin(tx['hash']),
			tx['timestamp'],
			tx['timestamp_unix'],
			tx['timestamp_nem'],
			tx['signer_id'],
			None if 'signature' not in tx else tobin(tx['signature']),
			tx['deadline'],
			tx['fee'],

			mosaic['id'],
			tx['supplyType'],
			tx['delta']
		)
		#print " [+] adding to db: ", obj,
		cur.execute(sql,obj)
		retId = cur.fetchone()[0]

		ret = self._getPreviousStateSupply(cur, mosaic['id'])
		val = ret[0] if ret else 0
		print "INSERTING ", val, " -> ",
		if tx['supplyType'] == 1:
			val += tx['delta']
		elif tx['supplyType'] == 2:
			val -= tx['delta']
		else:
			print "FAILED ON A TX:"
			print tx
			raise 1

		print val

		if ret and ret[1] == block['height']:
			sql = "UPDATE mosaic_state_supply SET quantity=%s WHERE id=%s"
			obj = (val, ret[2])
		else:
			sql = "INSERT INTO mosaic_state_supply (block_height, mosaic_id, quantity) VALUES (%s, %s, %s)"
			obj = (block['height'], mosaic['id'], val)
		cur.execute(sql, obj)

		#print retId
		return retId

	def _calculateLevy(self, levyType, multiplier, quantity, levyFee):
		if levyType == 1:
			return levyFee
		elif levyType == 2:
			return multiplier * quantity * levyFee / 10000
		

	def _addTransfer(self, cur, block, tx):
		v = tx['version'] & 0xffffff
		sql = "INSERT INTO transfers (block_height,hash,timestamp,timestamp_unix,timestamp_nem, signer_id, signature,deadline, recipient_id,amount,fee,message_type,message_data) VALUES (%s,%s, %s,%s,%s, %s,%s,%s, %s,%s,%s,%s,%s) RETURNING id";
		obj = (block['height'],
			tobin(tx['hash']),
			tx['timestamp'],
			tx['timestamp_unix'],
			tx['timestamp_nem'],
			tx['signer_id'],
			None if 'signature' not in tx else tobin(tx['signature']),
			tx['deadline'],
			tx['recipient_id'],
			tx['amount'],
			tx['fee'],
			None if len(tx['message']) == 0 else tx['message']['type'],
			None if len(tx['message']) == 0 else tobin(tx['message']['payload'])
		)
		#print " [+] adding to db: ", obj,
		cur.execute(sql, obj)
		retId = cur.fetchone()[0]
		#print retId

		if v == 2:
			locdb = Db(True)
			loccur = locdb.conn.cursor()
			for a in tx['mosaics']:
				mosFqdn = Db._getMosaicFqdn(a)
				mosaic = locdb._getMosaic(loccur, 'mosaic_fqdn', mosFqdn)
				sql = "INSERT INTO transfer_attachments (block_height,transfer_id, type,mosaic_id,quantity) VALUES(%s,%s, %s,%s,%s)"

				assert (a['quantity'] * tx['amount']) % 1000000 == 0, "invalid amount in a tx"
				obj = (block['height'],retId, 2, mosaic['id'], tx['amount']*a['quantity'] / 1000000 )
				cur.execute(sql,obj)

				if mosaic['levy']:
					levyFee = self._calculateLevy(mosaic['levy']['type'], tx['amount'], a['quantity'], mosaic['levy']['fee'])
					obj = (block['height'],retId, 12, mosaic['levy']['fee_mosaic']['id'], levyFee)
					cur.execute(sql,obj)
				
			loccur.close()
		return retId

	def _addTxes(self, cur, block, txes):
		handlers = {
			    257: self._addTransfer
			,  2049: self._addDelegated
			,  4097: self._addAggregateModification
			,  4100: self._addMultisig
			,  8193: self._addNamespace
			, 16385: self._addMosaic
			, 16386: self._addMosaicSupply
		}
		for tx in txes:
			print ('processing tx', tx['type'])
			txid = handlers[tx['type']](cur, block, tx)
			tx['id'] = txid
			# ugly hack for multiple txes in same block :/
			if tx['type'] == 8193 or tx['type'] == 16386:
				cur.close()
				self.commit()
				cur = self.conn.cursor()

	def processTxes(self, block, txes):
		cur = self.conn.cursor()
		self._addTxes(cur, block, txes)
		cur.close()

	def addBlock(self, block):
		cur = self.conn.cursor()
		sql = "INSERT INTO blocks (height,hash,timestamp,timestamp_unix, timestamp_nem, signer_id, signature, type, difficulty, tx_count, fees) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) RETURNING height";
		obj = (block['height'],
			tobin(block['hash']),
			block['timestamp'],
			block['timestamp_unix'],
			block['timestamp_nem'],
			block['signer_id'],
			tobin(block['signature']),
			block['type'],
			block['difficulty'],
			block['tx_count'],
			block['fees'])
		#print " [+] adding to db: ", obj
		cur.execute(sql, obj)
		retId = cur.fetchone()[0]
		cur.close()
		return retId

	def findBlock(self, height):
		cur = self.conn.cursor()
		cur.execute("SELECT * FROM blocks WHERE height = %s", (height,))
		data = cur.fetchone()
		cur.close()
		return data

	def getLastHeight(self):
		cur = self.conn.cursor()
		cur.execute('SELECT height FROM blocks ORDER BY height DESC LIMIT 1')
		data = cur.fetchone()
		cur.close()
		if data is None:
			return 0
		return data[0]
		
	def getBlocks(self, height):
		cur = self.conn.cursor()
		cur.execute('SELECT a.printablekey as "s_printablekey",a.publickey as "s_publickey",b.* FROM blocks b,accounts a WHERE b.height < %s AND b.signer_id=a.id ORDER BY height DESC LIMIT 25', (height,))
		data = cur.fetchall()
		cur.close()
		return data

	def getBlocksStats(self):
		cur = self.conn.cursor()
		cur.execute('SELECT height,timestamp_nem,difficulty,fees FROM blocks ORDER BY height DESC LIMIT 5000')
		data = cur.fetchall()
		cur.close()
		return data


	def getBlock(self, height):
		cur = self.conn.cursor()
		cur.execute('SELECT a.printablekey as "s_printablekey",a.publickey as "s_publickey",b.* FROM blocks b,accounts a WHERE b.height=%s AND b.signer_id=a.id ORDER BY height DESC LIMIT 1', (height,))
		data = cur.fetchone()
		cur.close()
		return data

	def getBlockByHash(self, blockHash):
		cur = self.conn.cursor()
		cur.execute('SELECT a.printablekey as "s_printablekey",a.publickey as "s_publickey",b.* FROM blocks b,accounts a WHERE b.hash=%s AND b.signer_id=a.id ORDER BY height DESC LIMIT 1', (tobin(blockHash),))
		data = cur.fetchone()
		cur.close()
		return data

	def getInouts(self, accId, txId, limit):
		cur = self.conn.cursor()
		cur.execute("SELECT * FROM inouts WHERE account_id = %s AND type<>3 AND id < %s ORDER BY id DESC LIMIT {}".format(limit), (accId,txId))
		data = cur.fetchall()
		cur.close()
		return data

	def getInoutsNext(self, accId, txId,limit):
		cur = self.conn.cursor()
		cur.execute('SELECT id FROM inouts WHERE account_id = %s AND type<>3 AND id > %s ORDER BY id DESC LIMIT '+str(limit), (accId, txId))
		data = cur.fetchall()
		cur.close()
		return data

	def getAccountMosaics(self, accId, limit):
		cur = self.conn.cursor()
		cur.execute('SELECT * FROM (SELECT *,row_number() OVER (PARTITION BY mosaic_id ORDER BY block_height DESC) AS rn FROM mosaic_amounts WHERE account_id=%s) q WHERE amount != 0 AND rn = 1 ORDER BY block_height DESC LIMIT '+str(limit), (accId,))
		data = cur.fetchall()
		print data
		cur.close()
		return data

	def getTransferSql(self,compare, comparator, limit):
		return 'SELECT s.printablekey as "s_printablekey",s.publickey as "s_publickey",r.printablekey as "r_printablekey",r.publickey as "r_publickey",t.* FROM transfers t,accounts s,accounts r WHERE t.{}{}%s AND t.signer_id=s.id AND t.recipient_id=r.id ORDER BY id DESC {}'.format(compare,comparator,limit)

	def getMatchingTransfers(self, ids, limit):
		cur = self.conn.cursor()
		cur.execute(self.getTransferSql('id', ' IN ', 'LIMIT '+str(limit)), (tuple(list(ids)),))
		#print cur.mogrify(self.getTransferSql('id', ' IN ', 'LIMIT 10'), (tuple(ids),))
		data = cur.fetchall()
		cur.execute('SELECT * FROM transfer_attachments t WHERE t.transfer_id IN %s ORDER BY id,type DESC', (tuple(list(ids)),))
		attachments = cur.fetchall()
		m = defaultdict(list)
		for e in attachments:
			m[e['transfer_id']].append(e)
		for e in data:
			if e['id'] in m:
				e['attachments'] = m[e['id']]
		cur.close()
		return data
	
	def getTransfer(self, compare, dataCompare):
		cur = self.conn.cursor()
		cur.execute(self.getTransferSql(compare, '=', 'LIMIT 1'), (dataCompare,))
		data = cur.fetchone()
		if data:
			cur.execute('SELECT * FROM transfer_attachments t WHERE t.transfer_id = %s ORDER BY id,type DESC', (data['id'],))
			att = cur.fetchall()

			ids = set(map(lambda e: e['mosaic_id'], att))
			m = {}
			if len(ids) > 0:
				_mosaicDefinitions = self.getMatchingMosaics(ids, 10)
				for e in _mosaicDefinitions:
					m[e['id']] = e
			data['mosaics'] = m
			data['attachments'] = att

		cur.close()
		return data
		
	def getMessages(self, spammers, txId, limit):
		spammersFrom = spammers[0]
		spammersTo = spammers[1]
		#print spammersFrom, spammersTo
		cur = self.conn.cursor()
		sql = 'SELECT s.printablekey as "s_printablekey",s.publickey as "s_publickey",r.printablekey as "r_printablekey",r.publickey as "r_publickey",t.* FROM transfers t,accounts s,accounts r WHERE t.id<%s AND t.message_type=1 AND t.signer_id=s.id AND t.recipient_id=r.id AND s.printablekey NOT IN %s AND r.printablekey NOT IN %s ORDER BY id DESC LIMIT {}'.format(limit)
		cur.execute(sql, (txId, tuple(spammersFrom), tuple(spammersTo)))
		#print cur.mogrify(sql, (txId, tuple(spammersFrom), tuple(spammersTo)))
		data = cur.fetchall()
		cur.close()
		return data
	
	def getMessagesNext(self, spammers, txId, limit):
		spammersFrom = spammers[0]
		spammersTo = spammers[1]
		cur = self.conn.cursor()
		sql = 'SELECT t.id FROM transfers t,accounts s,accounts r WHERE t.id > %s AND t.message_type=1 AND t.signer_id=s.id AND t.recipient_id=r.id AND s.printablekey NOT IN %s AND r.printablekey NOT IN %s ORDER BY id ASC LIMIT {}'.format(limit)
		cur.execute(sql, (txId, tuple(spammersFrom), tuple(spammersTo)))
		data = cur.fetchall()
		cur.close()
		return data


	def getTransfers(self, txId, limit):
		cur = self.conn.cursor()
		cur.execute(self.getTransferSql('id', '<', 'LIMIT '+str(limit)), (txId,))
		data = cur.fetchall()
		cur.close()
		return data

	def getBlockTransfers(self, height):
		cur = self.conn.cursor()
		cur.execute(self.getTransferSql('block_height', '=', ''), (height,))
		data = cur.fetchall()
		cur.close()
		return data

	def getDelegateSql(self, compare, comparator, limit):
		return 'SELECT s.printablekey as "s_printablekey",s.publickey as "s_publickey",r.printablekey as "r_printablekey",r.publickey as "r_publickey",d.* FROM delegates d,accounts s,accounts r WHERE d.{}{}%s AND d.signer_id=s.id AND d.remote_id=r.id ORDER BY id DESC {}'.format(compare, comparator, limit)
	
	def getMatchingDelegates(self, ids, limit):
		cur = self.conn.cursor()
		cur.execute(self.getDelegateSql('id', ' IN ', 'LIMIT '+str(limit)), (tuple(list(ids)),))
		data = cur.fetchall()
		cur.close()
		return data
	
	def getDelegate(self, compare, dataCompare):
		cur = self.conn.cursor()
		cur.execute(self.getDelegateSql(compare, '=', 'LIMIT 1'), (dataCompare,))
		data = cur.fetchone()
		cur.close()
		return data

	def getDelegates(self, txId, limit):
		cur = self.conn.cursor()
		cur.execute(self.getDelegateSql('id', '<', 'LIMIT '+str(limit)), (txId,))
		data = cur.fetchall()
		cur.close()
		return data

	def getBlockDelegates(self, height):
		cur = self.conn.cursor()
		cur.execute(self.getDelegateSql('block_height', '=', ''), (height,))
		data = cur.fetchall()
		cur.close()
		return data

	def _getModificationEntriesCounts(self, cur, txIds):
		cur.execute('SELECT modification_id,count(*) from modification_entries WHERE modification_id in %s GROUP BY modification_id', (tuple(list(txIds)),))
		data = cur.fetchall()
		return data

	def _addCounts(self, cur, data):
		if len(data) == 0:
			return
		# placing this here is not very nice, but will be more convenient
		counts = self._getModificationEntriesCounts(cur, map(lambda x: x['id'], data))
		temp = {}
		for elem in counts:
			temp[elem['modification_id']] = elem['count']
		for tx in data:
			tx['modifications_count'] = temp[tx['id']]

	def getModificationSql(self, compare, comparator, limit):
		return 'SELECT s.printablekey as "s_printablekey",s.publickey as "s_publickey",m.* FROM modifications m,accounts s WHERE m.{}{}%s AND m.signer_id=s.id ORDER BY id DESC {}'.format(compare,comparator, limit)

	def getMatchingModifications(self, ids, limit):
		cur = self.conn.cursor()
		cur.execute(self.getModificationSql('id', ' IN ', 'LIMIT '+str(limit)), (tuple(list(ids)),))
		data = cur.fetchall()
		cur.close()
		return data
	
	def getModification(self, compare, dataCompare):
		cur = self.conn.cursor()
		cur.execute(self.getModificationSql(compare, '=', 'LIMIT 1'), (dataCompare,))
		data = cur.fetchone()
		if data != None:
			cur.execute('SELECT a.printablekey as "printablekey",a.publickey as "publickey",e.* FROM modification_entries e,accounts a WHERE e.modification_id = %s AND e.cosignatory_id=a.id', (data['id'],))
			data['modifications'] = cur.fetchall()
			data['modifications_count'] = len(data['modifications'])
		cur.close()
		return data


	def getModifications(self, txId, limit):
		cur = self.conn.cursor()
		cur.execute(self.getModificationSql('id', '<', 'LIMIT '+str(limit)), (txId,))
		data = cur.fetchall()
		self._addCounts(cur, data)
		cur.close()
		return data

	def getBlockModifications(self, height):
		cur = self.conn.cursor()
		cur.execute(self.getModificationSql('block_height', '=', ''), (height,))
		data = cur.fetchall()
		self._addCounts(cur, data)
		cur.close()
		return data

	def getMultisigSql(self, compare, comparator, limit):
		return 'SELECT s.printablekey as "s_printablekey",s.publickey as "s_publickey",m.* FROM multisigs m,accounts s WHERE m.{}{}%s AND m.signer_id=s.id ORDER BY id DESC {}'.format(compare,comparator,limit)

	def getMatchingMultisigs(self, ids, limit):
		cur = self.conn.cursor()
		cur.execute(self.getMultisigSql('id', ' IN ', 'LIMIT '+str(limit)), (tuple(list(ids)),))
		data = cur.fetchall()
		cur.close()
		return data
	
	def getMultisig(self, compare, dataCompare):
		cur = self.conn.cursor()
		cur.execute(self.getMultisigSql(compare, '=', 'LIMIT 1'), (dataCompare,))
		data = cur.fetchone()
		if data != None:
			switch = {
				257: self.getTransfer
				, 2049: self.getDelegate
				, 4097: self.getModification
				, 8193: self.getNamespace
				, 16385: self.getMosaic
				#, 16386: self.getMosaicSupply
			}
			data['inner'] = switch[data['inner_type']]('id', data['inner_id'])

			cur.execute('SELECT a.printablekey as "s_printablekey",a.publickey as "s_publickey",s.* FROM signatures s,accounts a WHERE s.multisig_id = %s AND s.signer_id=a.id', (data['id'],))
			data['signatures'] = cur.fetchall()
			data['signatures_count'] = len(data['signatures'])
		cur.close()
		return data

	def getMultisigs(self, txId, limit):
		cur = self.conn.cursor()
		cur.execute(self.getMultisigSql('id', '<', 'LIMIT '+str(limit)), (txId,))
		data = cur.fetchall()
		cur.close()
		return data

	def getBlockMultisigs(self, height):
		cur = self.conn.cursor()
		cur.execute(self.getMultisigSql('block_height', '=', ''), (height,))
		data = cur.fetchall()
		cur.close()
		return data

	def getNamespaceSql(self,compare, comparator, limit):
		return 'SELECT s.printablekey as "s_printablekey",s.publickey as "s_publickey",sink.printablekey as "sink_printablekey",sink.publickey as "sink_publickey",t.* FROM namespaces t,accounts s,accounts sink WHERE t.{}{}%s AND t.signer_id=s.id AND t.rental_sink=sink.id ORDER BY id DESC {}'.format(compare,comparator,limit)

	def getNamespace(self, compare, dataCompare):
		cur = self.conn.cursor()
		print cur.mogrify(self.getNamespaceSql(compare, '=', 'LIMIT 1'), (dataCompare,))
		cur.execute(self.getNamespaceSql(compare, '=', 'LIMIT 1'), (dataCompare,))
		data = cur.fetchone()
		cur.close()
		return data

	def getNamespaces(self, txId, limit):
		cur = self.conn.cursor()
		cur.execute(self.getNamespaceSql('id', '<', 'LIMIT '+str(limit)), (txId,))
		data = cur.fetchall()
		cur.close()
		return data

	def getBlockNamespaces(self, height):
		cur = self.conn.cursor()
		cur.execute(self.getNamespaceSql('block_height', '=', ''), (height,))
		data = cur.fetchall()
		cur.close()
		return data


	def getRootNamespaces(self):
		sql = 'SELECT s.printablekey as "s_printablekey",s.publickey as "s_publickey",sink.printablekey as "sink_printablekey",sink.publickey as "sink_publickey",t.* FROM namespaces t,accounts s,accounts sink WHERE t.parent_ns IS NULL AND t.signer_id=s.id AND t.rental_sink=sink.id ORDER BY t.namespace_name ASC'
		cur = self.conn.cursor()
		cur.execute(sql)
		data = cur.fetchall()
		cur.close()
		return data

	def getNamespacesFrom(self, nsId):
		sql = 'SELECT s.printablekey as "s_printablekey",s.publickey as "s_publickey",sink.printablekey as "sink_printablekey",sink.publickey as "sink_publickey",t.* FROM namespaces t,accounts s,accounts sink WHERE t.parent_ns = %s AND t.signer_id=s.id AND t.rental_sink=sink.id ORDER BY t.namespace_name ASC'
		cur = self.conn.cursor()
		cur.execute(sql, (nsId,))
		data = cur.fetchall()
		cur.close()
		return data


	def getMosaicSql(self, compare, comparator, limit):
		return 'SELECT s.printablekey as "s_printablekey",s.publickey as "s_publickey",sink.printablekey as "sink_printablekey",sink.publickey as "sink_publickey",t.* FROM mosaics t,accounts s,accounts sink WHERE t.{}{}%s AND t.signer_id=s.id AND t.creation_sink=sink.id ORDER BY id DESC {}'.format(compare,comparator,limit)

	def getMatchingMosaics(self, ids, limit):
		cur = self.conn.cursor()
		cur.execute(self.getMosaicSql('id', ' IN ', 'LIMIT '+str(limit)), (tuple(list(ids)),))
		data = cur.fetchall()

		cur.execute('SELECT p.mosaic_id as "mosaic_id", p.name as "name",p.value as "value" FROM mosaic_properties p WHERE p.mosaic_id IN %s', (tuple(list(ids)),))
		_properties = cur.fetchall()
		properties = defaultdict(list)
		for e in _properties:
			properties[e['mosaic_id']].append(e)


		#cur.execute('SELECT * FROM mosaic_supplies s WHERE s.mosaic_id IN %s', (tuple(list(ids)),))
		#_supplies = cur.fetchall()

		sql = "SELECT * FROM (SELECT *,row_number() OVER (PARTITION BY mosaic_id ORDER BY block_height DESC) AS rn FROM mosaic_state_supply WHERE mosaic_id in %s) q where rn = 1"
		cur.execute(sql, (tuple(list(ids)),))
		_supplies = cur.fetchall()
	
		supplies = {}
		for e in _supplies:
			supplies[e['mosaic_id']] = e

		print properties
		print "--- --- --- --- ---"
		print supplies
		print "--- --- --- --- ---"

		for e in data:
			print e
			print "---"

			e['properties'] = properties[e['id']]
			e['properties_count'] = len(e['properties'])
			e['supply'] = supplies[e['id']]

		cur.close()
		return data
	
	def _getMosaic(self, cur, compare, dataCompare, level=1):
		cur.execute(self.getMosaicSql(compare, '=', 'LIMIT 1'), (dataCompare,))
		#print "SQL query: ", cur.mogrify(self.getMosaicSql(compare, '=', 'LIMIT 1'), (dataCompare,))
		data = cur.fetchone()
		#print "result", data

		cur.execute('SELECT p.name as "name",p.value as "value" FROM mosaic_properties p WHERE p.mosaic_id = %s', (data['id'],))
		data['properties'] = cur.fetchall()
		data['properties_count'] = len(data['properties'])

		#cur.execute('SELECT * FROM mosaic_supplies s WHERE s.mosaic_id = %s', (data['id'],))
		#data['supplies'] = cur.fetchall()
		#data['supplies_count'] = len(data['supplies'])

		_supplies = self._getPreviousStateSupply(cur, (data['id'], ))
		data['supply'] = _supplies

		cur.execute('SELECT r.printablekey as "r_printablekey",r.publickey as "r_publickey", m.block_height,m.type,m.fee_mosaic_id,m.fee,m.recipient_id FROM mosaic_levys m,accounts r WHERE mosaic_id = %s AND m.recipient_id=r.id ', (data['id'],))
		data['levy'] = cur.fetchone()

		if data['levy']:
			if level != 0:
				levyId = data['levy']['fee_mosaic_id']
				data['levy']['fee_mosaic'] = self._getMosaic(cur, 'id', levyId, level-1)
			else:
				data['levy']['fee_mosaic'] = {0:'unavailable'}

			del data['levy']['fee_mosaic_id']

		return data

	def getMosaic(self, compare, dataCompare):
		cur = self.conn.cursor()
		data = self._getMosaic(cur, compare, dataCompare)

		cur.execute('SELECT * FROM transfer_attachments t WHERE t.mosaic_id = %s ORDER BY id DESC LIMIT 10', (data['id'],))
		att = cur.fetchall()
		ids = set(map(lambda e: e['transfer_id'], att))
		txes = self.getMatchingTransfers(ids, 10) if len(ids) else []
		data['txes'] = txes
		cur.close()
		return data


	def getMosaics(self, txId, limit):
		cur = self.conn.cursor()
		cur.execute(self.getMosaicSql('id', '<', 'LIMIT ' + str(limit)), (txId,))
		data = cur.fetchall()
		cur.close()
		return data
	
	def getBlockMosaics(self, height):
		cur = self.conn.cursor()
		cur.execute(self.getMosaicSql('block_height', '=', ''), (height,))
		data = cur.fetchall()
		cur.close()
		return data


	def getMosaicsFrom(self, nsId):
		sql = 'SELECT s.printablekey as "s_printablekey",s.publickey as "s_publickey",sink.printablekey as "sink_printablekey",sink.publickey as "sink_publickey",t.* FROM mosaics t,accounts s,accounts sink WHERE t.parent_ns = %s AND t.signer_id=s.id AND t.creation_sink=sink.id ORDER BY t.mosaic_fqdn'
		cur = self.conn.cursor()
		cur.execute(sql, (nsId,))
		data = cur.fetchall()
		cur.close()
		return data

	def getTransactionByHash(self, name, txHash):
		switch = {
			'transfers': self.getTransfer
			, 'delegates': self.getDelegate
			, 'modifications': self.getModification
			, 'multisigs': self.getMultisig
			, 'namespaces': self.getNamespace
			, 'mosaics': self.getMosaic
		}
		return switch[name]('hash', tobin(txHash))
		
	def getTransactionsByType(self, name, txId, limit):
		switch = {
			'transfers': self.getTransfers
			, 'delegates': self.getDelegates
			, 'modifications': self.getModifications
			, 'multisigs': self.getMultisigs
			, 'namespaces': self.getNamespaces
			, 'mosaics': self.getMosaics
		}
		return switch[name](txId, limit)

	def getTableNext(self, table, txId, limit):
		cur = self.conn.cursor()
		cur.execute('SELECT id FROM '+table+' WHERE id > %s ORDER BY id ASC LIMIT '+str(limit), (txId,))
		data = cur.fetchall()
		cur.close()
		return data

	def getAccount(self, address):
		start = datetime.now()
		cur = self.conn.cursor()
		cur.execute('SELECT a.* FROM accounts a WHERE a.printablekey = %s', (address,))
		data = cur.fetchone()
		cur.close()
		print "time get", (datetime.now() - start).microseconds
		return data

	def getAccountById(self, accid):
		cur = self.conn.cursor()
		cur.execute('SELECT a.* FROM accounts a WHERE a.id = %s', (accid,))
		data = cur.fetchone()
		cur.close()
		return data

	def getAccountsByIds(self, ids):
		cur = self.conn.cursor()
		cur.execute('SELECT a.* FROM accounts a WHERE a.id in %s', (tuple(list(ids)),))
		data = cur.fetchall()
		cur.close()
		return data

	def getHarvestedBlocksCount(self, accid):
		start = datetime.now()
		cur = self.conn.cursor()
		cur.execute('SELECT COUNT(*) AS "harvested_count" FROM harvests WHERE account_id = %s', (accid,))
		data = cur.fetchone()['harvested_count']
		cur.close()
		print "time count", (datetime.now() - start).microseconds
		return data

	def getHarvestedBlocksReward(self, accid):
		start = datetime.now()
		cur = self.conn.cursor()
		cur.execute('SELECT type,cast(SUM(amount) as bigint) AS "sum" FROM inouts WHERE account_id=%s GROUP BY type', (accid,))
		data = cur.fetchall()
		cur.close()
		print "time reward", (datetime.now() - start).microseconds
		return data

	def getHarvestersByCount(self):
		cur = self.conn.cursor()
		cur.execute("""SELECT COUNT(h.block_height) AS harvestedblocks,MAX(h.block_height) AS lastBlock,h.account_id as id from harvests h GROUP BY h.account_id ORDER BY harvestedblocks DESC LIMIT 50""");
		#cur.execute("""SELECT COUNT(h.block_height) AS harvestedblocks,MAX(h.block_height) AS lastBlock,a.id from harvests h,accounts a where a.id=h.account_id GROUP BY a.id ORDER BY harvestedblocks DESC LIMIT 50""")
		data = cur.fetchall()
		cur.close()
		return data

	def getHarvestersFees(self, ids):
		start = datetime.now()
		cur = self.conn.cursor()
		cur.execute("""SELECT i.account_id, cast(CEIL(SUM(i.amount)/1000000) AS BIGINT) as fees FROM inouts i WHERE i.account_id IN %s AND i.type=3 GROUP BY i.account_id""", (tuple(list(ids)),));
		data = cur.fetchall()
		cur.close()
		print "time fees", (datetime.now() - start).microseconds
		return data
		

#	def getHarvesters(self):
#		cur = self.conn.cursor()
#		cur.execute("""
#SELECT COUNT(h.block_height) AS harvestedblocks,a.#ablekey,cast(CEIL(SUM(i.amount)/1000000) AS BIGINT) as fees FROM
#  harvests h
#INNER JOIN accounts a ON h.account_id=a.id
#LEFT JOIN inouts i ON i.type=3 AND h.block_height=i.block_height
#GROUP BY a.#ablekey ORDER BY harvestedblocks DESC LIMIT 50
#""");
#		data = cur.fetchall()
#		cur.close()
#		return data
	
	def addAccount(self, pub, printable):
		cur = self.conn.cursor()
		sql = "INSERT INTO accounts (printablekey, publickey) VALUES (%s,%s) RETURNING id";
		obj = (printable, bytearray(pub.decode('hex')))
		#print " [+] adding to db: ", pub, obj
		cur.execute(sql, obj)
		retId = cur.fetchone()[0]
		cur.close()
		return retId

	def updateAccount(self, retId, pub, printable):
		cur = self.conn.cursor()
		sql = "UPDATE accounts SET publickey=%s WHERE id=%s";
		obj = (bytearray(pub.decode('hex')), retId)
		print " [+] updating in db: ", pub, obj
		cur.execute(sql, obj)
		cur.close()
		return retId
	
	def addAccountPrint(self, printable):
		cur = self.conn.cursor()
		sql = "INSERT INTO accounts (printablekey) VALUES (%s) RETURNING id";
		# coma at end due to psycopg
		obj = (printable,)
		#print " [+] adding to db: ", obj
		cur.execute(sql, obj)
		retId = cur.fetchone()[0]
		cur.close()
		return retId