contract TokenPorter is ITokenPorter, Owned {
    using SafeMath for uint;
    Auctions public auctions;
    METToken public token;
    Validator public validator;
    ChainLedger public chainLedger;

    uint public burnSequence = 1;
    uint public importSequence = 1;
    bytes32[] public exportedBurns;
    uint[] public supplyOnAllChains = new uint[](6);

    /// @notice mapping that tracks valid destination chains for export
    mapping(bytes8 => address) public destinationChains;

    /// @notice Initialize TokenPorter contract.
    /// @param _tokenAddr Address of metToken contract
    /// @param _auctionsAddr Address of auctions contract
    function initTokenPorter(address _tokenAddr, address _auctionsAddr) public onlyOwner {
        require(_tokenAddr != 0x0);
        require(_auctionsAddr != 0x0);
        auctions = Auctions(_auctionsAddr);
        token = METToken(_tokenAddr);
    }

    /// @notice set address of validator contract
    /// @param _validator address of validator contract
    function setValidator(address _validator) public onlyOwner returns (bool) {
        require(_validator != 0x0);
        validator = Validator(_validator);
        return true;
    }

    /// @notice set address of chainLedger contract
    /// @param _chainLedger address of chainLedger contract
    function setChainLedger(address _chainLedger) public onlyOwner returns (bool) {
        require(_chainLedger != 0x0);
        chainLedger = ChainLedger(_chainLedger);
        return true;
    }

    /// @notice only owner can add destination chains
    /// @param _chainName string of destination blockchain name
    /// @param _contractAddress address of destination MET token to import to
    function addDestinationChain(bytes8 _chainName, address _contractAddress) 
        public onlyOwner returns (bool) 
    {
        require(_chainName != 0 && _contractAddress != address(0));
        destinationChains[_chainName] = _contractAddress;
        return true;
    }

    /// @notice only owner can remove destination chains
    /// @param _chainName string of destination blockchain name
    function removeDestinationChain(bytes8 _chainName) public onlyOwner returns (bool) {
        require(_chainName != 0);
        require(destinationChains[_chainName] != address(0));
        destinationChains[_chainName] = address(0);
        return true;   
    }

    /// @notice holds claims from users that have exported on-chain
    /// @param key is address of destination MET token contract
    /// @param subKey is address of users account that burned their original MET token
    mapping (address  => mapping(address => uint)) public claimables;

    /// @notice destination MET token contract calls claimReceivables to record burned 
    /// tokens have been minted in new chain
    /// @param recipients array of addresses of each user that has exported from
    /// original chain.  These can be generated by ExportReceiptLog
    function claimReceivables(address[] recipients) public returns (uint) {
        require(recipients.length > 0);

        uint total;
        for (uint i = 0; i < recipients.length; i++) {
            address recipient = recipients[i];
            uint amountBurned = claimables[msg.sender][recipient];
            if (amountBurned > 0) {
                claimables[msg.sender][recipient] = 0;
                emit ExportOnChainClaimedReceiptLog(msg.sender, recipient, amountBurned);
                total = total.add(1);
            }
        }
        return total;
    }

    /// @notice import MET tokens from another chain to this chain.
    /// @param _destinationChain destination chain name
    /// @param _addresses _addresses[0] is destMetronomeAddr and _addresses[1] is recipientAddr
    /// @param _extraData extra information for import
    /// @param _burnHashes _burnHashes[0] is previous burnHash, _burnHashes[1] is current burnHash
    /// @param _supplyOnAllChains MET supply on all supported chains
    /// @param _importData _importData[0] is _blockTimestamp, _importData[1] is _amount, _importData[2] is _fee
    /// _importData[3] is _burnedAtTick, _importData[4] is _genesisTime, _importData[5] is _dailyMintable
    /// _importData[6] is _burnSequence, _importData[7] is _dailyAuctionStartTime
    /// @param _proof proof
    /// @return true/false
    function importMET(bytes8 _originChain, bytes8 _destinationChain, address[] _addresses, bytes _extraData, 
        bytes32[] _burnHashes, uint[] _supplyOnAllChains, uint[] _importData, bytes _proof) public returns (bool)
    {
        
        require(msg.sender == address(token));
        require(_importData.length == 8);
        require(_addresses.length == 2);
        require(_burnHashes.length == 2);
        require(validator.isReceiptClaimable(_originChain, _destinationChain, _addresses, _extraData, _burnHashes, 
        _supplyOnAllChains, _importData, _proof));

        validator.claimHash(_burnHashes[1]);

        require(_destinationChain == auctions.chain());
        uint amountToImport = _importData[1].add(_importData[2]);
        require(amountToImport.add(token.totalSupply()) <= auctions.globalMetSupply());

        require(_addresses[0] == address(token));

        if (_importData[1] == 0) {
            return false;
        }

        if (importSequence == 1 && token.totalSupply() == 0) {
            auctions.prepareAuctionForNonOGChain();
        }
        
        token.mint(_addresses[1], _importData[1]);
        emit ImportReceiptLog(_addresses[1], _importData[1], _importData[2], _extraData,
        auctions.currentTick(), importSequence, _burnHashes[1],
        _burnHashes[0], auctions.dailyMintable(), now, msg.sender);
        importSequence++;
        chainLedger.registerImport(_originChain, _destinationChain, _importData[1]);
        return true;
    }

    /// @notice Export MET tokens from this chain to another chain.
    /// @param tokenOwner Owner of the token, whose tokens are being exported.
    /// @param _destChain Destination chain for exported tokens
    /// @param _destMetronomeAddr Metronome address on destination chain
    /// @param _destRecipAddr Recipient address on the destination chain
    /// @param _amount Amount of token being exported
    /// @param _extraData Extra data for this export
    /// @return boolean true/false based on the outcome of export
    function export(address tokenOwner, bytes8 _destChain, address _destMetronomeAddr,
        address _destRecipAddr, uint _amount, uint _fee, bytes _extraData) public returns (bool) 
    {
        require(msg.sender == address(token));

        require(_destChain != 0x0 && _destMetronomeAddr != 0x0 && _destRecipAddr != 0x0 && _amount != 0);
        require(destinationChains[_destChain] == _destMetronomeAddr);
        
        require(token.balanceOf(tokenOwner) >= _amount.add(_fee));

        token.destroy(tokenOwner, _amount.add(_fee));

        uint dailyMintable = auctions.dailyMintable();
        uint currentTick = auctions.currentTick();
       
       
        if (burnSequence == 1) {
            exportedBurns.push(keccak256(uint8(0)));
        }

        if (_destChain == auctions.chain()) {
            claimables[_destMetronomeAddr][_destRecipAddr] = 
                claimables[_destMetronomeAddr][_destRecipAddr].add(_amount);
        }
        uint blockTime = block.timestamp;
        bytes32 currentBurn = keccak256(
            blockTime, 
            auctions.chain(),
            _destChain, 
            _destMetronomeAddr, 
            _destRecipAddr, 
            _amount,
            currentTick,
            auctions.genesisTime(),
            dailyMintable,
            token.totalSupply(),
            _extraData,
            exportedBurns[burnSequence - 1]);
       
        exportedBurns.push(currentBurn);

        supplyOnAllChains[0] = token.totalSupply();
        
        emit ExportReceiptLog(_destChain, _destMetronomeAddr, _destRecipAddr, _amount, _fee, _extraData, 
            currentTick, burnSequence, currentBurn, exportedBurns[burnSequence - 1], dailyMintable,
            supplyOnAllChains, auctions.genesisTime(), blockTime, auctions.dailyAuctionStartTime());

        burnSequence = burnSequence + 1;
        chainLedger.registerExport(auctions.chain(), _destChain, _amount);
        return true;
    }
}
/// @title This contract will provide export functionality for tokens.
function addDestinationChain(bytes8 _chainName, address _contractAddress) 
    public onlyOwner returns (bool) 
{
    require(_chainName != 0 && _contractAddress != address(0));
    destinationChains[_chainName] = _contractAddress;
    return true;
}

/// @notice only owner can add destination chains
/// @param _chainName string of destination blockchain name
/// @param _contractAddress address of destination MET token to import to