contract iTradeExchange is Owned, usingProvable {
    using SafeMath for uint256;

    constructor() public payable {
        owner = msg.sender;
        lastPriceUpdate = 0;
        updatePrice();
    }
    function () external payable {
        revert();
    }

    function changeTokenAddress(address newTokenAddress) public onlyOwner {
        tokenAddress = newTokenAddress;
    }

    // Events
    event Deposit(string _type, uint256 amount, address to);
    event Withdraw(string _type, uint256 amount, address from);

    // Global Variables
    address public tokenAddress = 0xF5A68B17e69FF202b604Ac6Bd9b1482554592671;
    uint256 public tokenPrice;
    uint256 constant private tokenDecimals = 5;

    uint256 public fee = 100; // 10000 = 100%
    uint256 public oracleFee = 150000000000000;
    uint256 public outstandingFees = 0;
    // Administration
    function collectFees() public onlyOwner {
        msg.sender.transfer(outstandingFees);
        outstandingFees = 0;
    }
    function setFee(uint256 _fee) public onlyOwner {
        fee = _fee;
    }
    function setOracleFee(uint256 _wei) public onlyOwner {
        oracleFee = _wei;
    }

    // Order Variables
    struct accountStuct {
        uint256 tokenBalance;
        uint256 etherBalance;
        uint256 tokenOrder;
        uint256 tokenOrderIndex;
        uint256 etherOrder;
        uint256 etherOrderIndex;
    }
    mapping(address => accountStuct) public account; // Account and order data
    address[] public etherOrders; // Key for addresses with active ether orders
    uint256 public usedEtherOrders;
    address[] public tokenOrders; // Key for addresses with active token orders
    uint256 public usedTokenOrders;

    // Get Array Lengths
    function getEtherOrdersLength() public view returns(uint256) {
        return etherOrders.length;
    }
    function getTokenOrdersLength() public view returns(uint256) {
        return tokenOrders.length;
    }

    // Deposit Functions
    function depositETH() public payable {
        account[msg.sender].etherBalance = account[msg.sender].etherBalance.add(msg.value);
        emit Deposit("Ether", msg.value, msg.sender);
    }
    function depositITR(uint256 amount) public {
        require(IERC20(tokenAddress).allowance(msg.sender, address(this)) >= amount);
        IERC20(tokenAddress).transferFrom(msg.sender, address(this), amount);
        account[msg.sender].tokenBalance = account[msg.sender].tokenBalance.add(amount);
        emit Deposit("iTR", amount, msg.sender);
    }

    // Withdrawal Functions
    function withdrawETH(uint256 amount) public {
        require((account[msg.sender].etherBalance - account[msg.sender].etherOrder) >= amount);
        account[msg.sender].etherBalance = account[msg.sender].etherBalance.sub(amount);
        msg.sender.transfer(amount);
        emit Withdraw("Ether", amount, msg.sender);
    }
    function withdrawITR(uint256 amount) public {
        require((account[msg.sender].tokenBalance - account[msg.sender].tokenOrder) >= amount);
        account[msg.sender].tokenBalance = account[msg.sender].tokenBalance.sub(amount);
        IERC20(tokenAddress).transfer(msg.sender, amount);
        emit Withdraw("iTR", amount, msg.sender);
    }

    // Place Limit
    function limitBuy(uint256 amount) public {  // Place buy order with Ether
        require(account[msg.sender].etherBalance >= amount);
        require(account[msg.sender].etherOrder == 0);
        account[msg.sender].etherOrder = amount;
        etherOrders.push(msg.sender);
        account[msg.sender].etherOrderIndex = etherOrders.length - 1;
    }
    function limitSell(uint256 amount) public { // Place a sell order with ITR
        require(account[msg.sender].tokenBalance >= amount);
        require(account[msg.sender].tokenOrder == 0);
        account[msg.sender].tokenOrder = amount;
        tokenOrders.push(msg.sender);
        account[msg.sender].tokenOrderIndex = tokenOrders.length - 1;
    }

    // Cancel limits
    function cancelBuy() public {   // Cancels the buy order using Ether
        require(account[msg.sender].etherOrder > 0);
        for (uint256 i = account[msg.sender].etherOrderIndex; i < (etherOrders.length - 1); i++) {
            etherOrders[i] = etherOrders[i+1];
        }
        etherOrders.length -= 1;
        account[msg.sender].etherOrder = 0;
    }
    function cancelSell() public {  // Cancels the sell order of ITR
        require(account[msg.sender].tokenOrder > 0);
        for (uint256 i = account[msg.sender].tokenOrderIndex; i < (tokenOrders.length - 1); i++) {
            tokenOrders[i] = tokenOrders[i+1];
        }
        tokenOrders.length -= 1;
        account[msg.sender].tokenOrder = 0;
    }

    // Send Market Orders
    function sendMarketSells(uint256[] memory amounts, uint256 limit) public {
        uint256 amount = amounts.length;
        for (uint i = 0; i < amount; i++) {
            marketSell(amounts[i]);
        }
        if (limit > 0) {
            limitSell(limit);
        }
    }
    function sendMarketBuys(uint256[] memory amounts, uint256 limit) public {
        uint256 amount = amounts.length;
        for (uint i = 0; i < amount; i++) {
            marketBuy(amounts[i]);
        }
        if (limit > 0) {
            limitBuy(limit);
        }
    }

    // Call each
    function marketBuy(uint256 amount) public {
        require(account[tokenOrders[usedTokenOrders]].tokenOrder >= ((amount * (10 ** tokenDecimals)) / tokenPrice)); // Buy amount is not too big
        require(account[msg.sender].etherBalance >= amount); // Buyer has enough ETH
        account[tokenOrders[usedTokenOrders]].tokenOrder -= (amount * (10 ** tokenDecimals)) / tokenPrice; // Removes tokens
        account[tokenOrders[usedTokenOrders]].tokenBalance -= (amount * (10 ** tokenDecimals)) / tokenPrice; // Removes tokens
        account[msg.sender].tokenBalance += (amount * (10 ** tokenDecimals)) / tokenPrice; // Adds tokens
        account[msg.sender].etherBalance -= amount; // Removes ether
        uint256 _fee = (amount.mul(fee)).div(10000);
        outstandingFees = outstandingFees.add(_fee);
        account[tokenOrders[usedTokenOrders]].etherBalance += (amount.sub(_fee + oracleFee));
        if (((account[tokenOrders[usedTokenOrders]].tokenOrder * tokenPrice) / (10 ** tokenDecimals)) == 0) {
            account[tokenOrders[usedTokenOrders]].tokenBalance -= account[tokenOrders[usedTokenOrders]].tokenOrder;
            account[tokenOrders[usedTokenOrders]].tokenOrder = 0;
        }
        if (account[tokenOrders[usedTokenOrders]].tokenOrder == 0) {
            usedTokenOrders += 1;
        }
        updatePrice();
    }
    function marketSell(uint256 amount) public {
        require(account[etherOrders[usedEtherOrders]].etherOrder >= ((amount * tokenPrice) / (10 ** tokenDecimals))); // Sell amount is not too big
        require(account[msg.sender].tokenBalance >= amount); // Seller has enough tokens
        account[msg.sender].tokenBalance -= amount; // Removes tokens
        account[etherOrders[usedEtherOrders]].tokenBalance += amount; // Add tokens

        uint256 _etherAmount = ((amount * tokenPrice) / (10 ** tokenDecimals));

        account[etherOrders[usedEtherOrders]].etherOrder -= _etherAmount; // Removes ether
        account[etherOrders[usedEtherOrders]].etherBalance -= _etherAmount; // Removes ether
        uint256 _fee = (_etherAmount.mul(fee)).div(10000);
        outstandingFees = outstandingFees.add(_fee);
        account[msg.sender].etherBalance += _etherAmount.sub(_fee + oracleFee);
        if (((account[etherOrders[usedEtherOrders]].etherOrder * (10 ** tokenDecimals)) / tokenPrice) == 0) {
            account[etherOrders[usedEtherOrders]].etherBalance -= account[etherOrders[usedEtherOrders]].etherOrder;
            account[etherOrders[usedEtherOrders]].etherOrder = 0;
        }
        if (account[etherOrders[usedEtherOrders]].etherOrder == 0) {
            usedEtherOrders += 1;
        }
        updatePrice();
    }

    // Administration
    function donateEther() public payable {}

    function emergencyWithdraw(uint256 amount) public onlyOwner {
        msg.sender.transfer(amount);
    }

    function updatePrice() internal {
        if (lastPriceUpdate.add(43200) < now) {
            provable_query(300, "URL", "json(https://www.quandl.com/api/v3/datasets/LBMA/SILVER/data.json?limit=1&api_key=kqH6G9mSEqPLXq_8B9Mb).dataset_data.data[0][1]");
            bytes32 ethPrice = provable_query(10, "URL", "json(https://api.pro.coinbase.com/products/ETH-USD/ticker).price");
            oracleData[ethPrice] = true;
        }
    }

    uint256 public lastPriceUpdate;
    mapping(bytes32 => bool) oracleData;
    uint256 public latestEthPrice;
    function __callback(bytes32 myid, string memory result) public {
        if (msg.sender != provable_cbAddress()) revert('Not Called By Oracle');
        lastPriceUpdate = now;
        if (oracleData[myid] == true) {
            latestEthPrice = safeParseInt(result, 2);
        } else {
            uint256 silverusd = safeParseInt(result, 2).mul(1000000000000000000);
            tokenPrice = (silverusd.div(latestEthPrice));
        }
    }
}
function depositETH() public payable {
    account[msg.sender].etherBalance = account[msg.sender].etherBalance.add(msg.value);
    emit Deposit("Ether", msg.value, msg.sender);
}

// Deposit Functions