contract UAXCoinController is IERC20, UAXCoinOwnable {
    using SafeMath for uint256;

    UAXCoinAccounts private _accounts;

    string private _name;
    string private _symbol;
    uint8 private _decimals;

    /**
    * @dev Whitelisted account only mode
    */
    bool private _useWhiteList;

    /**
     * @dev Contract has frozen accounts
     */
    bool private _hasFrozenAccounts;

    /**
     * @dev Emitted when the freeze is triggered for a specific account.
     */
    event AccountFreeze(address account);

    /**
     * @dev Emitted when the unfreeze is triggered for a specific account.
     */
    event AccountUnfreeze(address account);

    /**
     * @dev Sets initial data
     */
    function initialize() public onlyOwner {
        _name = 'UAX';
        _symbol = 'UAX';
        _decimals = 2;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5,05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei.
     *
     * > Note that this information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * `IERC20.balanceOf` and `IERC20.transfer`.
     */
    function decimals() public view returns (uint8) {
        return _decimals;
    }

    /**
     * @dev Sets new token name.
     */
    function setName(string memory newName) public onlyOwner {
        _name = newName;
    }

    /**
     * @dev Sets new token symbol.
     */
    function setSymbol(string memory newSymbol) public onlyOwner {
        _symbol = newSymbol;
    }

    /**
     * @dev Accounts contract
     */
    function setAccounts(UAXCoinAccounts accounts) public onlyOwner {
        _accounts = accounts;
        _hasFrozenAccounts = accounts.hasFrozen();
    }

    /**
     * @dev totalSupply proxy
     */
    function totalSupply() public view returns (uint256) {
        return _accounts.totalSupply();
    }

    /**
     * @dev balanceOf proxy
     */
    function balanceOf(address account) public view returns (uint256) {
        return _accounts.balanceOf(account);
    }

    /**
     * @dev allowance proxy
     */
    function allowance(address owner, address spender) public view returns (uint256) {
        return _accounts.allowance(owner, spender);
    }

    /**
     * @dev Returns whitelisted accounts only mode state
     */
    function isWhitelistedModeActive() public view returns (bool) {
        return _useWhiteList;
    }

    /**
     * @dev Sets or resets mode when only whitelisted accounts are allowed
     */
    function allowWhitelistedAccountsOnly(bool value) public onlyOwner {
        _useWhiteList = value;
    }

    /**
     * @dev Freeze account
     */
    function freeze(address account) public onlyOwner {
        _accounts.freeze(account);
        _hasFrozenAccounts = true;
        emit AccountFreeze(account);
    }

    /**
     * @dev Unfreeze account
     */
    function unfreeze(address account) public onlyOwner {
        _accounts.unfreeze(account);
        _hasFrozenAccounts = _accounts.hasFrozen();
        emit AccountUnfreeze(account);
    }

    /**
     * @dev Marks address as whitelisted
     */
    function addToWhitelist(address account) public onlyOwner {
        _accounts.addToWhitelist(account);
    }

    /**
     * @dev Clears address whitelisted flag
     */
    function removeFromWhiteList(address account) public onlyOwner {
        _accounts.removeFromWhiteList(account);
    }

    function setNonce(address _address, uint256 _nonce) private {
        _accounts.setNonce(_address, _nonce);
    }

    /**
     * @dev transfer
     */
    function transfer(address to, uint256 value) public returns(bool) {
        if (_useWhiteList) {
            require(_accounts.bothWhitelisted(msg.sender, to), "Unknown account");
        }
        if (_hasFrozenAccounts) {
            require(!_accounts.anyFrozen(msg.sender, to), "Frozen account");
        }
        
        _transfer(msg.sender, to, value);

        return true;
    }

    /**
     * @dev ERC20 approve implementation
     */
    function approve(address spender, uint256 value) public returns(bool) {
        _approve(msg.sender, spender, value);
        return true;
    }

    /**
     * @dev ERC20 transferFrom implementation
     */
    function transferFrom(address from, address to, uint256 value) public returns(bool) {
        if (_useWhiteList) {
            require(_accounts.bothWhitelisted(from, to), "Unknown account");
        }
        if (_hasFrozenAccounts) {
            require(!_accounts.anyFrozen(from, to), "Frozen account");
        }
        uint256 allowanceValue = _accounts.allowance(from, msg.sender);
        allowanceValue = allowanceValue.sub(value);
        _approve(from, msg.sender, allowanceValue);

        _transfer(from, to, value);

        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to `approve` that can be used as a mitigation for
     * problems described in `IERC20.approve`.
     *
     * Emits an `Approval` event indicating the updated allowance.
     */
    function increaseAllowance(address spender, uint256 value) public returns (bool) {
        uint256 allowanceValue = _accounts.allowance(msg.sender, spender);
        allowanceValue = allowanceValue.add(value);
        _approve(msg.sender, spender, allowanceValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to `approve` that can be used as a mitigation for
     * problems described in `IERC20.approve`.
     *
     * Emits an `Approval` event indicating the updated allowance.
     */
    function decreaseAllowance(address spender, uint256 value)  public returns (bool) {
        uint256 allowanceValue = _accounts.allowance(msg.sender, spender);
        allowanceValue = allowanceValue.sub(value);
        _approve(msg.sender, spender, allowanceValue);
        return true;
    }

    /**
     * @dev Issue tokens to an account
     */
    function issue(uint256 value) public onlyOwner {
        _accounts.addBalanceAndTotalSupply(msg.sender, value);
        emit Transfer(address(0x0), msg.sender, value);
    }

    function delegatedTransfer(
        address _to,
        uint256 _value,
        uint256 _fee,
        uint256 _nonce,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    )
        public
        returns (bool)
    {
        if (_useWhiteList) {
            require(_accounts.isWhitelisted(_to), "Unknown account");
        }
        if (_hasFrozenAccounts) {
            require(!_accounts.isFrozen(_to), "Frozen account");
        }

        address _from = ecrecover(
            keccak256(
                abi.encodePacked(
                    address(this), msg.sender, _to, _value, _fee, _nonce
                )
            ),
            _v, _r, _s
        );

        if (_useWhiteList) {
            require(_accounts.isWhitelisted(_from), "Unknown account");
        }
        if (_hasFrozenAccounts) {
            require(!_accounts.isFrozen(_from), "Frozen account");
        }
        require(_nonce == nonce(_from), "Invalid nonce");
        require(_fee.add(_value) <= balanceOf(_from), "Invalid balance");
        require(
            _to != address(0),
            "ERC20: delegated transfer to the zero address"
        );

        setNonce(_from, _nonce.add(1));

        _accounts.transferToTwo(_from, _to, msg.sender, _value, _fee);

        emit Transfer(_from, _to, _value);
        emit Transfer(_from, msg.sender, _fee);

        return true;

    }

    function delegatedMultiTransfer(
        address[] memory _to_arr,
        uint256[] memory _value_arr,
        uint256[] memory _fee_arr,
        uint256[] memory _nonce_arr,
        uint8[] memory _v_arr,
        bytes32[] memory _r_arr,
        bytes32[] memory _s_arr
    )
    public
    returns (bool)
    {
        require(
            _to_arr.length == _value_arr.length &&
            _to_arr.length == _fee_arr.length &&
            _to_arr.length == _nonce_arr.length &&
            _to_arr.length == _v_arr.length &&
            _to_arr.length == _r_arr.length &&
            _to_arr.length == _s_arr.length,
            'Incorrect input length'
        );

        for (uint i = 0; i < _to_arr.length; i++) {
            delegatedTransfer(
                _to_arr[i],
                _value_arr[i],
                _fee_arr[i],
                _nonce_arr[i],
                _v_arr[i],
                _r_arr[i],
                _s_arr[i]
            );
        }
    }

    function nonce(address _address) public view returns (uint256) {
        return _accounts.nonce(_address);
    }

    /**
     * @dev Burn tokens from an account
     */
    function burn(uint256 value) public onlyOwner {
        _accounts.subBalanceAndTotalSupply(msg.sender, value);
        emit Transfer(msg.sender, address(0x0), value);
    }

    /**
     * @dev ERC20 transfer implementation
     */
    function _transfer(address from, address to, uint256 value) internal {
        require(to != address(0), "ERC20: transfer to the zero address");
        _accounts.transfer(from, to, value);
        emit Transfer(from, to, value);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
     *
     * This is internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an `Approval` event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _accounts.setAllowance(owner, spender, value);
        emit Approval(owner, spender, value);
    }
}
/**
 * @dev Upgradeable coin controller contract.
 */
function increaseAllowance(address spender, uint256 value) public returns (bool) {
    uint256 allowanceValue = _accounts.allowance(msg.sender, spender);
    allowanceValue = allowanceValue.add(value);
    _approve(msg.sender, spender, allowanceValue);
    return true;
}

/**
 * @dev Atomically increases the allowance granted to `spender` by the caller.
 *
 * This is an alternative to `approve` that can be used as a mitigation for
 * problems described in `IERC20.approve`.
 *
 * Emits an `Approval` event indicating the updated allowance.
 */