pragma solidity 0.5.0;

//
// 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.");
        _;
    }
}

// ----------------------------------------------------------------------------
// 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);
}

/**
 * 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;
    }
}

/**
 * @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;
    }
}

// ----------------------------------------------------------------------------
// TradeToken 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 TokenInterface {
    function hold(address who, uint256 quantity) external returns(bool);
}

/**
 * A version of the Regulation D contract (https://www.investopedia.com/terms/r/regulationd.asp) with the
 * added role of Transfer Agent to perform specialised actions.
 *
 * Part of the timelessluxurygroup.com ICO by Horizon-Globex.com of Switzerland.
 *
 * Author: Horizon Globex GmbH Development Team
 */
contract RegD is HorizonContractBase {
    using SafeMath for uint256;

    /**
     * The details of the tokens bought.
     */
    struct Holding {
        // The number of tokens purchased.
        uint256 quantity;

        // The date and time when the tokens are no longer restricted.
        uint256 releaseDate;

        // Whether the holder is an affiliate of the company or not.
        bool isAffiliate;
    }

    // Restrict functionality to the creator (owner) of the contract - the token issuer.
    modifier onlyIssuer {
        require(msg.sender == owner, "You must be issuer/owner to execute this function.");
        _;
    }

    // Restrict functionaly to the official Transfer Agent.
    modifier onlyTransferAgent {
        require(msg.sender == transferAgent, "You must be the Transfer Agent to execute this function.");
        _;
    }

    // The collection of all held tokens by user.
    mapping(address => Holding) public heldTokens;

    // The ICO contract, where all tokens this contract holds originate from.
    address public icoContract;

    // The ERC20 Token contract where tokens past their holding period are released to.
    address public tokenContract;

    // The authorised Transfer Agent who performs specialist actions on this contract.
    address public transferAgent;

    // Number of seconds a holding is held for before it can be released.
    uint256 public expiry = 0;

    // Emitted when someone subject to Regulation D buys tokens and they are held here.
    event TokensHeld(address indexed who, uint256 tokens, uint256 releaseDate);

    // Emitted when the tokens have passed their release date and have been returned to the original owner.
    event TokensReleased(address indexed who, uint256 tokens);

    // The Transfer Agent moved tokens from an address to a new wallet, for escheatment obligations.
    event TokensTransferred(address indexed from, address indexed to, uint256 tokens);

    // The Transfer Agent was unable to verify a token holder and needed to push out the release date.
    event ReleaseDateChanged(address who, uint256 oldReleaseDate, uint256 newReleaseDate);

    // Extra restrictions apply to company affiliates, notify when the status of an address changes.
    event AffiliateStatusChanged(address who, bool isAffiliate);

    /**
     * @notice Create this contract and assign the ICO contract where the tokens originate from.
     *
     * @param icoContract_      The address of the ICO contract.
     * @param expiry_           The number of seconds after holding before the tokens can be released.
     */
    constructor(address icoContract_, uint256 expiry_) public {
        icoContract = icoContract_;
        expiry = expiry_;
    }

    /**
     * @notice Set the address of the contract where tokens are released to after the holding period.
     *
     * @param tokenContract_    The contract address.
     */
    function setTokenContract(address tokenContract_) public onlyIssuer {
        tokenContract = tokenContract_;
    }

    /**
     * @notice Set the address of the Transfer Agent.
     *
     * @param who   The wallet id of the Transfer Agent.
     */
    function setTransferAgent(address who) public onlyIssuer {
        transferAgent = who;
    }

    /**
     * @notice Change the expiry for subsequent holdings, existing holdings are not affected.
     *
     * @param expiry_   The number of seconds after holding before the tokens can be released.
     */
    function setExpiry(uint256 expiry_) public onlyIssuer {
        expiry = expiry_;
    }

    /**
     * @notice Keep a US Citizen's tokens for one year.
     *
     * @param who           The wallet of the US Citizen.
     * @param quantity      The number of tokens to store.
     */
    function hold(address who, uint256 quantity) public onlyIssuer {
        require(who != address(0x0), "The null address cannot own tokens.");
        require(quantity != 0, "Quantity must be greater than zero.");
        require(!isExistingHolding(who), "Cannot overwrite an existing holding, use a new wallet.");

        // Create the holding for the customer who will get these tokens once custody ends.
        Holding memory holding = Holding(quantity, block.timestamp+expiry, false);
        heldTokens[who] = holding;
        emit TokensHeld(who, holding.quantity, holding.releaseDate);
    }
	
    /**
     * @notice Hold tokens post-ICO with a variable release date on those tokens.
     *
     * @param who           The wallet of the US Citizen.
     * @param quantity      The number of tokens to store.
	 * @param addedTime		The number of seconds to add to the current date to calculate the release date.
     */
    function postIcoHold(address who, uint256 quantity, uint256 addedTime) public onlyTransferAgent {
        require(who != address(0x0), "The null address cannot own tokens.");
        require(quantity != 0, "Quantity must be greater than zero.");
        require(!isExistingHolding(who), "Cannot overwrite an existing holding, use a new wallet.");

        bool res = ERC20Interface(icoContract).transferFrom(who, address(this), quantity);
        require(res, "Unable to complete Post-ICO Custody, token contract transfer failed.");
        if(res) {
            Holding memory holding = Holding(quantity, block.timestamp+addedTime, false);
            heldTokens[who] = holding;
            emit TokensHeld(who, holding.quantity, holding.releaseDate);
        }
    }

    /**
    * @notice Check if a user's holding are eligible for release.
    *
    * @param who        The user to check the holding of.
    * @return           True if can be released, false if not.
    */
    function canRelease(address who) public view returns (bool) {
        Holding memory holding = heldTokens[who];
        if(holding.releaseDate == 0 || holding.quantity == 0)
            return false;

        return block.timestamp > holding.releaseDate;
    }

    /**
     * @notice Release the tokens once the holding period expires, transferring them back to the ERC20 contract to the holder.
     *
     * NOTE: This function preserves the isAffiliate flag of the holder.
     *
     * @param who       The owner of the tokens.
     * @return          True on successful release, false on error.
     */
    function release(address who) public onlyTransferAgent returns (bool) {
        require(tokenContract != address(0x0), "ERC20 Token contract is null, nowhere to release to.");
        Holding memory holding = heldTokens[who];
        require(!holding.isAffiliate, "To release tokens for an affiliate use partialRelease().");

        if(block.timestamp > holding.releaseDate) {
            // Transfer the tokens from this contract's ownership to the original owner.
            bool res = TokenInterface(tokenContract).hold(who, holding.quantity);
            if(res) {
                heldTokens[who] = Holding(0, 0, holding.isAffiliate);
                emit TokensReleased(who, holding.quantity);
                return true;
            }
        }

        return false;
    }
	
    /**
     * @notice Release some of an affiliate's tokens to a broker/trading wallet.
     *
     * @param who       		The owner of the tokens.
	 * @param tradingWallet		The broker/trader receiving the tokens.
	 * @param amount 			The number of tokens to release to the trading wallet.
     */
    function partialRelease(address who, address tradingWallet, uint256 amount) public onlyTransferAgent returns (bool) {
        require(tokenContract != address(0x0), "ERC20 Token contract is null, nowhere to release to.");
        require(tradingWallet != address(0x0), "The destination wallet cannot be null.");
        require(!isExistingHolding(tradingWallet), "The destination wallet must be a new fresh wallet.");
        Holding memory holding = heldTokens[who];
        require(holding.isAffiliate, "Only affiliates can use this function; use release() for non-affiliates.");
        require(amount <= holding.quantity, "The holding has less than the specified amount of tokens.");

        if(block.timestamp > holding.releaseDate) {

            // Send the tokens currently held by this contract on behalf of 'who' to the nominated wallet.
            bool res = TokenInterface(tokenContract).hold(tradingWallet, amount);
            if(res) {
                heldTokens[who] = Holding(holding.quantity.sub(amount), holding.releaseDate, holding.isAffiliate);
                emit TokensReleased(who, amount);
                return true;
            }
        }

        return false;
    }

    /**
     * @notice Under special circumstances the Transfer Agent needs to move tokens around.
     *
     * @dev As the release date is accurate to one second it is very unlikely release dates will
     * match so an address that does not have a holding in this contract is required as the target.
     *
     * @param from      The current holder of the tokens.
     * @param to        The recipient of the tokens - must be a 'clean' address.
     * @param amount    The number of tokens to move.
     */
    function transfer(address from, address to, uint256 amount) public onlyTransferAgent returns (bool) {
        require(to != address(0x0), "Cannot transfer tokens to the null address.");
        require(amount > 0, "Cannot transfer zero tokens.");
        Holding memory fromHolding = heldTokens[from];
        require(fromHolding.quantity >= amount, "Not enough tokens to perform the transfer.");
        require(!isExistingHolding(to), "Cannot overwrite an existing holding, use a new wallet.");

        heldTokens[from] = Holding(fromHolding.quantity.sub(amount), fromHolding.releaseDate, fromHolding.isAffiliate);
        heldTokens[to] = Holding(amount, fromHolding.releaseDate, false);

        emit TokensTransferred(from, to, amount);

        return true;
    }

    /**
     * @notice The Transfer Agent may need to add time to the release date if they are unable to verify
     * the holder in a timely manner.
     *
     * @param who       The holder of the tokens.
     * @param tSeconds    The number of seconds to add to the release date.  NOTE: 'tSeconds' appears to
     *                  be a reserved word.
     */
    function addTime(address who, int tSeconds) public onlyTransferAgent returns (bool) {
        require(tSeconds != 0, "Time added cannot be zero.");
        
        Holding memory holding = heldTokens[who];
        uint256 oldDate = holding.releaseDate;
        uint256 newDate = tSeconds < 0 ? holding.releaseDate.sub(uint(-tSeconds)) : holding.releaseDate.add(uint(tSeconds));
        heldTokens[who] = Holding(holding.quantity, newDate, holding.isAffiliate);
        
        emit ReleaseDateChanged(who, oldDate, heldTokens[who].releaseDate);
        return true;
    }

    /**
     * @notice Company affiliates have added restriction, allow the Transfer Agent set/clear this flag
     * as needed.
     *
     * @param who           The address being affiliated/unaffiliated.
     * @param isAffiliate   Whether the address is an affiliate or not.
     */
    function setAffiliate(address who, bool isAffiliate) public onlyTransferAgent returns (bool) {
        require(who != address(0x0), "The null address cannot be used.");

        Holding memory holding = heldTokens[who];
        require(holding.isAffiliate != isAffiliate, "Attempt to set the same affiliate status that is already set.");

        heldTokens[who] = Holding(holding.quantity, holding.releaseDate, isAffiliate);

        emit AffiliateStatusChanged(who, isAffiliate);

        return true;
    }

    /**
     * @notice Check if a wallet is already in use, only new/fresh/clean wallets can hold tokens.
     *
     * @param who   The wallet to check.
     * @return      True if the wallet is in use, false otherwise.
     */
    function isExistingHolding(address who) public view returns (bool) {
        Holding memory h = heldTokens[who];
        return (h.quantity != 0 || h.releaseDate != 0);
    }
}