// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.

// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity.  If not, see <http://www.gnu.org/licenses/>.

import { getBalances, getTokens } from './balancesActions';
import { setAddressImage } from './imagesActions';

import * as abis from '../../contracts/abi';

import imagesEthereum from '../../../assets/images/contracts/ethereum-black-64x64.png';

const ETH = {
  name: 'Ethereum',
  tag: 'ETH',
  image: imagesEthereum
};

export default class Balances {
  constructor (store, api) {
    this._api = api;
    this._store = store;
    this._accountsInfo = null;
    this._tokens = [];
  }

  start () {
    this._subscribeBlockNumber();
    this._subscribeAccountsInfo();
  }

  _subscribeAccountsInfo () {
    this._api
      .subscribe('parity_accountsInfo', (error, accountsInfo) => {
        if (error) {
          return;
        }

        this._accountsInfo = accountsInfo;
        this._retrieveBalances();
      })
      .then((subscriptionId) => {
        console.log('_subscribeAccountsInfo', 'subscriptionId', subscriptionId);
      })
      .catch((error) => {
        console.warn('_subscribeAccountsInfo', error);
      });
  }

  _subscribeBlockNumber () {
    this._api
      .subscribe('eth_blockNumber', (error) => {
        if (error) {
          return;
        }

        this._retrieveTokens();
      })
      .then((subscriptionId) => {
        console.log('_subscribeBlockNumber', 'subscriptionId', subscriptionId);
      })
      .catch((error) => {
        console.warn('_subscribeBlockNumber', error);
      });
  }

  _retrieveTokens () {
    this._api.parity
      .registryAddress()
      .then((registryAddress) => {
        const registry = this._api.newContract(abis.registry, registryAddress);

        return registry.instance.getAddress.call({}, [this._api.util.sha3('tokenreg'), 'A']);
      })
      .then((tokenregAddress) => {
        const tokenreg = this._api.newContract(abis.tokenreg, tokenregAddress);

        return tokenreg.instance.tokenCount
          .call()
          .then((numTokens) => {
            const promisesTokens = [];
            const promisesImages = [];

            while (promisesTokens.length < numTokens.toNumber()) {
              const index = promisesTokens.length;

              promisesTokens.push(tokenreg.instance.token.call({}, [index]));
              promisesImages.push(tokenreg.instance.meta.call({}, [index, 'IMG']));
            }

            return Promise.all([
              Promise.all(promisesTokens),
              Promise.all(promisesImages)
            ]);
          });
      })
      .then(([_tokens, images]) => {
        const tokens = {};
        this._tokens = _tokens
          .map((_token, index) => {
            const [address, tag, format, name] = _token;

            const token = {
              address,
              name,
              tag,
              format: format.toString(),
              contract: this._api.newContract(abis.eip20, address)
            };
            tokens[address] = token;
            this._store.dispatch(setAddressImage(address, images[index]));

            return token;
          })
          .sort((a, b) => {
            if (a.tag < b.tag) {
              return -1;
            } else if (a.tag > b.tag) {
              return 1;
            }

            return 0;
          });

        this._store.dispatch(getTokens(tokens));
        this._retrieveBalances();
      })
      .catch((error) => {
        console.warn('_retrieveTokens', error);
        this._retrieveBalances();
      });
  }

  _retrieveBalances () {
    if (!this._accountsInfo) {
      return;
    }

    const addresses = Object.keys(this._accountsInfo);
    this._balances = {};

    Promise
      .all(
        addresses.map((address) => Promise.all([
          this._api.eth.getBalance(address),
          this._api.eth.getTransactionCount(address)
        ]))
      )
      .then((balanceTxCount) => {
        return Promise.all(
          balanceTxCount.map(([value, txCount], idx) => {
            const address = addresses[idx];

            this._balances[address] = {
              txCount,
              tokens: [{
                token: ETH,
                value
              }]
            };

            return Promise.all(
              this._tokens.map((token) => {
                return token.contract.instance.balanceOf.call({}, [address]);
              })
            );
          })
        );
      })
      .then((tokenBalances) => {
        addresses.forEach((address, idx) => {
          const balanceOf = tokenBalances[idx];
          const balance = this._balances[address];

          this._tokens.forEach((token, tidx) => {
            balance.tokens.push({
              token,
              value: balanceOf[tidx]
            });
          });
        });

        this._store.dispatch(getBalances(this._balances));
      })
      .catch((error) => {
        console.warn('_retrieveBalances', error);
      });
  }
}
