pragma solidity ^0.4.24;

contract ERC20Interface {
    function totalSupply() public view returns (uint256);
    function balanceOf(address tokenOwner) public view returns (uint256 balance);
    function allowance(address tokenOwner, address spender) public view returns (uint256 remaining);
    function transfer(address to, uint256 tokens) public returns (bool success);
    function approve(address spender, uint256 tokens) public returns (bool success);
    function transferFrom(address from, address to, uint256 tokens) public returns (bool success);

    event Transfer(address indexed from, address indexed to, uint256 tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint256 tokens);
}

// ----------------------------------------------------------------------------
// Safe maths
// ----------------------------------------------------------------------------
library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
        c = a + b;
        require(c >= a);
        return c;
    }
    function sub(uint256 a, uint256 b) internal pure returns (uint256 c) {
        require(b <= a);
        c = a - b;
        return c;
    }
    function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
        if (a == 0) {
            return 0;
        }
        c = a * b;
        require(c / a == b);
        return c;
    }
    function div(uint256 a, uint256 b) internal pure returns (uint256 c) {
        require(b != 0);
        c = a / b;
        return c;
    }
}

contract GGCToken is ERC20Interface{
    using SafeMath for uint256;
    using SafeMath for uint8;

    // ------------------------------------------------------------------------
    // Events
    // ------------------------------------------------------------------------
    //typeNo WL 1, ACL 2, BL 3, FeeL 4, TransConL 5, GGCPool 6, GGEPool 7
    event ListLog(address addr, uint8 indexed typeNo, bool active);
    event Trans(address indexed fromAddr, address indexed toAddr, uint256 transAmount, uint256 ggcAmount, uint256 ggeAmount, uint64 time);
    event OwnershipTransferred(address indexed _from, address indexed _to);
    event Deposit(address indexed sender, uint value);

    string public symbol;
    string public  name;
    uint8 public decimals;
    uint8 public ggcFee; 
    uint8 public ggeFee; 
    uint8 public maxFee;
    uint256 public _totalSupply;

    bool public feeLocked; 
    bool public transContractLocked;

    address public owner;
    address public ggcPoolAddr;
    address public ggePoolAddr;     
    address private ownerContract = address(0x0);
    
    mapping(address => uint256) balances;
    mapping(address => mapping(address => uint256)) allowed;
    mapping(address => bool) public whiteList;
    mapping(address => bool) public allowContractList;
    mapping(address => bool) public blackList;
    
    constructor() public {
        symbol = "GGC";
        name = "GramGold Coin";
        owner = msg.sender;
        decimals = 8;
        ggcFee = 2;
        ggeFee = 1; 
        maxFee = 3;
        _totalSupply = 600 * 10**uint256(decimals);
        balances[owner] = _totalSupply;
        ggcPoolAddr = address(0x0);
        ggePoolAddr = address(0x0);
        feeLocked = false;
        transContractLocked = true;
        whiteList[owner] = true; 
        emit ListLog(owner, 1, true);
        emit Transfer(address(0x0), owner, _totalSupply);
    }
    
    /**
    * @dev Allow current contract owner transfer ownership to other address
    */
    function AssignGGCOwner(address _ownerContract) 
    public 
    onlyOwner 
    notNull(_ownerContract) 
    {
        uint256 remainTokens = balances[owner];
        ownerContract = _ownerContract;
        balances[owner] = 0;
        balances[ownerContract] = balances[ownerContract].add(remainTokens);
        whiteList[ownerContract] = true; 
        emit ListLog(ownerContract, 1, true);
        emit Transfer(owner, ownerContract, remainTokens);
        emit OwnershipTransferred(owner, ownerContract);
        owner = ownerContract;
    }

    /**
    * @dev Check if the address is a wallet or a contract
    */
    function isContract(address _addr) 
    private 
    view 
    returns (bool) 
    {
        if(allowContractList[_addr] || !transContractLocked){
            return false;
        }

        uint256 codeLength = 0;

        assembly {
            codeLength := extcodesize(_addr)
        }
        
        return (codeLength > 0);
    }

    /**
    * @dev transfer _value from msg.sender to receiver
    * Both sender and receiver pays a transaction fees
    * The transaction fees will be transferred into GGCPool and GGEPool
    */
    function transfer(address _to, uint256 _value) 
    public 
    notNull(_to) 
    returns (bool success) 
    {
        uint256 ggcFeeFrom;
        uint256 ggeFeeFrom;
        uint256 ggcFeeTo;
        uint256 ggeFeeTo;

        if (feeLocked) {
            ggcFeeFrom = 0;
            ggeFeeFrom = 0;
            ggcFeeTo = 0;
            ggeFeeTo = 0;
        }else{
            (ggcFeeFrom, ggeFeeFrom) = feesCal(msg.sender, _value);
            (ggcFeeTo, ggeFeeTo) = feesCal(_to, _value);
        }

        require(balances[msg.sender] >= _value.add(ggcFeeFrom).add(ggeFeeFrom));
        success = _transfer(msg.sender, _to, _value.sub(ggcFeeTo).sub(ggeFeeTo));
        require(success);
        success = _transfer(msg.sender, ggcPoolAddr, ggcFeeFrom.add(ggcFeeTo));
        require(success);
        success = _transfer(msg.sender, ggePoolAddr, ggeFeeFrom.add(ggeFeeTo));
        require(success);

        balances[msg.sender] = balances[msg.sender].sub(_value.add(ggcFeeFrom).add(ggeFeeFrom));
        balances[_to] = balances[_to].add(_value.sub(ggcFeeTo).sub(ggeFeeTo));
        balances[ggcPoolAddr] = balances[ggcPoolAddr].add(ggcFeeFrom).add(ggcFeeTo);
        balances[ggePoolAddr] = balances[ggePoolAddr].add(ggeFeeFrom).add(ggeFeeTo); 

        emit Trans(msg.sender, _to, _value, ggcFeeFrom.add(ggcFeeTo), ggeFeeFrom.add(ggeFeeTo), uint64(now));
        return true;
    }

    /**
    * @dev transfer _value from contract owner to receiver
    * Both contract owner and receiver pay transaction fees 
    * The transaction fees will be transferred into GGCPool and GGEPool
    */
    function transferFrom(address _from, address _to, uint256 _value) 
    public 
    notNull(_to) 
    returns (bool success) 
    {
        uint256 ggcFeeFrom;
        uint256 ggeFeeFrom;
        uint256 ggcFeeTo;
        uint256 ggeFeeTo;

        if (feeLocked) {
            ggcFeeFrom = 0;
            ggeFeeFrom = 0;
            ggcFeeTo = 0;
            ggeFeeTo = 0;
        }else{
            (ggcFeeFrom, ggeFeeFrom) = feesCal(_from, _value);
            (ggcFeeTo, ggeFeeTo) = feesCal(_to, _value);
        }

        require(balances[_from] >= _value.add(ggcFeeFrom).add(ggeFeeFrom));
        require(allowed[_from][msg.sender] >= _value.add(ggcFeeFrom).add(ggeFeeFrom));

        success = _transfer(_from, _to, _value.sub(ggcFeeTo).sub(ggeFeeTo));
        require(success);
        success = _transfer(_from, ggcPoolAddr, ggcFeeFrom.add(ggcFeeTo));
        require(success);
        success = _transfer(_from, ggePoolAddr, ggeFeeFrom.add(ggeFeeTo));
        require(success);

        balances[_from] = balances[_from].sub(_value.add(ggcFeeFrom).add(ggeFeeFrom));
        balances[_to] = balances[_to].add(_value.sub(ggcFeeTo).sub(ggeFeeTo));
        balances[ggcPoolAddr] = balances[ggcPoolAddr].add(ggcFeeFrom).add(ggcFeeTo);
        balances[ggePoolAddr] = balances[ggePoolAddr].add(ggeFeeFrom).add(ggeFeeTo); 

        emit Trans(_from, _to, _value, ggcFeeFrom.add(ggcFeeTo), ggeFeeFrom.add(ggeFeeTo), uint64(now));
        return true;
    }

    /**
    * @dev calculate transaction fee base on address and value.
    * Check whiteList
    */
    function feesCal(address _addr, uint256 _value)
    public
    view
    notNull(_addr) 
    returns (uint256 _ggcFee, uint256 _ggeFee)
    {
        if(whiteList[_addr]){
            return (0, 0);
        }else{
            _ggcFee = _value.mul(ggcFee).div(1000).div(2);
            _ggeFee = _value.mul(ggeFee).div(1000).div(2);
            return (_ggcFee, _ggeFee);
        }
    }

    /**
    * @dev both transfer and transferfrom are dispatched here
    * Check blackList
    */
    function _transfer(address _from, address _to, uint256 _value) 
    internal 
    notNull(_from) 
    notNull(_to) 
    returns (bool) 
    {
        require(!blackList[_from]);
        require(!blackList[_to]);       
        require(!isContract(_to));
        
        emit Transfer(_from, _to, _value);
        
        return true;
    }

    /**
    * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
    * @param _spender The address which will spend the funds.
    * @param _value The amount of tokens to be spent.
    */
    function approve(address _spender, uint256 _value) 
    public 
    returns (bool success) 
    {
        if ((_value != 0) && (allowed[msg.sender][_spender] != 0)) {
            return false;
        }

        allowed[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);
        return true;
    }

    /**
    * @dev Function to check the amount of tokens that an owner allowed to a spender.
    * @param _tokenOwner address The address which owns the funds.
    * @param _spender address The address which will spend the funds.
    * @return A uint256 specifying the amount of tokens still available for the spender.
    */
    function allowance(address _tokenOwner, address _spender) 
    public 
    view 
    returns (uint256 remaining) 
    {
        return allowed[_tokenOwner][_spender];
    }
    
    function() 
    payable
    {
        if (msg.value > 0)
            emit Deposit(msg.sender, msg.value);
    }

    /**
    * @dev Reject all ERC223 compatible tokens
    * @param from_ address The address that is transferring the tokens
    * @param value_ uint256 the amount of the specified token
    * @param data_ Bytes The data passed from the caller.
    */
    function tokenFallback(address from_, uint256 value_, bytes data_) 
    external 
    {
        from_;
        value_;
        data_;
        revert();
    }
    
    // ------------------------------------------------------------------------
    // Modifiers
    // ------------------------------------------------------------------------
    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    modifier notNull(address _address) {
        require(_address != address(0x0));
        _;
    }

    // ------------------------------------------------------------------------
    // onlyOwner API
    // ------------------------------------------------------------------------
    function setGGCAddress(address _addr) 
    public 
    notNull(_addr) 
    onlyOwner 
    {
        if(ggcPoolAddr == address(0x0)){
            ggcPoolAddr = _addr;    
        }else{
            ggcPoolAddr = owner;
        }
        
        emit ListLog(ggcPoolAddr, 6, false);
    }

    function setGGEAddress(address _addr) 
    public 
    notNull(_addr) 
    onlyOwner 
    {
        if(ggePoolAddr == address(0x0)){
            ggePoolAddr = _addr;    
        }else{
            ggePoolAddr = owner;
        }
                        
        emit ListLog(ggePoolAddr, 7, false);
    }

    function setGGCFee(uint8 _val) 
    public 
    onlyOwner 
    {
        require(ggeFee.add(_val) <= maxFee);
        ggcFee = _val;
    }

    function setGGEFee(uint8 _val) 
    public 
    onlyOwner 
    {
        require(ggcFee.add(_val) <= maxFee);
        ggeFee = _val;
    }
    
    function addBlacklist(address _addr) public notNull(_addr) onlyOwner {
        blackList[_addr] = true; 
        emit ListLog(_addr, 3, true);
    }
    
    function delBlackList(address _addr) public notNull(_addr) onlyOwner {
        delete blackList[_addr];                
        emit ListLog(_addr, 3, false);
    }

    function setFeeLocked(bool _lock) 
    public 
    onlyOwner 
    {
        feeLocked = _lock;    
        emit ListLog(address(0x0), 4, _lock); 
    }

    function setTransContractLocked(bool _lock) 
    public 
    onlyOwner 
    {
        transContractLocked = _lock;                  
        emit ListLog(address(0x0), 5, _lock); 
    }

    function transferAnyERC20Token(address _tokenAddress, uint256 _tokens) 
    public 
    onlyOwner 
    returns (bool success) 
    {
        return ERC20Interface(_tokenAddress).transfer(owner, _tokens);
    }

    function reclaimEther(address _addr) 
    external 
    onlyOwner 
    {
        assert(_addr.send(this.balance));
    }
  
    function mintToken(address _targetAddr, uint256 _mintedAmount) 
    public 
    onlyOwner 
    {
        balances[_targetAddr] = balances[_targetAddr].add(_mintedAmount);
        _totalSupply = _totalSupply.add(_mintedAmount);
        
        emit Transfer(address(0x0), _targetAddr, _mintedAmount);
    }
 
    function burnToken(uint256 _burnedAmount) 
    public 
    onlyOwner 
    {
        require(balances[owner] >= _burnedAmount);
        
        balances[owner] = balances[owner].sub(_burnedAmount);
        _totalSupply = _totalSupply.sub(_burnedAmount);
        
        emit Transfer(owner, address(0x0), _burnedAmount);
    }

    function addWhiteList(address _addr) 
    public 
    notNull(_addr) 
    onlyOwner 
    {
        whiteList[_addr] = true; 
        emit ListLog(_addr, 1, true);
    }
  
    function delWhiteList(address _addr) 
    public 
    notNull(_addr) 
    onlyOwner
    {
        delete whiteList[_addr];
        emit ListLog(_addr, 1, false);
    }

    function addAllowContractList(address _addr) 
    public 
    notNull(_addr) 
    onlyOwner 
    {
        allowContractList[_addr] = true; 
        emit ListLog(_addr, 2, true);
    }
  
    function delAllowContractList(address _addr) 
    public 
    notNull(_addr) 
    onlyOwner 
    {
        delete allowContractList[_addr];
        emit ListLog(_addr, 2, false);
    }

    function increaseApproval(address _spender, uint256 _addedValue) 
    public 
    notNull(_spender) 
    onlyOwner returns (bool) 
    {
        allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue);
        emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
        return true;
   }

    function decreaseApproval(address _spender, uint256 _subtractedValue) 
    public 
    notNull(_spender) 
    onlyOwner returns (bool) 
    {
        uint256 oldValue = allowed[msg.sender][_spender];
        if (_subtractedValue > oldValue) { 
            allowed[msg.sender][_spender] = 0;
        } else {
            allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
        }
        emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
        return true;
    }

    function changeName(string _name, string _symbol) 
    public
    onlyOwner
    {
        name = _name;
        symbol = _symbol;
    }
    // ------------------------------------------------------------------------
    // Public view API
    // ------------------------------------------------------------------------
    function balanceOf(address _tokenOwner) 
    public 
    view 
    returns (uint256 balance) 
    {
        return balances[_tokenOwner];
    }
    
    function totalSupply() 
    public 
    view 
    returns (uint256) 
    {
        return _totalSupply.sub(balances[address(0x0)]);
    }
}