pragma solidity 0.5.0;

// ----------------------------------------------------------------------------
// ERC Token Standard #20 Interface
// https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC20/ERC20.sol
// https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC20/ERC20Basic.sol
// 
// ----------------------------------------------------------------------------
interface ERC20Interface {
    function totalSupply() external view returns (uint256);
    function balanceOf(address who) external view returns (uint256);
    function allowance(address approver, address spender) external view returns (uint256);
    function transfer(address to, uint256 value) external returns (bool);
    function approve(address spender, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);

    // solhint-disable-next-line no-simple-event-func-name
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed approver, address indexed spender, uint256 value);
}

//
// base contract for all our horizon contracts and tokens
//
contract HorizonContractBase {
    // The owner of the contract, set at contract creation to the creator.
    address public owner;

    constructor() public {
        owner = msg.sender;
    }

    // Contract authorization - only allow the owner to perform certain actions.
    modifier onlyOwner {
        require(msg.sender == owner, "Only the owner can call this function.");
        _;
    }
}

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 *
 * Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/math/SafeMath.sol
 */
library SafeMath {
    /**
     * @dev Multiplies two numbers, throws on overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }
        uint256 c = a * b;
        assert(c / a == b);
        return c;
    }

    /**
    * @dev Integer division of two numbers, truncating the quotient.
    */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // assert(b > 0); // Solidity automatically throws when dividing by 0
        // uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
        return a / b;
    }

    /**
    * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
    */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        assert(b <= a);
        return a - b;
    }

    /**
    * @dev Adds two numbers, throws on overflow.
    */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        assert(c >= a);
        return c;
    }
}

/**
 * ICOToken for the timelessluxurygroup.com by Horizon-Globex.com of Switzerland.
 *
 * An ERC20 standard
 *
 * Author: Horizon Globex GmbH Development Team
 *
 * Dev Notes
 *   NOTE: There is no fallback function as this contract will never contain Ether, only the ICO tokens.
 *   NOTE: There is no approveAndCall/receiveApproval or ERC223 functionality.
 *   NOTE: Coins will never be minted beyond those at contract creation.
 *   NOTE: Zero transfers are allowed - we don't want to break a valid transaction chain.
 *   NOTE: There is no selfDestruct, changeOwner or migration path - this is the only contract.
 */


contract ICOToken is ERC20Interface, HorizonContractBase {
    using SafeMath for uint256;

    // Contract authorization - only allow the official KYC provider to perform certain actions.
    modifier onlyKycProvider {
        require(msg.sender == regulatorApprovedKycProvider, "Only the KYC Provider can call this function.");
        _;
    }
	
	// Contract authorization - only allow the official issuer to perform certain actions.
    modifier onlyIssuer {
        require(msg.sender == issuer, "Only the Issuer can call this function.");
        _;
    }

    // The approved KYC provider that verifies all ICO/TGE Contributors.
    address public regulatorApprovedKycProvider;
    
    // The issuer
    address public issuer;

    // Public identity variables of the token used by ERC20 platforms.
    string public name;
    string public symbol;
    
    // There is no good reason to deviate from 18 decimals, see https://github.com/ethereum/EIPs/issues/724.
    uint8 public decimals = 18;
    
    // The total supply of tokens, set at creation, decreased with burn.
    uint256 public totalSupply_;

    // The supply of tokens, set at creation, to be allocated for the referral bonuses.
    uint256 public rewardPool_;

    // The Initial Coin Offering is finished.
    bool public isIcoComplete;

    // The balances of all accounts.
    mapping (address => uint256) public balances;

    // KYC submission hashes accepted by KYC service provider for AML/KYC review.
    bytes32[] public kycHashes;

    // All users that have passed the external KYC verification checks.
    address[] public kycValidated;

    // Addresses authorized to transfer tokens on an account's behalf.
    mapping (address => mapping (address => uint256)) internal allowanceCollection;

    // Lookup an ICO/TGE Contributor address to see if it was referred by another address (referee => referrer).
    mapping (address => address) public referredBy;

    // Emitted when the Initial Coin Offering phase ends, see closeIco().
    event IcoComplete();

    // Notification when tokens are burned by the owner.
    event Burn(address indexed from, uint256 value);
    
    // Emitted when mint event ocurred
    // added by andrewju
    event Mint(address indexed from, uint256 value);

    // Someone who was referred has purchased tokens, when the bonus is awarded log the details.
    event ReferralRedeemed(address indexed referrer, address indexed referee, uint256 value);

    /**
     * Initialise contract with the 50 million initial supply tokens, allocated to
     * the creator of the contract (the owner).
     */
    constructor(uint256 totalSupply, string memory _name, string memory _symbol, uint256 _rewardPool) public {
		name = _name;
		symbol = _symbol;
        totalSupply_ = totalSupply * 10 ** uint256(decimals);   // Set the total supply of ICO Tokens.
        balances[msg.sender] = totalSupply_;
        rewardPool_ = _rewardPool * 10 ** uint256(decimals);   // Set the total supply of ICO Reward Tokens.
        
        setKycProvider(msg.sender);
        setIssuer(msg.sender);
        
    }

    /**
     * The total number of tokens that exist.
     */
    function totalSupply() public view returns (uint256) {
        return totalSupply_;
    }

    /**
     * The total number of reward pool tokens that remains.
     */
    function rewardPool() public onlyOwner view returns (uint256) {
        return rewardPool_;
    }

    /**
     * Get the number of tokens for a specific account.
     *
     * @param who    The address to get the token balance of.
     */
    function balanceOf(address who) public view returns (uint256 balance) {
        return balances[who];
    }

    /**
     * Get the current allowanceCollection that the approver has allowed 'spender' to spend on their behalf.
     *
     * See also: approve() and transferFrom().
     *
     * @param _approver  The account that owns the tokens.
     * @param _spender   The account that can spend the approver's tokens.
     */
    function allowance(address _approver, address _spender) public view returns (uint256) {
        return allowanceCollection[_approver][_spender];
    }

    /**
     * Add the link between the referrer and who they referred.
     *
     * ---- ICO-Platform Note ----
     * The horizon-globex.com ICO platform offers functionality for referrers to sign-up
     * to refer Contributors. Upon such referred Contributions, Company shall automatically
     * award 1% of our "owner" ICO tokens to the referrer as coded by this Smart Contract.
     *
     * All referrers must successfully complete our ICO KYC review prior to being allowed on-board.
     * -- End ICO-Platform Note --
     *
     * @param referrer  The person doing the referring.
     * @param referee   The person that was referred.
     */
    function refer(address referrer, address referee) public onlyOwner {
        require(referrer != address(0x0), "Referrer cannot be null");
        require(referee != address(0x0), "Referee cannot be null");
        require(!isIcoComplete, "Cannot add new referrals after ICO is complete.");

        referredBy[referee] = referrer;
    }

    /**
     * Transfer tokens from the caller's account to the recipient.
     *
     * @param to    The address of the recipient.
     * @param value The number of tokens to send.
     */
    // solhint-disable-next-line no-simple-event-func-name
    function transfer(address to, uint256 value) public returns (bool) {
        return _transfer(msg.sender, to, value);
    }
	
    /**
     * Transfer pre-approved tokens on behalf of an account.
     *
     * See also: approve() and allowance().
     *
     * @param from  The address of the sender
     * @param to    The address of the recipient
     * @param value The number of tokens to send
     */
    function transferFrom(address from, address to, uint256 value) public returns (bool) {
        require(value <= allowanceCollection[from][msg.sender], "Amount to transfer is greater than allowance.");
		
        allowanceCollection[from][msg.sender] = allowanceCollection[from][msg.sender].sub(value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * Allow another address to spend tokens on your behalf.
     *
     * transferFrom can be called multiple times until the approved balance goes to zero.
     * Subsequent calls to this function overwrite the previous balance.
     * To change from a non-zero value to another non-zero value you must first set the
     * allowance to zero - it is best to use safeApprove when doing this as you will
     * manually have to check for transfers to ensure none happened before the zero allowance
     * was set.
     *
     * @param _spender   The address authorized to spend your tokens.
     * @param _value     The maximum amount of tokens they can spend.
     */
    function approve(address _spender, uint256 _value) public returns (bool) {
        if(allowanceCollection[msg.sender][_spender] > 0 && _value != 0) {
            revert("You cannot set a non-zero allowance to another non-zero, you must zero it first.");
        }

        allowanceCollection[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);

        return true;
    }

    /**
     * Allow another address to spend tokens on your behalf while mitigating a double spend.
     *
     * Subsequent calls to this function overwrite the previous balance.
     * The old value must match the current allowance otherwise this call reverts.
     *
     * @param spender   The address authorized to spend your tokens.
     * @param value     The maximum amount of tokens they can spend.
     * @param oldValue  The current allowance for this spender.
     */
    function safeApprove(address spender, uint256 value, uint256 oldValue) public returns (bool) {
        require(spender != address(0x0), "Cannot approve null address.");
        require(oldValue == allowanceCollection[msg.sender][spender], "The expected old value did not match current allowance.");

        allowanceCollection[msg.sender][spender] = value;
        emit Approval(msg.sender, spender, value);

        return true;
    }

    /**
     * The hash for all Know Your Customer information is calculated outside but stored here.
     * This storage will be cleared once the ICO completes, see closeIco().
     *
     * ---- ICO-Platform Note ----
     * The horizon-globex.com ICO platform's KYC app will register a hash of the Contributors
     * KYC submission on the blockchain. Our Swiss financial-intermediary KYC provider will be 
     * notified of the submission and retrieve the Contributor data for formal review.
     *
     * All Contributors must successfully complete our ICO KYC review prior to being allowed on-board.
     * -- End ICO-Platform Note --
     *
     * @param sha   The hash of the customer data.
    */
    function setKycHash(bytes32 sha) public onlyOwner {
        require(!isIcoComplete, "The ICO phase has ended, you can no longer set KYC hashes.");

        kycHashes.push(sha);
    }

    /**
     * A user has passed KYC verification, store them on the blockchain in the order it happened.
     * This will be cleared once the ICO completes, see closeIco().
     *
     * ---- ICO-Platform Note ----
     * The horizon-globex.com ICO platform's registered KYC provider submits their approval
     * for this Contributor to particpate using the ICO-Platform portal. 
     *
     * Each Contributor will then be sent the Ethereum, Bitcoin and IBAN account numbers to
     * deposit their Approved Contribution in exchange for ICO Tokens.
     * -- End ICO-Platform Note --
     *
     * @param who   The user's address.
     */
    function kycApproved(address who) public onlyKycProvider {
        require(!isIcoComplete, "The ICO phase has ended, you can no longer approve.");
        require(who != address(0x0), "Cannot approve a null address.");

        kycValidated.push(who);
    }

    /**
     * Set the address that has the authority to approve users by KYC.
     *
     * ---- ICO-Platform Note ----
     * The horizon-globex.com ICO platform shall register a fully licensed Swiss KYC
     * provider to assess each potential Contributor for KYC and AML under Swiss law. 
     *
     * -- End ICO-Platform Note --
     *
     * @param who   The address of the KYC provider.
     */
    function setKycProvider(address who) public onlyOwner {
        regulatorApprovedKycProvider = who;
    }
    
        /**
     * Set the issuer address
     *
     * @param who   The address of the issuer.
     */
    function setIssuer(address who) public onlyOwner {
        issuer = who;
    }
    
    
    /**
     * Retrieve the KYC hash from the specified index.
     *
     * @param   index   The index into the array.
     */
    function getKycHash(uint256 index) public view returns (bytes32) {
        return kycHashes[index];
    }

    /**
     * Retrieve the validated KYC address from the specified index.
     *
     * @param   index   The index into the array.
     */
    function getKycApproved(uint256 index) public view returns (address) {
        return kycValidated[index];
    }

    /**
     * When someone referred (the referee) purchases tokens the referrer gets a 1% bonus from the central pool.
     *
     * ---- ICO-Platform Note ----
     * The horizon-globex.com ICO platform's portal shall award referrers as part of the ICO
     * ICO Token issuance procedure as overseen by the Swiss KYC provider. 
     *
     * -- End ICO-Platform Note --
     *
     * @param referee   The referred account who just purchased some tokens.
     * @param referrer  The account that referred the one purchasing tokens.
     * @param value     The number of tokens purchased by the referee.
    */
    function awardReferralBonus(address referee, address referrer, uint256 value) private {
        uint256 bonus = value / 100;
        balances[owner] = balances[owner].sub(bonus);
        balances[referrer] = balances[referrer].add(bonus);
        rewardPool_ -= bonus;
        emit ReferralRedeemed(referee, referrer, bonus);
    }

    /**
     * During the ICO phase the owner will allocate tokens once KYC completes and funds are deposited.
     *
     * ---- ICO-Platform Note ----
     * The horizon-globex.com ICO platform's portal shall issue ICO Token to Contributors on receipt of 
     * the Approved Contribution funds at the KYC providers Escrow account/wallets.
     * Only after ICO Tokens are issued to the Contributor can the Swiss KYC provider allow the transfer
     * of funds from their Escrow to Company.
     *
     * -- End ICO-Platform Note --
     *
     * @param to       The recipient of the tokens.
     * @param value    The number of tokens to send.
     */
    function icoTransfer(address to, uint256 value) public onlyOwner {
        require(!isIcoComplete, "ICO is complete, use transfer().");

        // If an attempt is made to transfer more tokens than owned, transfer the remainder.
        uint256 toTransfer = (value > (balances[msg.sender] - rewardPool_ )) ? (balances[msg.sender] - rewardPool_) : value;
        
        _transfer(msg.sender, to, toTransfer);

        // Handle a referred account receiving tokens.
        address referrer = referredBy[to];
        if(referrer != address(0x0)) {
            referredBy[to] = address(0x0);
            awardReferralBonus(to, referrer, toTransfer);
        }
    }

    /**
     * End the ICO phase in accordance with KYC procedures and clean up.
     *
     * ---- ICO-Platform Note ----
     * The horizon-globex.com ICO platform's portal shall halt the ICO at the end of the 
     * Contribution Period, as defined in the ICO Terms and Conditions at timelessluxurygroup.com.
     *
     * -- End ICO-Platform Note --
     */
    function closeIco() public onlyOwner {
        require(!isIcoComplete, "The ICO phase has already ended, you cannot close it again.");
        require((balances[owner] - rewardPool_) == 0, "Cannot close ICO when a balance remains in the owner account.");

        isIcoComplete = true;
        delete kycHashes;
        delete kycValidated;

        emit IcoComplete();
    }
	
    /**
     * Internal transfer, can only be called by this contract
     *
     * @param from     The sender of the tokens.
     * @param to       The recipient of the tokens.
     * @param value    The number of tokens to send.
     */
    function _transfer(address from, address to, uint256 value) internal returns (bool) {
        require(from != address(0x0), "Cannot send tokens from null address");
        require(to != address(0x0), "Cannot transfer tokens to null");
        require(balances[from] >= value, "Insufficient funds");

        // Quick exit for zero, but allow it in case this transfer is part of a chain.
        if(value == 0)
            return true;
		
        // Perform the transfer.
        balances[from] = balances[from].sub(value);
        balances[to] = balances[to].add(value);
		
        // Any tokens sent to to owner are implicitly burned.
        if (to == owner) {
            _burn(to, value);
        }

        return true;
    }
    
    /**
     * Permanently mint tokens to increase the totalSupply_.
     *
     * @param value            The number of tokens to mint.
     */
    function mint(uint256 value) public onlyIssuer {
        require(value > 0, "Tokens to mint must be greater than zero");
        balances[owner] = balances[owner].add(value);
        totalSupply_ = totalSupply_.add(value);
        
        emit Mint(msg.sender, value);
        
    }
    
    /**
     * Permanently destroy tokens from totalSupply_.
     *
     * @param value            The number of tokens to burn.
     */
    function burn(uint256 value) public onlyIssuer {
        _burn(owner, value);
    }

    /**
     * Permanently destroy tokens belonging to a user.
     *
     * @param addressToBurn    The owner of the tokens to burn.
     * @param value            The number of tokens to burn.
     */
    function _burn(address addressToBurn, uint256 value) private returns (bool success) {
        require(value > 0, "Tokens to burn must be greater than zero");
        require(balances[addressToBurn] >= value, "Tokens to burn exceeds balance");

        balances[addressToBurn] = balances[addressToBurn].sub(value);
        totalSupply_ = totalSupply_.sub(value);

        emit Burn(msg.sender, value);

        return true;
    }
}