contract BICALogic is AdminUpgradeable {

    using SafeMath for uint256;

    /**
     * Event
     * @dev After issuing an ammout of BICA, emit an event for the value of requester.
     */
    event Requester(address _supplyAddress, address _receiver, uint256 _valueRequested);

    /**
     * Event
     * @dev After issuing an ammout of BICA, emit an event of paying margin.
     */
    event PayMargin(address _supplyAddress, address _marginAddress, uint256 _marginValue);


    /**
     * Event
     * @dev After issuing an ammout of BICA, emit an event of paying interest.
     */
    event PayInterest(address _supplyAddress, address _interestAddress, uint256 _interestValue);


    /**
     * Event
     * @dev After issuing an ammout of BICA, emit an event of paying multi fee.
     */
    event PayMultiFee(address _supplyAddress, address _feeAddress, uint256 _feeValue);

    /**
     * Event
     * @dev After freezing a user address, emit an event in logic contract.
     */
    event AddressFrozenInLogic(address indexed addr);

    /**
     * Event
     * @dev After unfreezing a user address, emit an event in logic contract.
     */
    event AddressUnfrozenInLogic(address indexed addr);

    /**
     * MEMBER: A reference to the proxy contract.
     * It links the proxy contract in one direction.
     */
    BICAProxy public bicaProxy;

    /**
     * MEMBER: A reference to the ledger contract.
     * It links the ledger contract in one direction.
     */
    BICALedger public bicaLedger;

    /**
     * MODIFIER: The call from bicaProxy is allowed only.
     */
    modifier onlyProxy {
        require(msg.sender == address(bicaProxy), "only the proxy contract allowed only");
        _;
    }

    /**
     * CONSTRUCTOR: Initialize with the proxy contract address, the ledger
     * contract and an admin address.
     */
    constructor (address _bicaProxy, address _bicaLedger, address _admin) public  AdminUpgradeable(_admin) {
        bicaProxy = BICAProxy(_bicaProxy);
        bicaLedger = BICALedger(_bicaLedger);
    }
    
    /**
     * METHOD: `approve` operation in logic contract.
     * @dev Receive the call request of `approve` from proxy contract and
     * request approve operation to ledger contract. Need to check the sender
     * and spender are not frozen
     * @param _sender The address initiating the approval in proxy.
     * @return success or not.
     */
    function approveWithSender(address _sender, address _spender, uint256 _value) public onlyProxy returns (bool success){
        require(_spender != address(0));

        bool senderFrozen = bicaLedger.getFrozenByAddress(_sender);
        require(!senderFrozen, "Sender is frozen");

        bool spenderFrozen = bicaLedger.getFrozenByAddress(_spender);
        require(!spenderFrozen, "Spender is frozen");

        bicaLedger.setAllowance(_sender, _spender, _value);
        bicaProxy.emitApproval(_sender, _spender, _value);
        return true;
    }

    /**
     * METHOD: Core logic of the `increaseApproval` method in proxy contract.
     * @dev Receive the call request of `increaseApproval` from proxy contract
     * and request increasing value of allownce to ledger contract. Need to
     * check the sender
     * and spender are not frozen
     * @param _sender The address initiating the approval in proxy.
     * @return success or not.
     */
    function increaseApprovalWithSender(address _sender, address _spender, uint256 _addedValue) public onlyProxy returns (bool success) {
        require(_spender != address(0));

        bool senderFrozen = bicaLedger.getFrozenByAddress(_sender);
        require(!senderFrozen, "Sender is frozen");

        bool spenderFrozen = bicaLedger.getFrozenByAddress(_spender);
        require(!spenderFrozen, "Spender is frozen");

        uint256 currentAllowance = bicaLedger.allowed(_sender, _spender);
        uint256 newAllowance = currentAllowance.add(_addedValue);

        require(newAllowance >= currentAllowance);

        bicaLedger.setAllowance(_sender, _spender, newAllowance);
        bicaProxy.emitApproval(_sender, _spender, newAllowance);
        return true;
    }

    /**
    * METHOD: Core logic of the `decreaseApproval` method in proxy contract.
    * @dev Receive the call request of `decreaseApproval` from proxy contract
    * and request decreasing value of allownce to ledger contract. Need to
    * check the sender and spender are not frozen
    * @param _sender The address initiating the approval in proxy.
    * @return success or not.
    */
    function decreaseApprovalWithSender(address _sender, address _spender, uint256 _subtractedValue) public onlyProxy returns (bool success) {
        require(_spender != address(0));

        bool senderFrozen = bicaLedger.getFrozenByAddress(_sender);
        require(!senderFrozen, "Sender is frozen");

        bool spenderFrozen = bicaLedger.getFrozenByAddress(_spender);
        require(!spenderFrozen, "Spender is frozen");
        
        uint256 currentAllowance = bicaLedger.allowed(_sender, _spender);
        uint256 newAllowance = currentAllowance.sub(_subtractedValue);

        require(newAllowance <= currentAllowance);

        bicaLedger.setAllowance(_sender, _spender, newAllowance);
        bicaProxy.emitApproval(_sender, _spender, newAllowance);
        return true;
    }


    /**
     * METHOD: Core logic of comfirming request of issuetoken to a specified receiver.
     * @dev Admin can issue an ammout of BICA only.
     * @param _requesterAccount The address of request account.
     * @param _requestValue The value of requester.
     * @param _marginAccount The address of margin account.
     * @param _marginValue The value of token to pay to margin account.
     * @param _interestAccount The address accepting interest.
     * @param _interestValue The value of interest.
     * @param _otherFeeAddress The address accepting multi fees.
     * @param _otherFeeValue The value of other fees.
     */
    function issue(address _requesterAccount, uint256 _requestValue,
        address _marginAccount, uint256 _marginValue,
        address _interestAccount, uint256 _interestValue,
        address _otherFeeAddress, uint256 _otherFeeValue) public adminOperations {

        require(_requesterAccount != address(0));
        require(_marginAccount != address(0));
        require(_interestAccount != address(0));
        require(_otherFeeAddress != address(0));

        require(!bicaLedger.getFrozenByAddress(_requesterAccount), "Requester is frozen");
        require(!bicaLedger.getFrozenByAddress(_marginAccount), "Margin account is frozen");
        require(!bicaLedger.getFrozenByAddress(_interestAccount), "Interest account is frozen");
        require(!bicaLedger.getFrozenByAddress(_otherFeeAddress), "Other fee account is frozen");

        uint256 requestTotalValue = _marginValue.add(_interestValue).add(_otherFeeValue).add(_requestValue);

        uint256 supply = bicaLedger.totalSupply();
        uint256 newSupply = supply.add(requestTotalValue);

        if (newSupply >= supply) {
            bicaLedger.setTotalSupply(newSupply);
            bicaLedger.addBalance(_marginAccount, _marginValue);
            bicaLedger.addBalance(_interestAccount, _interestValue);
            if ( _otherFeeValue > 0 ){
                bicaLedger.addBalance(_otherFeeAddress, _otherFeeValue);
            }
            bicaLedger.addBalance(_requesterAccount, _requestValue);

            emit Requester(msg.sender, _requesterAccount, _requestValue);
            emit PayMargin(msg.sender, _marginAccount, _marginValue);
            emit PayInterest(msg.sender, _interestAccount, _interestValue);
            emit PayMultiFee(msg.sender, _otherFeeAddress, _otherFeeValue);

            bicaProxy.emitTransfer(address(0), _marginAccount, _marginValue);
            bicaProxy.emitTransfer(address(0), _interestAccount, _interestValue);
            bicaProxy.emitTransfer(address(0), _otherFeeAddress, _otherFeeValue);
            bicaProxy.emitTransfer(address(0), _requesterAccount, _requestValue);
        }
    }

    /**
     * METHOD: Burn the specified value of the message sender's balance.
     * @dev Admin can call this method to burn some amount of BICA.
     * @param _value The amount of token to be burned.
     * @return success or not.
     */
    function burn(uint256 _value) public adminOperations returns (bool success) {
        bool burnerFrozen = bicaLedger.getFrozenByAddress(msg.sender);
        require(!burnerFrozen, "Burner is frozen");

        uint256 balanceOfSender = bicaLedger.balances(msg.sender);
        require(_value <= balanceOfSender);

        bicaLedger.setBalance(msg.sender, balanceOfSender.sub(_value));
        bicaLedger.setTotalSupply(bicaLedger.totalSupply().sub(_value));

        bicaProxy.emitTransfer(msg.sender, address(0), _value);

        return true;
    }

    /**
     * METHOD: Freeze a user address.
     * @dev Admin can call this method to freeze a user account.
     * @param _user user address.
     */
    function freeze(address _user) public adminOperations {
        require(_user != address(0), "the address to be frozen cannot be 0");
        bicaLedger.freezeByAddress(_user);
        emit AddressFrozenInLogic(_user);
    }

    /**
     * METHOD: Unfreeze a user address.
     * @dev Admin can call this method to unfreeze a user account.
     * @param _user user address.
     */
    function unfreeze(address _user) public adminOperations {
        require(_user != address(0), "the address to be unfrozen cannot be 0");
        bicaLedger.unfreezeByAddress(_user);
        emit AddressUnfrozenInLogic(_user);
    }

    /**
     * METHOD: Core logic of `transferFrom` interface method in ERC20 token standard.
     * @dev It can only be called by the `bicaProxy` contract.
     * @param _sender The address initiating the approval in proxy.
     * @return success or not.
     */
    function transferFromWithSender(address _sender, address _from, address _to, uint256 _value) public onlyProxy returns (bool success){
        require(_to != address(0));

        bool senderFrozen = bicaLedger.getFrozenByAddress(_sender);
        require(!senderFrozen, "Sender is frozen");
        bool fromFrozen = bicaLedger.getFrozenByAddress(_from);
        require(!fromFrozen, "`from` is frozen");
        bool toFrozen = bicaLedger.getFrozenByAddress(_to);
        require(!toFrozen, "`to` is frozen");

        uint256 balanceOfFrom = bicaLedger.balances(_from);
        require(_value <= balanceOfFrom);

        uint256 senderAllowance = bicaLedger.allowed(_from, _sender);
        require(_value <= senderAllowance);

        bicaLedger.setBalance(_from, balanceOfFrom.sub(_value));

        bicaLedger.addBalance(_to, _value);

        bicaLedger.setAllowance(_from, _sender, senderAllowance.sub(_value));

        bicaProxy.emitTransfer(_from, _to, _value);

        return true;
    }

    /**
    * METHOD: Core logic of `transfer` interface method in ERC20 token standard.
    * @dev It can only be called by the `bicaProxy` contract.
    * @param _sender The address initiating the approval in proxy.
    * @return success or not.
    */
    function transferWithSender(address _sender, address _to, uint256 _value) public onlyProxy returns (bool success){
        require(_to != address(0));

        bool senderFrozen = bicaLedger.getFrozenByAddress(_sender);
        require(!senderFrozen, "sender is frozen");
        bool toFrozen = bicaLedger.getFrozenByAddress(_to);
        require(!toFrozen, "to is frozen");

        uint256 balanceOfSender = bicaLedger.balances(_sender);
        require(_value <= balanceOfSender);

        bicaLedger.setBalance(_sender, balanceOfSender.sub(_value));

        bicaLedger.addBalance(_to, _value);

        bicaProxy.emitTransfer(_sender, _to, _value);

        return true;
    }

    /**
     * METHOD: Core logic of `totalSupply` interface method in ERC20 token standard.
     */
    function totalSupply() public view returns (uint256) {
        return bicaLedger.totalSupply();
    }

    /**
     * METHOD: Core logic of `balanceOf` interface method in ERC20 token standard.
     */
    function balanceOf(address _owner) public view returns (uint256 balance) {
        return bicaLedger.balances(_owner);
    }

    /**
     * METHOD: Core logic of `allowance` interface method in ERC20 token standard.
     */
    function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
        return bicaLedger.allowed(_owner, _spender);
    }
}
/**
 * @dev This contract is the core contract of all logic. It links `bicaProxy`
 * and `bicaLedger`. It implements the issue of new amount of token, burn some
 * value of someone's token.
 */
function increaseApprovalWithSender(address _sender, address _spender, uint256 _addedValue) public onlyProxy returns (bool success) {
    require(_spender != address(0));

    bool senderFrozen = bicaLedger.getFrozenByAddress(_sender);
    require(!senderFrozen, "Sender is frozen");

    bool spenderFrozen = bicaLedger.getFrozenByAddress(_spender);
    require(!spenderFrozen, "Spender is frozen");

    uint256 currentAllowance = bicaLedger.allowed(_sender, _spender);
    uint256 newAllowance = currentAllowance.add(_addedValue);

    require(newAllowance >= currentAllowance);

    bicaLedger.setAllowance(_sender, _spender, newAllowance);
    bicaProxy.emitApproval(_sender, _spender, newAllowance);
    return true;
}

/**
 * METHOD: Core logic of the `increaseApproval` method in proxy contract.
 * @dev Receive the call request of `increaseApproval` from proxy contract
 * and request increasing value of allownce to ledger contract. Need to
 * check the sender
 * and spender are not frozen
 * @param _sender The address initiating the approval in proxy.
 * @return success or not.
 */