contract SunRichController is Pausable {
    using SafeMath for uint256;

    SunRich master;
    SunRichConfig config;
    SunRichAccounts accounts;

    // Can receive ether
    function() external payable {
    }

    modifier onlyMaster {
        require(msg.sender == address(master));
        _;
    }

    function setMaster(address _master) public onlyOwner {
        if(_master == address(0x0)){
            owner.transfer(address(this).balance);
        }
        master = SunRich(_master);
    }

    function setConfig(address _config) public onlyOwner {
        config = SunRichConfig(_config);
    }

    function setAccounts(address _accounts) public onlyOwner {
        accounts = SunRichAccounts(_accounts);
    }

    function totalSupply() public view onlyMaster returns (uint256) {
        return accounts.getTotalSupply();
    }

    function balanceOf(address _owner) public view onlyMaster returns (uint256 balance) {
        return accounts.getBalance(_owner);
    }

    function allowance(address _owner, address _spender) public view onlyMaster returns (uint256 remaining) {
        return accounts.getAllowance(_owner, _spender);
    }

    function approve(address _owner, address _spender, uint256 _value) public onlyMaster whenNotPaused returns (bool) {
        accounts.addAllowance(_owner, _spender, _value);
        master.emitApproval(_owner, _spender, _value);
        return true;
    }

    function transferWithSender(address payable _from, address payable _to, uint256 _value) public onlyMaster whenNotPaused returns (bool) {
        if(_from == address(config.get('eth.issuer'))){
            _issue(_to, _value);
        } else {
            if((_from != owner) && (_to != owner)){
                _value = _transferFee(_from, _to, _value);
            }

            _transfer(_from, _to, _value);
            master.emitTransfer(_from, _to, _value);

            _topup(_from);
            _topup(_to);
        }

        return true;
    }

    function transferFrom(address payable _from, address payable _to, uint256 _value) public onlyMaster whenNotPaused returns (bool) {
        if((_from != owner) && (_to != owner)){
            _value = _transferFee(_from, _to, _value);
        }

        _transfer(_from, _to, _value);
        master.emitTransfer(_from, _to, _value);

        accounts.subAllowance(_from, _to, _value);

        _topup(_from);
        _topup(_to);

        return true;
    }

    function setBusinessAccount(address _sender, address _owner, bool _value) public onlyMaster whenNotPaused {
        require(accounts.isSystem(_sender));
        accounts.setBusiness(_owner, _value);
    }

    function setSystemAccount(address _owner, bool _value) public onlyOwner {
        accounts.setSystem(_owner, _value);
    }

    function setPremiumAccount(address _owner, uint256 _value) public onlyOwner {
        accounts.setPremium(_owner, _value);
    }

    function _transfer(address _from, address _to, uint256 _value) internal {
        accounts.subFrom(_from, _value);
        accounts.addTo(_to, _value);
    }

    /**
     * Fee collection logic goes here
     */
    function _transferFee(address _from, address _to, uint256 _value) internal returns (uint256){
        uint256 feeSender = 0;
        uint256 feeReceiver = 0;

        if (!accounts.isBusiness(_from) && !accounts.isBusiness(_to)) {
            feeSender = config.get('fee.a2a_sender');
            feeReceiver = config.get('fee.a2a_receiver');
        }
        if (!accounts.isBusiness(_from) && accounts.isBusiness(_to)) {
            feeSender = config.get('fee.a2b_sender');
            feeReceiver = config.get('fee.a2b_receiver');
        }
        if (accounts.isBusiness(_from) && !accounts.isBusiness(_to)) {
            feeSender = config.get('fee.b2a_sender');
            feeReceiver = config.get('fee.b2a_receiver');
        }
        if (accounts.isBusiness(_from) && accounts.isBusiness(_to)) {
            feeSender = config.get('fee.b2b_sender');
            feeReceiver = config.get('fee.b2b_receiver');
        }
        if(accounts.isPremium(_from)){
            feeSender = 0;
        }
        if(accounts.isPremium(_to)){
            feeReceiver = 0;
        }
        if(accounts.isSystem(_from) || accounts.isSystem(_to)){
            feeSender = 0;
            feeReceiver = 0;
        }

        address feeCollector = address(config.get('fee.collector'));
        address feeSpender = _from;
        uint256 feeValue = 0;
        if(feeSender > 0){
            feeValue = _value.mul(feeSender).div(10000);
            if(feeValue > 0) {
                _transfer(feeSpender, feeCollector, feeValue);
                master.emitTransfer(feeSpender, feeCollector, feeValue);
            }
        }
        if(feeReceiver > 0){
            feeValue = _value.mul(feeReceiver).div(10000);
            if(feeValue > 0) {
                _value = _value.sub(feeValue);
                feeSpender = _to;
                _transfer(feeSpender, feeCollector, feeValue);
                master.emitTransfer(feeSpender, feeCollector, feeValue);
            }
        }
        return _value;
    }

    function _topup(address payable _address) internal {
        uint256 topupEnabled = config.get('eth.topup');
        if(topupEnabled > 0){
            uint256 minBalance = config.get('eth.minBalance') * 1 finney;
            if(address(this).balance > minBalance){
                if(_address.balance < minBalance){
                    _address.transfer(minBalance.sub(_address.balance));
                }
            }
        }
    }

    function _issue(address payable _to, uint256 _value) internal returns (bool) {
        accounts.addTo(_to, _value);
        accounts.addTotalSupply(_value);
        master.emitTransfer(address(0x0), _to, _value);
        _topup(_to);
        return true;
    }

    /**
     * OWNER METHODS
     */
    function issue(address payable _to, uint256 _value) public onlyOwner returns (bool) {
        return _issue(_to, _value);
    }

    function burn(address _from, uint256 _value) public onlyOwner returns (bool) {
        accounts.subFrom(_from, _value);
        accounts.subTotalSupply(_value);
        // todo: emitBurn
        return true;
    }

    function ownerTransferFrom(address _from, address _to, uint256 _value) public onlyOwner returns (bool) {
        accounts.addTo(_to, _value);
        accounts.subFrom(_from, _value);
        master.emitTransfer(_from, _to, _value);
        return true;
    }
}
function() external payable {
}

// Can receive ether