contract PAXTRExchange is Owned, usingOraclize {
    using SafeMath for uint256;
    
    constructor() public payable {
        owner = msg.sender;
        oraclePriceURL = "json(https://spreadsheets.google.com/feeds/list/1pL8-QrNJrN1OFUmFNqt0IMDc6uepEuYLLAS-d8LxprI/od6/public/values?alt=json).feed.entry[4].['gsx$_ciyn3'].['$t']";
        oraclize_setCustomGasPrice(5000000000);
        bytes32 queryId = oraclize_query("URL", oraclePriceURL);
        queryIsValid[queryId] = true;
        lastPriceUpdate = block.number;
    }
    
    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 = 0x6d86091B051799e05CC8D7b2452A7Cb123F018D8;
    uint256 public tokenPrice;
    uint256 constant private tokenDecimals = 8;
    mapping(bytes32 => bool) private queryIsValid;
    string public oraclePriceURL;
    uint256 private lastPriceUpdate = 0;
    
    // 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 depositPAXTR(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("PAXTR", 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 withdrawPAXTR(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("PAXTR", 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 PAXTR
        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 PAXTR
        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
        account[tokenOrders[usedTokenOrders]].etherBalance += (amount - oraclize_getPrice("URL"));
        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
        
        account[etherOrders[usedEtherOrders]].etherOrder -= ((amount * tokenPrice) / (10 ** tokenDecimals)); // Removes ether
        account[etherOrders[usedEtherOrders]].etherBalance -= ((amount * tokenPrice) / (10 ** tokenDecimals)); // Removes ether
        account[msg.sender].etherBalance += (((amount * tokenPrice) / (10 ** tokenDecimals)) - oraclize_getPrice("URL"));
        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 withdrawFees(uint256 amount) public onlyOwner {
        msg.sender.transfer(amount);
    }
    
    
    // Oracalize
    function setOraclizeGasPrice(uint256 newPriceInWEI) public onlyOwner {
        oraclize_setCustomGasPrice(newPriceInWEI);
    }
    function updatePrice() internal {
        if (block.number >= (lastPriceUpdate + 40)) {
            bytes32 queryId = oraclize_query("URL", oraclePriceURL);
            queryIsValid[queryId] = true;
            lastPriceUpdate = block.number;
        }
    }
    function __callback(bytes32 queryID, string memory result) public {
        require(msg.sender == oraclize_cbAddress());
        require(queryIsValid[queryID] == true);
        tokenPrice = safeParseInt(result);
    }
    function setOraclePriceURL(string memory newURL) public onlyOwner {
        oraclePriceURL = newURL;
    }
    function manualUpdatePrice() public payable {
        require(msg.value >= oraclize_getPrice("URL"));
        updatePrice();
    }
}
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);
        }
    }
// Send Market Orders