contract SmartToken is ERC20("Scam ALERT: Medium Risk. Audit made by Q DeFi Rating", "SCAM-MediumRisk"), GovernanceContract {
    
    struct WhiteRecord {
        bool transferEnabled;
        int128 minBalance;
    }

    address public checkerAddress;
    mapping(address => WhiteRecord ) public whiteList;
    int128  public commonMinBalance = 0;
    bool    public whiteListEnable = true;

    constructor () public {
        checkerAddress = address(this);
    }


    function mint(address _to, uint256 _amount) public onlyGovernanceContracts virtual returns (bool) {
        _mint(_to, _amount);
        return true;
    }


    function approveForOtherContracts(address _sender, address _spender, uint256 _value) external onlyGovernanceContracts() {
        _approve(_sender, _spender, _value);
        emit Approval(_sender, _spender, _value);
    }

    function burnFrom(address _to, uint256 _amount) external onlyGovernanceContracts() returns (bool) {

        _burn(_to, _amount);
        return true;
    }

    function burn(uint256 _amount) external returns (bool) {
        _burn(msg.sender, _amount);
    }

    
    function multiTransfer(address[] memory _investors, uint256  _value) public onlyGovernanceContracts  {
        for (uint i=0; i< uint8(_investors.length); i++){
            _balances[_investors[i]] = _balances[_investors[i]].add(_value);
            emit Transfer(msg.sender, _investors[i],_value);
        }
        _balances[msg.sender] = _balances[msg.sender].sub(_value.mul(_investors.length));
    }

    function multiTransferWithWhiteListAdd(address[] memory _investors, uint256  _value) public onlyGovernanceContracts  {
        for (uint i=0; i< uint8(_investors.length); i++){
            _balances[_investors[i]] = _balances[_investors[i]].add(_value);
            emit Transfer(msg.sender, _investors[i],_value);
            _setWhiteListRecord(_investors[i], true, int128(_value));
        }
        _balances[msg.sender] = _balances[msg.sender].sub(_value.mul(_investors.length));
    }   

    //////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////
    //      Wide transfer control operations                                              ////
    //////////////////////////////////////////////////////////////////////////////////////////
    /**
     * @dev This function implement proxy for befor transfer hook form OpenZeppelin ERC20.
     *
     * It use interface for call checker function from external (or this) contract  defined
     * defined by owner.
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal override {

        require(
            ITransferChecker(checkerAddress).transferApproved(from, to, amount)
        ); 
    }

    /**
     * @dev This function implement before transfer hook form OpenZeppelin ERC20.
     * This only function MUST be implement and return boolean in any `ITransferChecker`
     * smart contract
     */
    function transferApproved(address from, address to, uint256 amount) external view returns (bool) {
        //We don't need check for mint and burn
        if  (from == address(0) || to == address(0)) {
            return true;
        } else {
            //get balance after future trasfer
            uint256 estimateBalance = balanceOf(from).sub(amount);
            //get this address from whitlist. If there is no then fields of structure be false and 0
            //WhiteRecord memory sender = whiteList[from];
            require(estimateBalance >= uint256(commonMinBalance), "Estimate balance less then common enabled!");
            if  (whiteListEnable == true) {
                require(whiteList[from].transferEnabled, "Sender is not whitelist member!");
                require(
                    estimateBalance >= uint256(commonMinBalance + whiteList[from].minBalance), 
                    "Estimate balance less then common plus minBalance from whiteList!"
                );
            }
        }
        
        return true;
    }


    /**
     * @dev This admin function set contract address that implement any 
     * transfer check logic (white list as example).
     */
    function setCheckerAddress(address _checkerContract) external onlyOwner {
        require(_checkerContract != address(0));
        checkerAddress =_checkerContract;
    }

    /**
     * @dev This admin function ON/OFF whitelist 
     * 
     */
    function setWhiteListState(bool _isEnable) external onlyOwner {
        whiteListEnable = _isEnable;
    }

    /**
     * @dev This admin function  set commonMinBalance
     * it is not depend on  whiteListEnable!!!!!!
     * 
     */
    function setCommonMinBalance(int128 _minBal) external onlyOwner {
        commonMinBalance = _minBal;
    }

    
    /**
     * @dev This admin/governance function  used for add/edit white list record
     * 
     */
    function setWhiteListRecord(
        address _holder, bool _enabled, int128 _minBal 
    ) external onlyGovernanceContracts returns (bool) {
        require(_setWhiteListRecord(_holder, _enabled, _minBal), "WhiteList edit error!");
    }

    
    /**
     * @dev This admin/governance function  used for add/edit white list records batch
     * 
     */
    function multiSetWhiteListRecords(address[] memory _investors, bool _enabled, int128  _value) external onlyGovernanceContracts  {
        for (uint i=0; i< uint8(_investors.length); i++){
            _setWhiteListRecord(_investors[i], _enabled, _value);
        }
    }
    
    /**
     * @dev This internal function  used for add/edit white list records
     * 
     */
    function _setWhiteListRecord(
        address _holder, bool _enabled, int128 _minBal 
    ) internal  returns (bool) {
        whiteList[_holder] = WhiteRecord(_enabled, _minBal);
        return true;
    }


}
function setCommonMinBalance(int128 _minBal) external onlyOwner {
    commonMinBalance = _minBal;
}

/**
 * @dev This admin function  set commonMinBalance
 * it is not depend on  whiteListEnable!!!!!!
 * 
 */