#if ETH_QTQML
#include <QtQml/QtQml>
#endif
#include <QtCore/QtCore>
#include <QtWebKitWidgets/QWebFrame>
#include <libethcore/FileSystem.h>
#include <libethcore/Dagger.h>
#include <libevmface/Instruction.h>
#include <liblll/Compiler.h>
#include <libethereum/Client.h>
#include <libethereum/PeerServer.h>
#include "QEthereum.h"
using namespace std;

// types
using eth::bytes;
using eth::bytesConstRef;
using eth::h160;
using eth::h256;
using eth::u160;
using eth::u256;
using eth::u256s;
using eth::Address;
using eth::BlockInfo;
using eth::Client;
using eth::Instruction;
using eth::KeyPair;
using eth::NodeMode;
using eth::PeerInfo;
using eth::RLP;
using eth::Secret;
using eth::Transaction;

// functions
using eth::toHex;
using eth::disassemble;
using eth::formatBalance;
using eth::fromHex;
using eth::right160;
using eth::simpleDebugOut;
using eth::toLog2;
using eth::toString;
using eth::units;

// vars
using eth::g_logPost;
using eth::g_logVerbosity;
using eth::c_instructionInfo;

// Horrible global for the mainwindow. Needed for the QmlEthereums to find the Main window which acts as multiplexer for now.
// Can get rid of this once we've sorted out ITC for signalling & multiplexed querying.
eth::Client* g_qmlClient;
QObject* g_qmlMain;

QmlAccount::QmlAccount(QObject*)
{
}

QmlAccount::~QmlAccount()
{
}

void QmlAccount::setEthereum(QmlEthereum* _eth)
{
	if (m_eth == _eth)
		return;
	if (m_eth)
		disconnect(m_eth, SIGNAL(changed()), this, SIGNAL(changed()));
	m_eth = _eth;
	if (m_eth)
		connect(m_eth, SIGNAL(changed()), this, SIGNAL(changed()));
	ethChanged();
	changed();
}

eth::u256 QmlAccount::balance() const
{
	if (m_eth)
		return m_eth->balanceAt(m_address);
	return u256(0);
}

double QmlAccount::txCount() const
{
	if (m_eth)
		return m_eth->txCountAt(m_address);
	return 0;
}

bool QmlAccount::isContract() const
{
	if (m_eth)
		return m_eth->isContractAt(m_address);
	return 0;
}

QmlEthereum::QmlEthereum(QObject* _p): QObject(_p)
{
	connect(g_qmlMain, SIGNAL(changed()), SIGNAL(changed()));
}

QmlEthereum::~QmlEthereum()
{
}

Client* QmlEthereum::client() const
{
	return g_qmlClient;
}

Address QmlEthereum::coinbase() const
{
	return client()->address();
}

void QmlEthereum::setCoinbase(Address _a)
{
	if (client()->address() != _a)
	{
		client()->setAddress(_a);
		changed();
	}
}

u256 QmlEthereum::balanceAt(Address _a) const
{
	return client()->postState().balance(_a);
}

bool QmlEthereum::isContractAt(Address _a) const
{
	return client()->postState().addressHasCode(_a);
}

bool QmlEthereum::isMining() const
{
	return client()->isMining();
}

bool QmlEthereum::isListening() const
{
	return client()->haveNetwork();
}

void QmlEthereum::setMining(bool _l)
{
	if (_l)
		client()->startMining();
	else
		client()->stopMining();
}

void QmlEthereum::setListening(bool _l)
{
	if (_l)
		client()->startNetwork();
	else
		client()->stopNetwork();
}

double QmlEthereum::txCountAt(Address _a) const
{
	return (double)client()->postState().transactionsFrom(_a);
}

unsigned QmlEthereum::peerCount() const
{
	return (unsigned)client()->peerCount();
}

void QmlEthereum::transact(Secret _secret, u256 _amount, u256 _gasPrice, u256 _gas, QByteArray _init)
{
	client()->transact(_secret, _amount, bytes(_init.data(), _init.data() + _init.size()), _gas, _gasPrice);
}

void QmlEthereum::transact(Secret _secret, Address _dest, u256 _amount, u256 _gasPrice, u256 _gas, QByteArray _data)
{
	client()->transact(_secret, _amount, _dest, bytes(_data.data(), _data.data() + _data.size()), _gas, _gasPrice);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

eth::bytes toBytes(QString const& _s)
{
	if (_s.startsWith("0x"))
		// Hex
		return eth::fromHex(_s.mid(2).toStdString());
	else if (!_s.contains(QRegExp("[^0-9]")))
		// Decimal
		return eth::toCompactBigEndian(eth::bigint(_s.toStdString()));
	else
		// Binary
		return asBytes(_s);
}

QString padded(QString const& _s, unsigned _l, unsigned _r)
{
	eth::bytes b = toBytes(_s);
	while (b.size() < _l)
		b.insert(b.begin(), 0);
	while (b.size() < _r)
		b.push_back(0);
	return asQString(eth::asBytes(eth::asString(b).substr(b.size() - max(_l, _r))));
}

//"0xff".bin().unbin()

QString QEthereum::secretToAddress(QString _s) const
{
	return toQJS(KeyPair(toSecret(_s)).address());
}

QString padded(QString const& _s, unsigned _l)
{
	if (_s.startsWith("0x") || !_s.contains(QRegExp("[^0-9]")))
		// Numeric: pad to right
		return padded(_s, _l, _l);
	else
		// Text: pad to the left
		return padded(_s, 0, _l);
}

QString unpadded(QString _s)
{
	while (_s.size() && _s.endsWith(QChar(0)))
		_s.chop(1);
	return _s;
}

QEthereum::QEthereum(QObject* _p, Client* _c, QList<eth::KeyPair> _accounts): QObject(_p), m_client(_c), m_accounts(_accounts)
{
	// required to prevent crash on osx when performing addto/evaluatejavascript calls
	this->moveToThread(_p->thread());
}

QEthereum::~QEthereum()
{
}

void QEthereum::setup(QWebFrame* _e)
{
	// Alex: JS codes moved to mainwin until qtwebkit bugs are resolved (#245)
}

void QEthereum::teardown(QWebFrame*)
{
}

Client* QEthereum::client() const
{
	return m_client;
}

QString QEthereum::lll(QString _s) const
{
	return asQString(eth::compileLLL(_s.toStdString()));
}

QString QEthereum::sha3(QString _s) const
{
	return toQJS(eth::sha3(asBytes(_s)));
}

QString QEthereum::coinbase() const
{
	return toQJS(client()->address());
}

QString QEthereum::number() const
{
	return QString::number(client()->blockChain().number() + 1);
}

QString QEthereum::account() const
{
	if (m_accounts.empty())
		return toQJS(Address());
	return toQJS(m_accounts[0].address());
}

QStringList QEthereum::accounts() const
{
	QStringList ret;
	for (auto i: m_accounts)
		ret.push_back(toQJS(i.address()));
	return ret;
}

QString QEthereum::key() const
{
	if (m_accounts.empty())
		return toQJS(KeyPair().sec());
	return toQJS(m_accounts[0].sec());
}

QStringList QEthereum::keys() const
{
	QStringList ret;
	for (auto i: m_accounts)
		ret.push_back(toQJS(i.sec()));
	return ret;
}

void QEthereum::setCoinbase(QString _a)
{
	if (client()->address() != toAddress(_a))
	{
		client()->setAddress(toAddress(_a));
		changed();
	}
}

QString QEthereum::balanceAt(QString _a) const
{
	return toQJS(client()->postState().balance(toAddress(_a)));
}

QString QEthereum::storageAt(QString _a, QString _p) const
{
	eth::ClientGuard l(const_cast<Client*>(m_client));
	return toQJS(client()->postState().storage(toAddress(_a), toU256(_p)));
}

double QEthereum::txCountAt(QString _a) const
{
	return (double)client()->postState().transactionsFrom(toAddress(_a));
}

bool QEthereum::isContractAt(QString _a) const
{
	return client()->postState().addressHasCode(toAddress(_a));
}

u256 QEthereum::balanceAt(Address _a) const
{
	return client()->postState().balance(_a);
}

double QEthereum::txCountAt(Address _a) const
{
	return (double)client()->postState().transactionsFrom(_a);
}

bool QEthereum::isContractAt(Address _a) const
{
	return client()->postState().addressHasCode(_a);
}

QString QEthereum::balanceAt(QString _a, int _block) const
{
	return toQJS(client()->balanceAt(toAddress(_a), _block));
}

QString QEthereum::stateAt(QString _a, QString _p, int _block) const
{
	return toQJS(client()->stateAt(toAddress(_a), toU256(_p), _block));
}

QString QEthereum::codeAt(QString _a, int _block) const
{
	return ::fromBinary(client()->codeAt(toAddress(_a), _block));
}

double QEthereum::countAt(QString _a, int _block) const
{
	return (double)(uint64_t)client()->countAt(toAddress(_a), _block);
}

QString QEthereum::getTransactions(QString _a) const
{
	eth::TransactionFilter filter;

	QJsonObject f = QJsonDocument::fromJson(_a.toUtf8()).object();
	if (f.contains("earliest"))
		filter.withEarliest(f["earliest"].toInt());
	if (f.contains("latest"))
		filter.withLatest(f["latest"].toInt());
	if (f.contains("max"))
		filter.withMax(f["max"].toInt());
	if (f.contains("skip"))
		filter.withSkip(f["skip"].toInt());
	if (f.contains("from"))
	{
		if (f["from"].isArray())
			for (auto i: f["from"].toArray())
				filter.from(toAddress(i.toString()));
		else
			filter.from(toAddress(f["from"].toString()));
	}
	if (f.contains("to"))
	{
		if (f["to"].isArray())
			for (auto i: f["to"].toArray())
				filter.to(toAddress(i.toString()));
		else
			filter.to(toAddress(f["to"].toString()));
	}
	if (f.contains("altered"))
	{
		if (f["altered"].isArray())
			for (auto i: f["altered"].toArray())
				if (i.isObject())
					filter.altered(toAddress(i.toObject()["id"].toString()), toU256(i.toObject()["at"].toString()));
				else
					filter.altered(toAddress(i.toString()));
		else
			if (f["altered"].isObject())
				filter.altered(toAddress(f["altered"].toObject()["id"].toString()), toU256(f["altered"].toObject()["at"].toString()));
			else
				filter.altered(toAddress(f["altered"].toString()));
	}

	QJsonArray ret;
	for (eth::PastTransaction const& t: m_client->transactions(filter))
	{
		QJsonObject v;
		v["data"] = ::fromBinary(t.data);
		v["gas"] = toQJS(t.gas);
		v["gasPrice"] = toQJS(t.gasPrice);
		v["nonce"] = (int)t.nonce;
		v["to"] = toQJS(t.receiveAddress);
		v["value"] = toQJS(t.value);
		v["from"] = toQJS(t.sender());
		v["timestamp"] = (int)t.timestamp;
		v["block"] = toQJS(t.block);
		v["index"] = (int)t.index;
		v["age"] = (int)t.age;
		ret.append(v);
	}
	return QString::fromUtf8(QJsonDocument(ret).toJson());
}

bool QEthereum::isMining() const
{
	return client()->isMining();
}

bool QEthereum::isListening() const
{
	return client()->haveNetwork();
}

void QEthereum::setMining(bool _l)
{
	if (_l)
		client()->startMining();
	else
		client()->stopMining();
}

void QEthereum::setListening(bool _l)
{
	if (_l)
		client()->startNetwork();
	else
		client()->stopNetwork();
}

unsigned QEthereum::peerCount() const
{
	return (unsigned)client()->peerCount();
}

QString QEthereum::doCreate(QString _secret, QString _amount, QString _init, QString _gas, QString _gasPrice)
{
	client()->changed();
	auto ret = toQJS(client()->transact(toSecret(_secret), toU256(_amount), toBytes(_init), toU256(_gas), toU256(_gasPrice)));
	while (!client()->peekChanged())
		this_thread::sleep_for(chrono::milliseconds(10));
	return ret;
}

void QEthereum::doTransact(QString _secret, QString _amount, QString _dest, QString _data, QString _gas, QString _gasPrice)
{
	client()->changed();
	client()->transact(toSecret(_secret), toU256(_amount), toAddress(_dest), toBytes(_data), toU256(_gas), toU256(_gasPrice));
	while (!client()->peekChanged())
		this_thread::sleep_for(chrono::milliseconds(10));
}

// extra bits needed to link on VS
#ifdef _MSC_VER

// include moc file, ofuscated to hide from automoc
#include\
"moc_QEthereum.cpp"

#endif