contract GCACToken is ERC20 {

    enum TokenType { Unknown, GCAC, LiquidityPool }

    /**
     * @notice The details of a future company cashout.
     */
    struct Notice {
        // The maximum number of tokens proposed for sale.
        uint256 amount;

        // The date after which company tokens can be swapped.
        uint256 releaseDate;

        // Whether the notice given is for this contract's tokens (GCAC) or the
        // liquidity pool tokens created by Uniswap where fees are periodically
        // cashed in.
        TokenType tokenType;
    }

    // Event fired when a restricted wallet gives notice of a potential future trade.
    event NoticeGiven(address indexed who, uint256 amount, uint256 releaseDate, TokenType tokenType);

    /**
     * @notice Notice must be given to the public before treasury tokens can be swapped.
     */
    Notice public noticeTreasury;

    /**
     * @notice Notice must be given to the public before Liquidity Tokens can be removed from the pool.
     */
    Notice public noticeLiquidity;

    /**
    * @notice The account that created this contract, also functions as the liquidity provider.
    */
    address public owner;

    /**
     * @notice Holder of the company's 50% share of all Uniswap tokens.  Can only interact with the
     * Uniswap pair/router, is forbidden from trading tokens elsewhere.
     */
    address public treasury;

    /**
     * @notice The account that performs the 1% of sales buyback of tokens, all bought tokens are burned.
     * @dev They cannot be autoburned during transfer as the Uniswap client prevents the transaction.
     */
    address public buyback;

    /**
     * @notice The address of the Uniswap router, the liquidity provider and treasury can only interact with this
     * address.  This prevents trading outside of Uniswap for these accounts.
     */
    address public router;

    /**
     * @notice The address of the Uniswap Pair/ERC20 contract holding the Liquidity Pool tokens.
     */
    address public pairAddress;

    /**
     * @notice Restrict functionaly to the contract owner.
     */
    modifier onlyOwner {
        require(_msgSender() == owner, "You are not Owner.");
        _;
    }

    /**
     * @notice Restrict functionaly to the buyback account.
     */
    modifier onlyBuyback {
        require(_msgSender() == buyback, "You are not Buyback.");
        _;
    }

    constructor(uint256 initialSupply, string memory name, string memory symbol) ERC20(name, symbol) {
        owner = _msgSender();
        _mint(_msgSender(), initialSupply);
    }

    /**
     * Set the account that burns GCAC tokens periodically.
     */
    function setBuyback(address who) public onlyOwner {
        require(buyback == address(0), "The Buyback address can only be set once.");
        buyback = who;
    }

    /**
     * Set the address of the account holding GCAC tokens on behalf of the company.
     */
    function setTreasury(address who) public onlyOwner {
        require(treasury == address(0), "The Treasury address can only be set once.");
        treasury = who;
    }

    /**
     * Set the address of the Uniswap router, only this address is allowed to move Treasury tokens.
     */
    function setRouter(address who) public onlyOwner {
        require(router == address(0), "The Router address can only be set once.");
        router = who;
    }

    /**
     * Set the address of the Uniswap Pair/Pool contract.
     */
    function setPairAddress(address who) public onlyOwner {
        require(pairAddress == address(0), "The Pair address can only be set once.");
        pairAddress = who;
    }

    /**
     * @notice Treasury and Liquidity tokens must give advanced notice to the public before they can
     * be used.  The token type is determined by the address giving notice.
     *
     * @param who The address giving notice of a sale in the future.
     * @param amount The maximum number of tokens (in wei).
     * @param numSeconds The number of seconds the tokens cannot be sold for.
     */
    function giveNotice(address who, uint256 amount, uint256 numSeconds) public onlyOwner {
        require(pairAddress != address(0), "The Uniswap Pair contract address must be set.");
        require(who == treasury || who == address(this), "Only Treasury and Liquidity must give notice.");

        uint256 when = block.timestamp + (numSeconds * 1 seconds);

        TokenType tokenType;

        if(who == treasury) {
            require(noticeTreasury.releaseDate == 0 || block.timestamp >= noticeTreasury.releaseDate, "Cannot overwrite an active existing notice.");
            require(amount <= balanceOf(who), "Can't give notice for more GCAC tokens than owned.");
            tokenType = TokenType.GCAC;
            noticeTreasury = Notice(amount, when, tokenType);
        }
        else {
            require(noticeLiquidity.releaseDate == 0 || block.timestamp >= noticeLiquidity.releaseDate, "Cannot overwrite an active existing notice.");
            ERC20 pair = ERC20(pairAddress);
            require(amount <= pair.balanceOf(who), "Can't give notice for more Liquidity Tokens than owned.");
            tokenType = TokenType.LiquidityPool;
            noticeLiquidity = Notice(amount, when, tokenType);
        }

        emit NoticeGiven(who, amount, when, tokenType);
    }

    /**
     * @notice Enforce rules around the company accounts:
     * - Liquidity Pool Creator (owner) can never receive tokens back from Uniswap.
     * - Treasury can only send tokens to Uniswap.
     * - Tokens bought back by the company are immediateley burned.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal override {
        require(recipient != owner, "Liquidity Pool Creator cannot receive tokens.");
        require(sender != buyback, "Buyback cannot transfer tokens, it can only burn.");
        if(sender == treasury) {
            require(_msgSender() == router, "Treasury account tokens can only be moved by the Uniswap Router.");
            require(noticeTreasury.releaseDate != 0 && block.timestamp >= noticeTreasury.releaseDate, "Notice period has not been set or has not expired.");
            require(amount <= noticeTreasury.amount, "Treasury can't transfer more tokens than given notice for.");
            require(noticeTreasury.tokenType == TokenType.GCAC, "The notice given for this user is the wrong token type.");

            // Clear the remaining notice balance, this prevents giving notice on all tokens and
            // trickling them out.
            noticeTreasury = Notice(0, 0, TokenType.Unknown);
        }

        super._transfer(sender, recipient, amount);
    }

    /**
     * @notice Periodically draw down any fee entitlement from the Liquidity Pool after giving notice.
     * @param to The account to send the tokens to.
     * @param amount The number of tokens, in wei.
     */
    function transferLiquidityTokens(address to, uint256 amount) public onlyOwner {
        require(pairAddress != address(0), "The Uniswap Pair contract address must be set.");

        require(noticeLiquidity.releaseDate != 0 && block.timestamp >= noticeLiquidity.releaseDate, "Notice period has not been set or has not expired.");
        require(amount <= noticeLiquidity.amount, "Insufficient Liquidity Token balance.");
        require(noticeLiquidity.tokenType == TokenType.LiquidityPool, "The notice given for this user is the wrong token type.");

        ERC20 pair = ERC20(pairAddress);
        pair.transfer(to, amount);

        // Clear the notice even if only partially used.
        noticeLiquidity = Notice(0, 0, TokenType.Unknown);
    }

    /**
     * @notice The buyback account periodically buys tokens and then burns them to reduce the
     * total supply pushing up the price of the remaining tokens.
     */
    function burn() public onlyBuyback {
        _burn(buyback, balanceOf(buyback));
    }
}
/**
 * @title Global Cannabis Applications Corporation (GCAC) contract for Uniswap.
 * @author Abbey Technology GmbH
 * @notice Token contract for use with Uniswap.  Enforces restrictions outlined in the prospectus.
 */
function giveNotice(address who, uint256 amount, uint256 numSeconds) public onlyOwner {
    require(pairAddress != address(0), "The Uniswap Pair contract address must be set.");
    require(who == treasury || who == address(this), "Only Treasury and Liquidity must give notice.");

    uint256 when = block.timestamp + (numSeconds * 1 seconds);

    TokenType tokenType;

    if(who == treasury) {
        require(noticeTreasury.releaseDate == 0 || block.timestamp >= noticeTreasury.releaseDate, "Cannot overwrite an active existing notice.");
        require(amount <= balanceOf(who), "Can't give notice for more GCAC tokens than owned.");
        tokenType = TokenType.GCAC;
        noticeTreasury = Notice(amount, when, tokenType);
    }
    else {
        require(noticeLiquidity.releaseDate == 0 || block.timestamp >= noticeLiquidity.releaseDate, "Cannot overwrite an active existing notice.");
        ERC20 pair = ERC20(pairAddress);
        require(amount <= pair.balanceOf(who), "Can't give notice for more Liquidity Tokens than owned.");
        tokenType = TokenType.LiquidityPool;
        noticeLiquidity = Notice(amount, when, tokenType);
    }

    emit NoticeGiven(who, amount, when, tokenType);
}

/**
 * @notice Treasury and Liquidity tokens must give advanced notice to the public before they can
 * be used.  The token type is determined by the address giving notice.
 *
 * @param who The address giving notice of a sale in the future.
 * @param amount The maximum number of tokens (in wei).
 * @param numSeconds The number of seconds the tokens cannot be sold for.
 */