contract StorageController is SafeMath, CreatorEnabled, StringMover {

    Storage public stor;
    IMNTP public mntpToken;
    IGold public goldToken;
    IGoldIssueBurnFee public goldIssueBurnFee;

    address public managerAddress = 0x0;

    event TokenBuyRequest(address _from, string _userId, uint _reference, uint _amount, uint indexed _index);
    event TokenSellRequest(address _from, string _userId, uint _reference, uint _amount, uint indexed _index);
    event RequestCancelled(uint indexed _index);
    event RequestProcessed(uint indexed _index);
    event RequestFailed(uint indexed _index);

    modifier onlyManagerOrCreator() { require(msg.sender == managerAddress || msg.sender == creator); _; }

    function StorageController(address _mntpContractAddress, address _goldContractAddress, address _storageAddress, address _goldIssueBurnFeeContract) public {
        creator = msg.sender;

        if (0 != _storageAddress) {
             // use existing storage
             stor = Storage(_storageAddress);
        } else {
             stor = new Storage();
        }

        require(0x0!=_mntpContractAddress);
        require(0x0!=_goldContractAddress);
        require(0x0!=_goldIssueBurnFeeContract);

        mntpToken = IMNTP(_mntpContractAddress);
        goldToken = IGold(_goldContractAddress);
        goldIssueBurnFee = IGoldIssueBurnFee(_goldIssueBurnFeeContract);
    }

    function setManagerAddress(address _address) public onlyCreator {
       managerAddress = _address;
    }

    // Only old controller can call setControllerAddress
    function changeController(address _newController) public onlyCreator {
        stor.setControllerAddress(_newController);
    }

    function setHotWalletAddress(address _hotWalletAddress) public onlyCreator {
       stor.setHotWalletAddress(_hotWalletAddress);
    }

    function getHotWalletAddress() public constant returns (address) {
        return stor.hotWalletAddress();
    }

    function changeGoldIssueBurnFeeContract(address _goldIssueBurnFeeAddress) public onlyCreator {
        goldIssueBurnFee = IGoldIssueBurnFee(_goldIssueBurnFeeAddress);
    }

    function addDoc(string _ipfsDocLink) public onlyManagerOrCreator returns(uint) {
        return stor.addDoc(_ipfsDocLink);
    }

    function getDocCount() public constant returns (uint) {
        return stor.getDocCount();
    }

    function getDoc(uint _index) public constant returns (string) {
        bytes32 x;
        bytes32 y;
        (x, y) = stor.getDocAsBytes64(_index);
        return bytes64ToString(x,y);
    }


    function addGoldTransaction(string _userId, int _amount) public onlyManagerOrCreator returns(uint) {
        return stor.addGoldTransaction(_userId, _amount);
    }

    function getGoldTransactionsCount(string _userId) public constant returns (uint) {
        return stor.getGoldTransactionsCount(_userId);
    }

    function getAllGoldTransactionsCount() public constant returns (uint) {
        return stor.getAllGoldTransactionsCount();
    }

    function getGoldTransaction(string _userId, uint _index) public constant returns(int) {
        require(keccak256(_userId) != keccak256(""));

        return stor.getGoldTransaction(_userId, _index);
    }

    function getUserHotGoldBalance(string _userId) public constant returns(uint) {
        require(keccak256(_userId) != keccak256(""));

        return stor.getUserHotGoldBalance(_userId);
    }

    function addBuyTokensRequest(string _userId, uint _reference) public payable returns(uint) {
        require(keccak256(_userId) != keccak256(""));
        require(msg.value > 0);

        uint reqIndex = stor.addBuyTokensRequest(msg.sender, _userId, _reference, msg.value);

        TokenBuyRequest(msg.sender, _userId, _reference, msg.value, reqIndex);

        return reqIndex;
    }

    function addSellTokensRequest(string _userId, uint _reference, uint _amount) public returns(uint) {
        require(keccak256(_userId) != keccak256(""));
        require(_amount > 0);

        uint tokenBalance = goldToken.balanceOf(msg.sender);

        require(tokenBalance >= _amount);

        burnGoldTokens(msg.sender, _amount);

        uint reqIndex = stor.addSellTokensRequest(msg.sender, _userId, _reference, _amount);

        TokenSellRequest(msg.sender, _userId, _reference, _amount, reqIndex);

        return reqIndex;
    }

    function getRequestsCount() public constant returns(uint) {
        return stor.getRequestsCount();
    }

    function getRequest(uint _index) public constant returns(address, string, uint, bool, uint8, uint) {
        address sender;
        bytes32 userIdBytes;
        uint reference;
        bool buy;
        uint8 state;
        uint inputAmount;

        (sender, userIdBytes, reference, buy, state, inputAmount) = stor.getRequest(_index);

        string memory userId = bytes32ToString(userIdBytes);
        
        return (sender, userId, reference, buy, state, inputAmount);
    }
    
    function getRequestBaseInfo(uint _index) public constant returns(address, uint8, uint, uint) {
        return stor.getRequestBaseInfo(_index);
    }

    function cancelRequest(uint _index) onlyManagerOrCreator public {

        address sender;
        string memory userId;
        uint reference;
        bool isBuy;
        uint state;
        uint inputAmount;
        (sender, userId, reference, isBuy, state, inputAmount) = getRequest(_index);
        require(0 == state);

        if (isBuy) {
            sender.transfer(inputAmount);
        } else {
            goldToken.issueTokens(sender, inputAmount);
        }

        stor.cancelRequest(_index);

        RequestCancelled(_index);
    }

    function processRequest(uint _index, uint _weiPerGold, uint _discountPercent) onlyManagerOrCreator public returns(bool) {
        require(_index < getRequestsCount());

        address sender;
        string memory userId;
        uint reference;
        bool isBuy;
        uint state;
        uint inputAmount;
        
        (sender, userId, reference, isBuy, state, inputAmount) = getRequest(_index);
        require(0 == state);

        bool processResult = false;
        uint outputAmount = 0;

        if (isBuy) {
            (processResult, outputAmount) = processBuyRequest(userId, sender, inputAmount, _weiPerGold, false, _discountPercent);
        } else {
            (processResult, outputAmount) = processSellRequest(userId, sender, inputAmount, _weiPerGold, false);
        }

        if (processResult) {
            stor.setRequestProcessed(_index, outputAmount);
            RequestProcessed(_index);
        } else {
            stor.setRequestFailed(_index);
            RequestFailed(_index);
        }

        return processResult;
    }

    function processBuyRequestFiat(string _userId, uint _reference, address _userAddress, uint _amountCents, uint _centsPerGold) onlyManagerOrCreator public returns(bool) {
      
      uint reqIndex = stor.addBuyTokensRequest(_userAddress, _userId, _reference, _amountCents);

      bool processResult = false;
      uint outputAmount = 0;
        
      (processResult, outputAmount) = processBuyRequest(_userId, _userAddress, _amountCents * 1 ether, _centsPerGold * 1 ether, true, 0);

      if (processResult) {
        stor.setRequestProcessed(reqIndex, outputAmount);
        RequestProcessed(reqIndex);
      } else {
        stor.setRequestFailed(reqIndex);
        RequestFailed(reqIndex);
      }

      return processResult;
    }

    function processSellRequestFiat(uint _index, uint _centsPerGold) onlyManagerOrCreator public returns(bool) {
        require(_index < getRequestsCount());

        address sender;
        string memory userId;
        uint reference;
        bool isBuy;
        uint state;
        uint inputAmount;
        (sender, userId, reference, isBuy, state, inputAmount) = getRequest(_index);
        require(0 == state);

        
        // fee
        uint userMntpBalance = mntpToken.balanceOf(sender);
        uint fee = goldIssueBurnFee.calculateBurnGoldFee(userMntpBalance, inputAmount, true);

        require(inputAmount > fee);

        if (fee > 0) {
             inputAmount = safeSub(inputAmount, fee);
        }

        require(inputAmount > 0);

        uint resultAmount = inputAmount * _centsPerGold / 1 ether;
        
        stor.setRequestProcessed(_index, resultAmount);
        RequestProcessed(_index);
        
        return true;
    }

    function processBuyRequest(string _userId, address _userAddress, uint _amountWei, uint _weiPerGold, bool _isFiat, uint _discountPercent) internal returns(bool, uint) {
        require(keccak256(_userId) != keccak256(""));

        uint userMntpBalance = mntpToken.balanceOf(_userAddress);
        uint fee = goldIssueBurnFee.calculateIssueGoldFee(userMntpBalance, _amountWei, _isFiat);
        require(_amountWei > fee);

        // issue tokens minus fee
        uint amountWeiMinusFee = _amountWei;
        if (fee > 0) {
            amountWeiMinusFee = safeSub(_amountWei, fee);
        }

        require(amountWeiMinusFee > 0);

        uint tokensWei = safeDiv(uint(amountWeiMinusFee) * 1 ether, _weiPerGold);
        
        //discount
        tokensWei = SafeMath.safeAdd(tokensWei, calcPercent(tokensWei, _discountPercent));

        issueGoldTokens(_userAddress, tokensWei);

        // request from hot wallet
        if (isHotWallet(_userAddress)) {
            addGoldTransaction(_userId, int(tokensWei));
        }

        return (true, tokensWei);
    }

    function processSellRequest(string _userId, address _userAddress, uint _amountWei, uint _weiPerGold, bool _isFiat) internal returns(bool, uint) {
        require(keccak256(_userId) != keccak256(""));

        uint amountWei = safeMul(_amountWei, _weiPerGold) / 1 ether;

        require(amountWei > 0);
        // request from hot wallet
        if (isHotWallet(_userAddress)) {
            // TODO: overflow
            addGoldTransaction(_userId, - int(_amountWei));
        }

        // fee
        uint userMntpBalance = mntpToken.balanceOf(_userAddress);
        uint fee = goldIssueBurnFee.calculateBurnGoldFee(userMntpBalance, amountWei, _isFiat);

        require(amountWei > fee);

        uint amountWeiMinusFee = amountWei;

        if (fee > 0) {
             amountWeiMinusFee = safeSub(amountWei, fee);
        }

        require(amountWeiMinusFee > 0);

        if (amountWeiMinusFee > this.balance) {
            issueGoldTokens(_userAddress, _amountWei);
            return (false, 0);
        }

        _userAddress.transfer(amountWeiMinusFee);

        return (true, amountWeiMinusFee);
    }



    //////// INTERNAL REQUESTS FROM HOT WALLET
    function processInternalRequest(string _userId, bool _isBuy, uint _amountCents, uint _centsPerGold, uint _discountPercent) onlyManagerOrCreator public {
      if (_isBuy) {
          processBuyRequest(_userId, getHotWalletAddress(), _amountCents, _centsPerGold, true, _discountPercent);
      } else {
          processSellRequest(_userId, getHotWalletAddress(), _amountCents, _centsPerGold, true);
      }
    }

    function transferGoldFromHotWallet(address _to, uint _value, string _userId) onlyManagerOrCreator public {
      require(keccak256(_userId) != keccak256(""));

      uint balance = getUserHotGoldBalance(_userId);
      require(balance >= _value);

      goldToken.burnTokens(getHotWalletAddress(), _value);
      goldToken.issueTokens(_to, _value);

      addGoldTransaction(_userId, -int(_value));
    }


    function withdrawEth(address _userAddress, uint _value) onlyManagerOrCreator public {
        require(_value >= 0.1 * 1 ether);

        if (this.balance < _value) _value = this.balance;

        _userAddress.transfer(_value);
    }

    function withdrawTokens(address _userAddress, uint _value) onlyManagerOrCreator public {
        burnGoldTokens(address(this), _value);

        issueGoldTokens(_userAddress, _value);
    }

    ////////
    function issueGoldTokens(address _userAddress, uint _tokenAmount) internal {
        require(0!=_tokenAmount);
        goldToken.issueTokens(_userAddress, _tokenAmount);
    }

    function burnGoldTokens(address _userAddress, uint _tokenAmount) internal {
        require(0!=_tokenAmount);
        goldToken.burnTokens(_userAddress, _tokenAmount);
    }

    function isHotWallet(address _address) internal returns(bool) {
       return _address == getHotWalletAddress();
    }
    
    function calcPercent(uint256 amount, uint256 percent) public pure returns(uint256) {
        return SafeMath.safeDiv(SafeMath.safeMul(SafeMath.safeDiv(amount, 100), percent), 1 ether);
    }

         // Default fallback function
    function() payable {
    }
}
function issueGoldTokens(address _userAddress, uint _tokenAmount) internal {
    require(0!=_tokenAmount);
    goldToken.issueTokens(_userAddress, _tokenAmount);
}

////////