pragma solidity ^0.4.25;

contract SafeMath {
  function Sub(uint128 a, uint128 b) pure public returns (uint128) {
    assert(b <= a);
    return a - b;
  }

  function Add(uint128 a, uint128 b) pure public returns (uint128) {
    uint128 c = a + b;
    assert(c>=a && c>=b);
    return c;
  }
}

contract Token { 
  function totalSupply() public view returns (uint256 supply);
  function balanceOf(address _owner) public view returns (uint256 balance);
  function transfer(address _to, uint256 _value) public returns (bool success);
  function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
  function approve(address _spender, uint256 _value) public returns (bool success);
  function allowance(address _owner, address _spender) public view returns (uint256 remaining);
  event Transfer(address indexed _from, address indexed _to, uint256 _value);
  event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}

contract DEX is SafeMath
{
    uint32 public lastTransferId = 1;
    event NewDeposit(uint32 indexed prCode, uint32 indexed accountId, uint128 amount, uint64 timestamp, uint32 lastTransferId);
    event NewWithdraw(uint32 indexed prCode, uint32 indexed accountId, uint128 amount, uint64 timestamp, uint32 lastTransferId);
    uint32 public lastNewOrderId = 1;
    event NewOrder(uint32 indexed prTrade, uint32 indexed prBase, uint32 indexed accountId, uint32 id, bool isSell, uint80 price, uint104 qty, uint32 lastNewOrderId);
    event NewCancel(uint32 indexed prTrade, uint32 indexed prBase, uint32 indexed accountId, uint32 id, bool isSell, uint80 price, uint104 qt, uint32 lastNewOrderId);
    event NewBestBidAsk(uint32 indexed prTrade, uint32 indexed prBase, bool isBid, uint80 price);
    uint32 public lastTradeId = 1;
    event NewTrade(uint32 indexed prTrade, uint32 prBase, uint32 indexed bidId, uint32 indexed askId, uint32 accountIdBid, uint32 accountIdAsk, bool isSell, uint80 price, uint104 qty, uint32 lastTradeId, uint64 timestamp);
    
    uint256 public constant basePrice = 10000000000;
    uint80 public constant maxPrice = 10000000000000000000001;
    uint104 public constant maxQty = 1000000000000000000000000000001;
    uint128 public constant maxBalance = 1000000000000000000000000000000000001;
    bool public isContractUse;
    
    constructor() public
    {
        owner = msg.sender;
        operator = owner;
        AddOwner();
        AddProduct(18, 0x0);
        //lastProductId = 1; // productId == 1 -> ETH 0x0
        isContractUse = true;
    }
    
    address public owner;
    // Functions with this modifier can only be executed by the owner
    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }
    
    address public operator;
    modifier onlyOperator() {
        require(msg.sender == operator);
        _;
    }
    function transferOperator(address _operator) onlyOwner public {
        operator = _operator;
    }
    
    modifier onlyExOwner()  {
        require(owner_id[msg.sender] != 0);
        _;
    }
    
    modifier onlyContractUse {
        require(isContractUse == true);
        _;
    }
    function SetIsContractUse(bool _isContractUse) onlyOperator public 
    {
        isContractUse = _isContractUse;
    }
    
    uint32 public lastOwnerId;
    uint256 public newOwnerFee;
    mapping (uint32 => address) id_owner;
    mapping (address => uint32) owner_id;
    mapping (uint32 => uint8) ownerId_takerFeeRateLocal;
    mapping (uint32 => uint32) ownerId_accountId;
    
    function DeleteOwner(uint32 orderId) onlyOperator public 
    {
        require(lastOwnerId >= orderId && orderId > 0);
        owner_id[id_owner[orderId]] = 0;
    }
    
    function AddOwner() public payable 
    {
        require(msg.value >= newOwnerFee);
        require(owner_id[msg.sender] == 0);
        
        owner_id[msg.sender] = ++lastOwnerId;
        id_owner[lastOwnerId] = msg.sender;
        
        ownerId_accountId[lastOwnerId] = FindOrAddAccount();
        prCode_AccountId_Balance[1][ownerId_accountId[1]].available += uint128(msg.value);
        //overflow safe: eth balance & trasnfer << 2^128
    }
    function SetOwnerFee(uint256 ownerFee) onlyOperator public
    {
        newOwnerFee = ownerFee;
    }
    function GetOwnerList() view public returns (address[] owners, uint32[] ownerIds)
    {
        owners = new address[](lastOwnerId);
        ownerIds = new uint32[](lastOwnerId);
        
        for (uint32 i = 1; i <= lastOwnerId; i++)
        {
            owners[i - 1] = id_owner[i];
            ownerIds[i - 1] = i;
        }
    }
    function setTakerFeeRateLocal(uint8 _takerFeeRate) public
    {
        require (_takerFeeRate <= 100);
        uint32 ownerId = owner_id[msg.sender];
        require(ownerId != 0);
        ownerId_takerFeeRateLocal[ownerId] = _takerFeeRate;//bp
    }
    function getTakerFeeRateLocal(uint32 ownerId) public view returns (uint8)/////
    {
        return ownerId_takerFeeRateLocal[ownerId];//bp
    }
    
    function airDrop(uint32 prCode, uint32[] accountIds, uint104[] qtys) public
    {
        require(owner_id[msg.sender] != 0);
        //uint32 ownerId = owner_id[msg.sender];
        uint32 accountId = FindOrRevertAccount();// ownerId_accountId[owner_id[msg.sender]];
        require(accountId_freeze[accountId] == false);
        uint256 n = accountIds.length;
        require(n == qtys.length);// && n <= 1000000);
        
        uint128 sum = 0;
        for (uint32 i = 0; i < n; i++)
        {
            sum += qtys[i];
        }
        
        prCode_AccountId_Balance[prCode][accountId].available = Sub(prCode_AccountId_Balance[prCode][accountId].available, sum); 
        
        for (i = 0; i < n; i++)
        {
            prCode_AccountId_Balance[prCode][accountIds[i]].available += qtys[i];
        }
    }
    
    struct ProductInfo
    {
        uint256 divider;
        bool isTradeBid;
        bool isTradeAsk;
        bool isDeposit;
        bool isWithdraw;
        uint32 ownerId;
        uint104 minQty;
    }
    
    uint32 public lastProductId;
    uint256 public newProductFee;
    mapping (uint32 => address) prCode_product;
    mapping (address => uint32) product_prCode;
    mapping (uint32 => ProductInfo) prCode_productInfo;
    function AddProduct(uint256 decimals, address product) payable onlyExOwner public
    {
        require(msg.value >= newProductFee);
        require(product_prCode[product] == 0);
        require(decimals <= 18);
        
        product_prCode[product] = ++lastProductId;  
        prCode_product[lastProductId] = product;
        
        ProductInfo memory productInfo;
        productInfo.divider = 10 ** decimals; // max = 10 ^ 18
        productInfo.ownerId = owner_id[msg.sender];
        //productInfo.isDeposit = isDeposit;
        prCode_productInfo[lastProductId] = productInfo;
        
        prCode_AccountId_Balance[1][ownerId_accountId[1]].available += uint128(msg.value);
    }
    function SetProductInfo(uint32 prCode, bool isTradeBid, bool isTradeAsk, bool isDeposit, bool isWithdraw, uint104 _minQty) public
    {
        ProductInfo storage prInfo = prCode_productInfo[prCode];
        
        require(msg.sender == operator || owner_id[msg.sender] == prInfo.ownerId );
        
        prInfo.isTradeBid = isTradeBid;
        prInfo.isTradeAsk = isTradeAsk;
        prInfo.isDeposit = isDeposit;
        prInfo.isWithdraw = isWithdraw;
        prInfo.minQty = _minQty;
    }/*
    function SetProductMinQty(uint32 prCode, uint104 _minQty) public
    {
        ProductInfo storage prInfo = prCode_productInfo[prCode];
        require(msg.sender == operator || owner_id[msg.sender] == prInfo.ownerId );
        
        prInfo.minQty = _minQty;
    }*/
    function SetProductFee(uint256 productFee) onlyOperator public
    {
        newProductFee = productFee;
    }
    function GetProductList() view public returns (address[] products, uint32[] productIds)
    {
        products = new address[](lastProductId);
        productIds = new uint32[](lastProductId);
        
        for (uint32 i = 1; i <= lastProductId; i++)
        {
            products[i - 1] = prCode_product[i];
            productIds[i - 1] = i;
        }
    }
    function GetProductInfo(address product) view public returns (uint32 prCode, uint256 divider, bool isTradeBid, bool isTradeAsk, bool isDeposit, bool isWithdraw, uint32 ownerId, uint104 minQty)
    {
        prCode = product_prCode[product];
        require(prCode != 0);
        
        divider = prCode_productInfo[prCode].divider;
        isTradeBid = prCode_productInfo[prCode].isTradeBid;
        isTradeAsk = prCode_productInfo[prCode].isTradeAsk;
        isDeposit = prCode_productInfo[prCode].isDeposit;
        isWithdraw = prCode_productInfo[prCode].isWithdraw;
        ownerId = prCode_productInfo[prCode].ownerId;
        minQty = prCode_productInfo[prCode].minQty;
    }/*
    function AcceptProduct(uint32 prCode, bool isTrade) onlyOperator public
    {
        prCode_productInfo[prCode].isTrade = isTrade;
    }*/
    
    uint32 public lastAcccountId;
    mapping (uint32 => uint8) id_announceLV; //0: None, 1: Trade, 2:Balance, 3:DepositWithdrawal, 4:OpenOrder
    mapping (uint32 => address) id_account;
    mapping (uint32 => bool) accountId_freeze;
    mapping (address => uint32) account_id;
    
    function FindOrAddAccount() private returns (uint32)
    {
        if (account_id[msg.sender] == 0)
        {
            account_id[msg.sender] = ++lastAcccountId;
            id_account[lastAcccountId] = msg.sender;
        }
        return account_id[msg.sender];
    }
    function FindOrRevertAccount() private view returns (uint32)
    {
        uint32 accountId = account_id[msg.sender];
        require(accountId != 0);
        return accountId;
    }
    /*
    function GetAccountList() view onlyOperator public returns (address[] owners)//, uint32[] Ids)// Delete Later`
    {
        owners = new address[](lastAcccountId);
        //Ids = new uint32[](lastAcccountId);
        
        for (uint32 i = 1; i <= lastAcccountId; i++)
        {
            owners[i - 1] = id_account[i];
            //Ids[i - 1] = i;
        }
    }*/
    function GetMyAccountId() view public returns (uint32)
    {
        return account_id[msg.sender];
    }
    function GetAccountId(address account) view public returns (uint32)
    {
        return account_id[account];
    }
    function GetMyAnnounceLV() view public returns (uint32)
    {
        return id_announceLV[account_id[msg.sender]];
    }
    function ChangeAnnounceLV(uint8 announceLV) public
    {
        id_announceLV[FindOrRevertAccount()] = announceLV;
    }
    function SetFreezeByAddress(bool isFreeze, address account) onlyOperator public
    {
        uint32 accountId = account_id[account];
        
        if (accountId != 0)
        {
            accountId_freeze[accountId] = isFreeze;
        }
    }
    
    struct Balance
    {
        uint128 reserved;
        uint128 available;
    }
    
    struct ListItem
    {
        uint32 prev;
        uint32 next;
    }
    
    struct OrderLink
    {
        //uint32 orderN;
        uint32 firstId;
        uint32 lastId;
        uint80 nextPrice;
        uint80 prevPrice;
        mapping (uint32 => ListItem) id_orderList;
    }
    
    struct Order
    {
        uint32 ownerId;
        uint32 accountId;
        uint32 prTrade;
        uint32 prBase;
        uint104 qty;
        uint80 price;
        bool isSell;
        //uint64 timestamp;
    }

    uint32 public lastOrderId;
    mapping (uint32 => Order) id_Order;
        
    struct OrderBook
    {
        uint8 tickSize;
        
        uint80 bestBidPrice;
        uint80 bestAskPrice;

        mapping (uint80 => OrderLink) bidPrice_Order;
        mapping (uint80 => OrderLink) askPrice_Order;
    }
    mapping (uint32 => mapping (uint32 => OrderBook)) basePID_tradePID_orderBook;
    function SetOrderBookTickSize(uint32 prTrade, uint32 prBase, uint8 _tickSize) onlyOperator public
    {
        basePID_tradePID_orderBook[prBase][prTrade].tickSize = _tickSize;
    }
    
    mapping (uint32 => mapping (uint32 => Balance)) prCode_AccountId_Balance;
    
    //trading fee
    uint8 public takerFeeRateMain;
    function setTakerFeeRateMain(uint8 _takerFeeRateMain) onlyOperator public
    {
        if (_takerFeeRateMain <= 100)
            takerFeeRateMain = _takerFeeRateMain;//bp
    }
    
    struct OpenOrder
    {
        uint32 startId;
        mapping(uint32 => ListItem) id_orderList;
    }
    mapping(uint32 => OpenOrder) accountId_OpenOrder;
    function AddOpenOrder(uint32 accountId, uint32 orderId) private
    {
        OpenOrder memory openOrder = accountId_OpenOrder[accountId];

        if (openOrder.startId != 0)
        {
            accountId_OpenOrder[accountId].id_orderList[openOrder.startId].prev = orderId;
            accountId_OpenOrder[accountId].id_orderList[orderId].next = openOrder.startId;
        }
        accountId_OpenOrder[accountId].startId = orderId;
    }
    function RemoveOpenOrder(uint32 accountId, uint32 orderId) private
    {
        OpenOrder memory openOrder = accountId_OpenOrder[accountId];

        uint32 nextId = accountId_OpenOrder[accountId].id_orderList[orderId].next;
        uint32 prevId = accountId_OpenOrder[accountId].id_orderList[orderId].prev;

        if (nextId != 0)
        {
            accountId_OpenOrder[accountId].id_orderList[nextId].prev = prevId;
        }

        if (prevId != 0)
        {
            accountId_OpenOrder[accountId].id_orderList[prevId].next = nextId;
        }
        
        if (openOrder.startId == orderId)
        {
            accountId_OpenOrder[accountId].startId = nextId;
        }
    }

    struct DWrecord
    {
        uint32 prCode;
        bool isDeposit;
        uint128 qty;
        uint64 timestamp;
    }
    
    struct DWrecords
    {
        uint32 N;
        mapping (uint32 => DWrecord) N_DWrecord;
    }
    mapping (uint32 => DWrecords) AccountId_DWrecords;
    function RecordDW(uint32 accountId, uint32 prCode, bool isDeposit, uint128 qty) private
    {
        //DWrecords storage dWrecords = AccountId_DWrecords[accountId];

        DWrecord memory dW;
        dW.isDeposit = isDeposit;
        dW.prCode = prCode;
        dW.qty = qty;
        dW.timestamp = uint64(now);

        AccountId_DWrecords[accountId].N_DWrecord[++AccountId_DWrecords[accountId].N] = dW;
        
        if (isDeposit == true)
            emit NewDeposit(prCode, accountId, qty, dW.timestamp, lastTransferId++);
        else 
            emit NewWithdraw(prCode, accountId, qty, dW.timestamp, lastTransferId++);
    }
    function GetDWrecords(uint32 N, uint32 accountId) view public returns (uint32[] prCode, bool[] isDeposit, uint128[] qty, uint64[] timestamp)
    {
        //require (id_announceLV[accountId] > 2 || accountId == account_id[msg.sender]);
        checkAnnounceLV(accountId, 3);
        
        DWrecords storage dWrecords = AccountId_DWrecords[accountId];
        uint32 n = dWrecords.N;
        
        if (n > N)
            n = N;
            
        prCode = new uint32[](n);
        isDeposit = new bool[](n);
        qty = new uint128[](n);
        timestamp = new uint64[](n);

        for (uint32 i = dWrecords.N; i > dWrecords.N - n; i--)
        {
            N = dWrecords.N - i;
            prCode[N] = dWrecords.N_DWrecord[i].prCode;//Bug0309
            isDeposit[N] = dWrecords.N_DWrecord[i].isDeposit;//Bug0309
            qty[N] = dWrecords.N_DWrecord[i].qty;//Bug0309
            timestamp[N] = dWrecords.N_DWrecord[i].timestamp;//Bug0309
        }
    }
    
/////////////////
    function depositETH() payable public
    {
        uint32 accountId = FindOrAddAccount();
        prCode_AccountId_Balance[1][accountId].available = Add(prCode_AccountId_Balance[1][accountId].available, uint128(msg.value));
        RecordDW(accountId, 1, true, uint104(msg.value));
    }

    function withdrawETH(uint104 amount) public
    {
        uint32 accountId = FindOrRevertAccount();
        require(accountId_freeze[accountId] == false);
        prCode_AccountId_Balance[1][accountId].available = Sub(prCode_AccountId_Balance[1][accountId].available, amount);
        require(msg.sender.send(amount));
        RecordDW(accountId, 1, false,  amount);
    }

    function depositWithdrawToken(uint128 amount, bool isDeposit, address prAddress) public
    {
        uint32 prCode = product_prCode[prAddress];
        require(amount < maxBalance && prCode != 0);
        uint32 accountId = FindOrAddAccount();
        require(accountId_freeze[accountId] == false);
        //require(accountId != 0);
        
        if (isDeposit == true)
        {
            require(prCode_productInfo[prCode].isDeposit == true);//Bug0310
            require(Token(prAddress).transferFrom(msg.sender, this, amount));
            prCode_AccountId_Balance[prCode][accountId].available = Add(prCode_AccountId_Balance[prCode][accountId].available, amount);
            require (prCode_AccountId_Balance[prCode][accountId].available < maxBalance);
        }
        else
        {
            require(prCode_productInfo[prCode].isWithdraw == true);//Bug0310
            prCode_AccountId_Balance[prCode][accountId].available = Sub(prCode_AccountId_Balance[prCode][accountId].available, amount);
            require(Token(prAddress).transfer(msg.sender, amount));    
        }
        RecordDW(accountId, prCode, isDeposit, amount);
    }
    
    function emergencyWithdrawal(uint32 prCode, uint256 amount) onlyOwner public
    {
        require (isContractUse == false);//Added
        if (prCode == 1)
            require(msg.sender.send(amount));
        else
            Token(prCode_product[prCode]).transfer(msg.sender, amount);
    }
 /*
    function withdrawToken(address prAddress, uint128 amount) public
    {        
        uint32 prCode = product_prCode[prAddress];
        require(amount < maxBalance && prCode != 0);
        uint32 accountId = account_id[msg.sender];
        require(accountId != 0);
        
        //Balance storage balance = prCode_AccountId_Balance[prCode][accountId];
        prCode_AccountId_Balance[prCode][accountId].available = SafeMath.Sub(prCode_AccountId_Balance[prCode][accountId].available, amount);
        require(Token(prAddress).transfer(msg.sender, amount));
        RecordDW(accountId, prCode, false, amount);
    }
    /*
    function withdrawToken(address prAddress, uint128 amount, address toAddress) public
    {        
        uint32 prCode = product_prCode[prAddress];
        require(amount < maxBalance && prCode != 0);
        
        uint32 accountId = account_id[msg.sender];
        require(accountId != 0);
        
        //Balance storage balance = prCode_AccountId_Balance[prCode][accountId];
        prCode_AccountId_Balance[prCode][accountId].available = SafeMath.Sub(prCode_AccountId_Balance[prCode][accountId].available, amount);
        require(Token(prAddress).transfer(toAddress, amount));
        RecordDW(accountId, prCode, false, amount);
    }*/
    /*
    uint32 public maxOrderN;
    function SetMaxOrderN(uint32 _maxOrderN) public onlyOwner
    {
        maxOrderN = _maxOrderN;
    }*/
    function GetNextTick(bool isAsk, uint80 price, uint8 n) public pure returns (uint80)
    {
        if (price > 0)
        {
            uint80 tick = GetTick(price, n);
    
            if (isAsk == true)
                return (((price - 1) / tick) + 1) * tick;
            else
                return (price / tick) * tick;
        }
        else
        {
            return price;
        }
    }
    
    function GetTick(uint80 price, uint8 n)  public pure returns  (uint80)
    {
        if (n < 1)
            n = 1;
        
        uint80 x = 1;
        
        for (uint8 i=1; i <= n / 2; i++)
        {
            x *= 10;
        }
        
        if (price < 10 * x)
            return 1;
        else
        {
            uint80 tick = 10000;
                
            uint80 priceTenPercent = price / 10 / x;
                
            while (priceTenPercent > tick)
            {
                tick *= 10;
            }
    
            while (priceTenPercent < tick)
            {
                tick /= 10;
            }
            
            if (n % 2 == 1)
            {
                if (price >= 50 * tick * x)
                {
                    tick *= 5;
                }
            }
            else
            {
                if (price < 50 * tick * x)
                {
                    tick *= 5;
                }
                else
                {
                    tick *= 10;
                }
                
            }
            
            return tick;
        }
    }
    /*
    function LimitOrders(uint32 orderN, uint32 ownerId, uint32[] prTrade, uint32[] prBase, bool[] isSell, uint80[] price, uint104[] qty) public returns (uint32[])
    {
        require(orderN <= 10 &&  orderN == prTrade.length && orderN == prBase.length && orderN == isSell.length && orderN == price.length && orderN == qty.length);
        
        uint32[] memory orderId = new uint32[](orderN);
        for (uint32 i = 0; i < orderN; i++)
        {
            orderId[i] = LimitOrder(ownerId, prTrade[i], prBase[i], isSell[i], price[i], qty[i]);
        }
        return orderId;
    }
    */
    function LimitOrder(uint32 ownerId, uint32 prTrade, uint32 prBase, bool isSell, uint80 price, uint104 qty) public onlyContractUse  returns  (uint32)
    {
        uint32 accountId = FindOrRevertAccount();
        require(accountId_freeze[accountId] == false);
        uint80 lastBestPrice;
        OrderBook storage orderBook = basePID_tradePID_orderBook[prBase][prTrade];
        require(price != 0 && price <= maxPrice && qty <= maxQty &&
            ((isSell == false && prCode_productInfo[prTrade].isTradeBid == true && prCode_productInfo[prBase].isTradeAsk == true) 
            || (isSell == true && prCode_productInfo[prTrade].isTradeAsk == true && prCode_productInfo[prBase].isTradeBid == true)) 
            && prCode_productInfo[prTrade].minQty <= qty);
        
        if (isSell == true)
        {
            price = GetNextTick(true, price, orderBook.tickSize);
            lastBestPrice = orderBook.bestAskPrice;
        }
        else
        {
            price = GetNextTick(false, price, orderBook.tickSize);
            lastBestPrice = orderBook.bestBidPrice;
        }
        
        Order memory order;
        order.ownerId = ownerId;
        order.isSell = isSell;
        order.prTrade = prTrade;
        order.prBase = prBase;
        order.accountId = accountId;
        order.price = price;
        order.qty = qty;
        //order.timestamp = uint64(now);
        
        require (IsPossibleLimit(order));
        
        emit NewOrder(order.prTrade, order.prBase, order.accountId, ++lastOrderId, order.isSell, order.price, order.qty, lastNewOrderId++);

        //uint104 tradedQty = matchOrder(orderBook, order, lastOrderId);
        //BalanceUpdateByLimitAfterTrade(order, qty, tradedQty);

        BalanceUpdateByLimitAfterTrade(order, qty, matchOrder(orderBook, order, lastOrderId));

        if (order.qty != 0)
        {
            uint80 priceNext;
            uint80 price0;
            
            if (isSell == true)
            {
                price0 = orderBook.bestAskPrice;
                if (price0 == 0)
                {
                    orderBook.askPrice_Order[price].prevPrice = 0;
                    orderBook.askPrice_Order[price].nextPrice = 0;
                    orderBook.bestAskPrice = price;
                }
                else if(price < price0)
                {
                    orderBook.askPrice_Order[price0].prevPrice = price;
                    orderBook.askPrice_Order[price].prevPrice = 0;
                    orderBook.askPrice_Order[price].nextPrice = price0;
                    orderBook.bestAskPrice = price;
                }
                else if (orderBook.askPrice_Order[price].firstId == 0)// .orderN == 0)
                {
                    priceNext = price0;
                    
                    while (priceNext != 0 && priceNext < price)
                    {
                        price0 = priceNext;
                        priceNext = orderBook.askPrice_Order[price0].nextPrice;
                    }
                    
                    orderBook.askPrice_Order[price0].nextPrice = price;
                    orderBook.askPrice_Order[price].prevPrice = price0;
                    orderBook.askPrice_Order[price].nextPrice = priceNext;
                    if (priceNext != 0)
                    {
                        orderBook.askPrice_Order[priceNext].prevPrice = price;
                    }
                }
                
                OrderLink storage orderLink = orderBook.askPrice_Order[price];
            }
            else
            {
                price0 = orderBook.bestBidPrice;
                if (price0 == 0)
                {
                    orderBook.bidPrice_Order[price].prevPrice = 0;
                    orderBook.bidPrice_Order[price].nextPrice = 0;
                    orderBook.bestBidPrice = price;
                }
                else if (price > price0)
                {
                    orderBook.bidPrice_Order[price0].prevPrice = price;
                    orderBook.bidPrice_Order[price].prevPrice = 0;
                    orderBook.bidPrice_Order[price].nextPrice = price0;
                    orderBook.bestBidPrice = price;
                }
                else if (orderBook.bidPrice_Order[price].firstId == 0)// .orderN == 0)
                {
                    priceNext = price0;

                    while (priceNext != 0 && priceNext > price)
                    {
                        price0 = priceNext;
                        priceNext = orderBook.bidPrice_Order[price0].nextPrice;
                    }
                    
                    orderBook.bidPrice_Order[price0].nextPrice = price;
                    orderBook.bidPrice_Order[price].prevPrice = price0;
                    orderBook.bidPrice_Order[price].nextPrice = priceNext;
                    if (priceNext != 0)
                    {
                        orderBook.bidPrice_Order[priceNext].prevPrice = price;
                    }
                }

                orderLink = orderBook.bidPrice_Order[price];
            }
            
            if (lastOrderId != 0)
            {
                orderLink.id_orderList[lastOrderId].prev = orderLink.lastId;// .firstID;
                if (orderLink.firstId != 0)
                {
                    orderLink.id_orderList[orderLink.lastId].next = lastOrderId;
                }
                else
                {
                    orderLink.id_orderList[lastOrderId].next = 0;
                    orderLink.firstId = lastOrderId;
                }
                orderLink.lastId = lastOrderId;
            }

            //orderLink.id_orderList.Add(id, listItem);
            //id_Order.Add(id, order);
            //orderLink.id_orderList[lastOrderId] = listItem;
            
            AddOpenOrder(accountId, lastOrderId);
            //orderLink.orderN += 1;
            id_Order[lastOrderId] = order;
            //emit NewHogaChange(prTrade, prBase, isSell, price);
            
        }

        if (isSell == true && lastBestPrice != orderBook.bestAskPrice)
        {
            emit NewBestBidAsk(prTrade, prBase, isSell, orderBook.bestAskPrice);
        }
        if (isSell == false && lastBestPrice != orderBook.bestBidPrice)
        {
            emit NewBestBidAsk(prTrade, prBase, isSell, orderBook.bestBidPrice);
        }
        
        return lastOrderId;
    }
    
    function BalanceUpdateByLimitAfterTrade(Order order, uint104 qty, uint104 tradedQty) private
    {
        uint32 ownerId = order.ownerId;
        uint32 accountId = order.accountId;
        uint32 prTrade = order.prTrade;
        uint32 prBase = order.prBase;
        uint80 price = order.price;
        uint104 orderQty = order.qty;
        
        //require(qty >= orderQty);// && tradedQty < maxQty);
        
        //Balance storage balance;
        uint32 prTemp;

        if (order.isSell)
        {
            Balance storage balance = prCode_AccountId_Balance[prTrade][accountId];
            balance.available = Sub(balance.available, qty);
            
            if (orderQty != 0)
                balance.reserved = Add(balance.reserved, orderQty);

            prTemp = prBase;
        }
        else
        {
            balance = prCode_AccountId_Balance[prBase][accountId];/////
            if (orderQty != 0)
            {
                uint256 temp = prCode_productInfo[prBase].divider * orderQty * price / basePrice / prCode_productInfo[prTrade].divider;
                require (temp < maxQty);
                balance.available = Sub(balance.available, tradedQty + uint104(temp));
                balance.reserved = Add(balance.reserved, uint104(temp));
            }
            else
            {
                balance.available = Sub(balance.available, tradedQty);///////
            }
            tradedQty = qty - orderQty;

            prTemp = prTrade;
        }
        if (tradedQty != 0)
        {
            uint104 takeFeeMain = tradedQty * takerFeeRateMain / 10000;
            uint104 takeFeeLocal = tradedQty * ownerId_takerFeeRateLocal[ownerId] / 10000;
            prCode_AccountId_Balance[prTemp][accountId].available += tradedQty - takeFeeMain - takeFeeLocal;
            prCode_AccountId_Balance[prTemp][ownerId_accountId[1]].available += takeFeeMain;
            prCode_AccountId_Balance[prTemp][ownerId_accountId[ownerId]].available += takeFeeLocal;
        }
    }

    function IsPossibleLimit(Order memory order) private view returns (bool)
    {
        if (order.isSell)
        {
            if (prCode_AccountId_Balance[order.prTrade][order.accountId].available >= order.qty)
                return true;
            else
                return false;
        }
        else
        {
            if (prCode_AccountId_Balance[order.prBase][order.accountId].available >= prCode_productInfo[order.prBase].divider * order.qty * order.price / basePrice / prCode_productInfo[order.prTrade].divider)
                return true;
            else
                return false;
        }
    }

    function matchOrder(OrderBook storage ob, Order memory order, uint32 id) private returns (uint104)//, OrderBook storage orderBook, Order order, uint32 id) private returns (uint104)
    {
        uint32 prTrade = order.prTrade;
        uint32 prBase = order.prBase; 
        uint80 tradePrice;

        if (order.isSell == true)
            tradePrice = ob.bestBidPrice;
        else
            tradePrice = ob.bestAskPrice;

        bool isBestPriceUpdate = false;

        //OrderLink storage orderLink;// = price_OrderLink[tradePrice];
        uint104 qtyBase = 0;
        //Order storage matchingOrder;
        uint104 tradeAmount;
        
        while (tradePrice != 0 && order.qty > 0 && ((order.isSell && order.price <= tradePrice) || (!order.isSell && order.price >= tradePrice)))
        {
            if (order.isSell == true)
                OrderLink storage orderLink = ob.bidPrice_Order[tradePrice];
            else
                orderLink = ob.askPrice_Order[tradePrice];
                
            uint32 orderId = orderLink.firstId;
            
            while (orderLink.firstId != 0 && orderId != 0 && order.qty != 0)
            {
                Order storage matchingOrder = id_Order[orderId];
                if (matchingOrder.qty >= order.qty)
                {
                    tradeAmount = order.qty;
                    matchingOrder.qty -= order.qty;
                    order.qty = 0;
                }
                else
                {
                    tradeAmount = matchingOrder.qty;
                    order.qty -= matchingOrder.qty;
                    matchingOrder.qty = 0;
                }
                
                qtyBase += BalanceUpdateByTradeCp(order, matchingOrder, tradeAmount);
                
                uint32 orderAccountID = order.accountId;

                if (order.isSell == true)
                    emit NewTrade(prTrade, prBase, orderId, id, matchingOrder.accountId, orderAccountID, true, tradePrice,  tradeAmount, lastTradeId++, uint64(now));
                else
                    emit NewTrade(prTrade, prBase, id, orderId, orderAccountID, matchingOrder.accountId, false, tradePrice,  tradeAmount, lastTradeId++, uint64(now));
                
                if (matchingOrder.qty != 0)
                {
                    //id_Order[tradePrice] = matchingOrder;
                    break;
                }
                else
                {
                    if (RemoveOrder(prTrade, prBase, matchingOrder.isSell, tradePrice, orderId) == true)
                    {
                        RemoveOpenOrder(matchingOrder.accountId, orderId);
                    }
                    orderId = orderLink.firstId;
                }
            }
            
            //emit NewHogaChange(prTrade, prBase, !order.isSell, tradePrice);

            if (orderLink.firstId == 0)// .orderN == 0)
            {
                tradePrice = orderLink.nextPrice;
                isBestPriceUpdate = true;
            }
        }
        
        if (isBestPriceUpdate == true)
        {
            if (order.isSell)
            {
                ob.bestBidPrice = tradePrice;
            }
            else
            {
                ob.bestAskPrice = tradePrice;
            }
            
            emit NewBestBidAsk(prTrade, prBase, !order.isSell, tradePrice);
        }

        return qtyBase;
    }
    
    function BalanceUpdateByTradeCp(Order order, Order matchingOrder, uint104 tradeAmount) private returns (uint104)
    {
        uint32 accountId = matchingOrder.accountId;
        uint32 prTrade = order.prTrade; 
        uint32 prBase = order.prBase; 
        require (tradeAmount < maxQty);
        uint256 qtyBase = prCode_productInfo[prBase].divider * tradeAmount * matchingOrder.price / basePrice / prCode_productInfo[prTrade].divider;
        require (qtyBase < maxQty);
        /*
        if (order.isSell == true)
        {
            prCode_AccountId_Balance[prTrade][accountId].available = SafeMath.Add(prCode_AccountId_Balance[prTrade][accountId].available, tradeAmount);
            prCode_AccountId_Balance[prBase][accountId].reserved = SafeMath.Sub(prCode_AccountId_Balance[prBase][accountId].reserved, uint104(qtyBase));
        }
        else
        {
            prCode_AccountId_Balance[prTrade][accountId].reserved = SafeMath.Sub(prCode_AccountId_Balance[prTrade][accountId].reserved, tradeAmount);
            prCode_AccountId_Balance[prBase][accountId].available = SafeMath.Add(prCode_AccountId_Balance[prBase][accountId].available, uint104(qtyBase));
        }
        */
        Balance storage balanceTrade = prCode_AccountId_Balance[prTrade][accountId];
        Balance storage balanceBase = prCode_AccountId_Balance[prBase][accountId];
        
        if (order.isSell == true)
        {
            balanceTrade.available = SafeMath.Add(balanceTrade.available, tradeAmount);
            balanceBase.reserved = SafeMath.Sub(balanceBase.reserved, uint104(qtyBase));
        }
        else
        {
            balanceTrade.reserved = SafeMath.Sub(balanceTrade.reserved, tradeAmount);
            balanceBase.available = SafeMath.Add(balanceBase.available, uint104(qtyBase));
        }

        return uint104(qtyBase);
    }
    
    function RemoveOrder(uint32 prTrade, uint32 prBase, bool isSell, uint80 price, uint32 id) private returns (bool)
    {
        OrderBook storage ob = basePID_tradePID_orderBook[prBase][prTrade];
        
        if (isSell == false)
        {
            OrderLink storage orderLink = ob.bidPrice_Order[price];
        }
        else
        {
            orderLink = ob.askPrice_Order[price];
        }
        
        if (id != 0)
        {
            ListItem memory removeItem = orderLink.id_orderList[id];
            if (removeItem.next != 0)
            {
                orderLink.id_orderList[removeItem.next].prev = removeItem.prev;
            }

            if (removeItem.prev != 0)
            {
                orderLink.id_orderList[removeItem.prev].next = removeItem.next;
            }

            if (id == orderLink.lastId)
            {
                orderLink.lastId = removeItem.prev;
            }
            
            if (id == orderLink.firstId)
            {
                orderLink.firstId = removeItem.next;
            }

            delete orderLink.id_orderList[id];

            if (orderLink.firstId == 0)
            {
                if (orderLink.nextPrice != 0)
                {
                    if (isSell == true)
                        OrderLink storage replaceLink = ob.askPrice_Order[orderLink.nextPrice];
                    else
                        replaceLink = ob.bidPrice_Order[orderLink.nextPrice];

                    replaceLink.prevPrice = orderLink.prevPrice;
                }
                if (orderLink.prevPrice != 0)
                {
                    if (isSell == true)
                        replaceLink = ob.askPrice_Order[orderLink.prevPrice];
                    else
                        replaceLink = ob.bidPrice_Order[orderLink.prevPrice];

                    replaceLink.nextPrice = orderLink.nextPrice;
                }

                if (price == ob.bestAskPrice)
                {
                    ob.bestAskPrice = orderLink.nextPrice;
                }
                if (price == ob.bestBidPrice)
                {
                    ob.bestBidPrice = orderLink.nextPrice;
                }
            }
            return true;
        }
        else    
        {
            return false;
        }
    }

    function cancelOrders(uint32[] id) public
    {
        for (uint32 i = 0; i < id.length; i++)
        {
            cancelOrder(id[i]);
        }
    }
    
    function cancelOrder(uint32 id) public returns (bool)
    {
        Order memory order = id_Order[id];
        uint32 accountId = account_id[msg.sender];
        require(order.accountId == accountId);
        
        uint32 prTrade = order.prTrade;
        uint32 prBase = order.prBase;
        bool isSell = order.isSell;
        uint80 price = order.price;
        uint104 qty = order.qty;
        
        if (RemoveOrder(prTrade, prBase, isSell, price, id) == false)
            return false;
        else
        {
            RemoveOpenOrder(accountId, id);
        }

        //Balance storage balance;
        
        if (isSell)
        {
            Balance storage balance = prCode_AccountId_Balance[prTrade][accountId];
            balance.available = SafeMath.Add(balance.available, qty);
            balance.reserved = SafeMath.Sub(balance.reserved, qty);
        }
        else
        {
            balance = prCode_AccountId_Balance[prBase][accountId];
            uint256 temp = prCode_productInfo[prBase].divider * qty * price / basePrice / prCode_productInfo[prTrade].divider;
            require (temp < maxQty);
            balance.available = SafeMath.Add(balance.available, uint104(temp));
            balance.reserved = SafeMath.Sub(balance.reserved, uint104(temp));
        }

        //RemoveOrder(prTrade, prBase, isSell, price, id);//, msg);
        //emit NewHogaChange(prTrade, prBase, isSell, order.price);
        
        emit NewCancel(prTrade, prBase, accountId, id, isSell, price, qty, lastNewOrderId++);
        return true;
    }
    function checkAnnounceLV(uint32 accountId, uint8 LV) private view
    {
        require(accountId == account_id[msg.sender] || id_announceLV[accountId] >= LV || msg.sender == operator || owner_id[msg.sender] != 0);
    }
    /*
    function getBalance(uint32[] prCode) view public returns (uint128[] available, uint128[] reserved)
    {
        (available, reserved) = getBalance(prCode, msg.sender);
    }
      */
    function getBalance(uint32[] prCode, uint32 accountId) view public returns (uint128[] available, uint128[] reserved)
    {
        if (accountId == 0)
            accountId = account_id[msg.sender];
        checkAnnounceLV(accountId, 2);
        
        uint256 n = prCode.length;
        available = new uint128[](n);
        reserved = new uint128[](n);
        
        for (uint32 i = 0; i < n; i++)
        {
            available[i] = prCode_AccountId_Balance[prCode[i]][accountId].available;
            reserved[i] = prCode_AccountId_Balance[prCode[i]][accountId].reserved;
        }
    }
    
    function getBalanceByProduct(uint32 prCode, uint128 minQty) view public returns (uint32[] accountId, uint128[] balanceSum)
    {
        require (owner_id[msg.sender] != 0 || msg.sender == operator);
        uint32 n = 0;
        for (uint32 i = 1; i <= lastAcccountId; i++)//Bug0319
        {
            if (prCode_AccountId_Balance[prCode][i].available + prCode_AccountId_Balance[prCode][i].reserved >= minQty)
                n++;
        }
        accountId = new uint32[](n);
        balanceSum = new uint128[](n);
        
        n = 0;
        uint128 temp;
        for (i = 1; i <= lastAcccountId; i++)//Bug0319
        {
            temp = prCode_AccountId_Balance[prCode][i].available + prCode_AccountId_Balance[prCode][i].reserved;
            if (temp >= minQty)//Bug0319
            {
                accountId[n] = i;
                balanceSum[n++] = temp;
            }
        }
    }
    
    function getOrderBookInfo(uint32[] prTrade, uint32 prBase) view public returns (uint80[] bestBidPrice, uint80[] bestAskPrice)
    {
        uint256 n = prTrade.length;
        //require(n == prBase.length);
        bestBidPrice = new uint80[](n);//prTrade.length);
        bestAskPrice = new uint80[](n);//prTrade.length);
        
        for (uint256 i = 0; i < n; i++)
        {
            OrderBook memory orderBook = basePID_tradePID_orderBook[prBase][prTrade[i]];// iCode_OrderBook[prCode];
            bestBidPrice[i] = orderBook.bestBidPrice;
            bestAskPrice[i] = orderBook.bestAskPrice;
        }
    }
    /*
    function getOrder(uint32[] id) view public returns (uint32[] prTrade, uint32[] prBase, bool[] sell, uint80[] price, uint104[] qty)
    {
        uint256 n = id.length;
        prTrade = new uint32[](n);
        prBase = new uint32[](n);
        sell = new bool[](n);
        price = new uint80[](n);
        qty = new uint104[](n);
        
        for (uint256 i = 0; i < n; i++)
        {
            Order memory order = id_Order[id[i]];
            prTrade[i] = order.prTrade;
            prBase[i] = order.prBase;
            sell[i] = order.isSell;
            price[i] = order.price;
            qty[i] = order.qty;
        }
    }
    */
    
    function getOrder(uint32 id) view public returns (uint32 prTrade, uint32 prBase, bool sell, uint80 price, uint104 qty, uint32 accountId)//, uint64 timestamp)
    {
        Order memory order = id_Order[id];
        
        accountId = order.accountId;
        checkAnnounceLV(accountId, 4);
        
        prTrade = order.prTrade;
        prBase = order.prBase;
        price = order.price;
        sell = order.isSell;
        qty = order.qty;
        //timestamp = order.timestamp;
    }
    
    function GetMyOrders(uint32 accountId) view public returns (uint32[] orderId, uint32[] prTrade, uint32[] prBase, bool[] sells, uint80[] prices, uint104[] qtys)//, uint64[] timestamp)
    {
        if (accountId == 0)
            accountId = account_id[msg.sender];
        
        checkAnnounceLV(accountId, 4);
        
        OpenOrder storage openOrder = accountId_OpenOrder[accountId];
     
        uint32 id = accountId_OpenOrder[accountId].startId;
        uint32 orderN = 0;
        while (id != 0)
        {
            id = openOrder.id_orderList[id].next;
            orderN++;
        }

        orderId = new uint32[](orderN);
        prTrade = new uint32[](orderN);
        prBase = new uint32[](orderN);
        qtys = new uint104[](orderN);
        prices = new uint80[](orderN);
        sells = new bool[](orderN);
        //timestamp = new uint64[](orderN);
        
        id = openOrder.startId;
        if (id != 0)
        {
            Order memory order;
            uint32 i = 0;
            while (id != 0)
            {
                order = id_Order[id];
                
                orderId[i] = id;
                prTrade[i] = order.prTrade;
                prBase[i] = order.prBase;
                qtys[i] = order.qty;
                prices[i] = order.price;
                sells[i++] = order.isSell;
                //timestamp[i++] = order.timestamp;

                id = openOrder.id_orderList[id].next;
            }
        }
    }
    
    function GetHogaDetail(uint32 prTrade, uint32 prBase, uint80 price, bool isSell) view public returns (uint32[] orderIds)
    {
        if (isSell == false)
        {
            OrderLink storage orderLink = basePID_tradePID_orderBook[prBase][prTrade].bidPrice_Order[price];
        }
        else if (isSell == true)
        {
            orderLink = basePID_tradePID_orderBook[prBase][prTrade].askPrice_Order[price];
        }
        else
        {
            return;
        }
        
        uint32 n = 0;
        uint32 id0 = orderLink.firstId;
        while (id0 != 0)
        {
            id0 = orderLink.id_orderList[id0].next;
            n++;
        }
        
        orderIds = new uint32[](n);
        n = 0;
        id0 = orderLink.firstId;
        while (id0 != 0)
        {
            orderIds[n++] = id0;
            id0 = orderLink.id_orderList[id0].next;
        }
    }
    
    function GetHoga(uint32 prTrade, uint32 prBase, uint32 hogaN) public view returns (uint80[] priceB, uint104[] volumeB, uint80[] priceA, uint104[] volumeA)
    {
        OrderBook storage ob = basePID_tradePID_orderBook[prBase][prTrade];
        
        (priceB, volumeB) = GetHoga(ob, hogaN, false);
        (priceA, volumeA) = GetHoga(ob, hogaN, true);
    }
    
    function GetHoga(OrderBook storage ob, uint32 hogaN, bool isSell) private view returns (uint80[] prices, uint104[] volumes)
    {
        prices = new uint80[](hogaN);
        volumes = new uint104[](hogaN);
        
        uint32 n;
        uint32 id0;
        uint80 price;
        uint104 sum;
        
        if (isSell == false)
            price = ob.bestBidPrice;
        else
            price = ob.bestAskPrice;// .bestBidPrice;
        
        if (price != 0)
        {
            n = 0;
            while (price != 0 && n < hogaN)
            {
                if (isSell == false)
                    OrderLink storage orderLink = ob.bidPrice_Order[price];
                else
                    orderLink = ob.askPrice_Order[price];
                
                id0 = orderLink.firstId;
                sum = 0;
                while (id0 != 0)
                {
                    sum += id_Order[id0].qty;
                    id0 = orderLink.id_orderList[id0].next;
                }
                prices[n] = price;
                volumes[n] = sum;
                price = orderLink.nextPrice;
                n++;
            }

            if (n > 0)
            {
                while (n < hogaN)
                {
                    if (isSell == true)
                        prices[n] = GetNextTick(true, prices[n - 1] + 1, ob.tickSize);
                    else
                        prices[n] = GetNextTick(false, prices[n - 1] - 1, ob.tickSize);
                    n++;
                }
            }
        }
    }
}