// Contract: GoaldToken
// Target function: function transfer(address recipient, uint256 amount) public override returns (bool)
 uint256 private constant STAGE_INITIAL               = 0;
    uint256 private constant STAGE_ISSUANCE_CLAIMED      = 1;
    uint256 private constant STAGE_DAO_INITIATED         = 2;
    uint256 private constant STAGE_ALL_GOVERNANCE_ISSUED = 3;
    uint256 private _governanceStage;

    // Reentrancy reversions are the only calls to revert (in this contract) that do not have reasons. We add a third state, 'frozen'
    // to allow for locking non-admin functions. The contract may be permanently frozen if it has been upgraded.
    uint256 private constant RE_NOT_ENTERED = 1;
    uint256 private constant RE_ENTERED     = 2;
    uint256 private constant RE_FROZEN      = 3;
    uint256 private _status;

    // Separate reentrancy status to further guard against arbitrary calls against a DAO contract via `unsafeCallDAO()`.
    uint256 private _daoStatus;

    // Override decimal places to 2. See `GoaldProxy.REWARD_THRESHOLD`.
    constructor() ERC20("Goald", "GOALD") public {
        _setupDecimals(DECIMALS);
        _status    = RE_FROZEN;
        _daoStatus = RE_NOT_ENTERED;
    }

    /// Events ///
    
    event DAOStatusChanged(address daoAddress, uint256 status);

    event DAOUpgraded(address daoAddress);

    event GoaldDeployed(address goaldAddress);

    event ManagerChanged(address newManager);

    /// Admin ///

    /** Freezes the contract. Only admin functions can be called. */
    function freeze() external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED);
        require(msg.sender == _manager, "Not manager");

        _status = RE_FROZEN;
    }

    /** Sets the status of a given DAO. */
    function setDAOStatus(address daoAddress, uint256 index, uint256 status) external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED || _status == RE_FROZEN);
        require(msg.sender == _manager, "Not manager");

        // Validate the index as well.
        require(_daoAddresses[index] == daoAddress, "Non-matching DAO index");

        // Validate the status.
        require(status == VALID_DAO || status == INVALID_DAO, "Invalid status");
        uint256 currentStatus = _isValidDAO[daoAddress];
        require(currentStatus != status && (currentStatus == VALID_DAO || currentStatus == INVALID_DAO), "Invalid current status");

        // Update the status.
        _isValidDAO[daoAddress] = status;

        // Hello world!
        emit DAOStatusChanged(daoAddress, status);
    }

    function setManager(address newManager) external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED || _status == RE_FROZEN);
        require(msg.sender == _manager,      "Not manager");
        require(newManager != address(0),    "Can't be zero address");
        require(newManager != address(this), "Can't be this address");

        // If the issuance has been claimed but the DAO has not been initialized, then the new manager must be able to initialize it.
        require((_governanceStage != STAGE_ISSUANCE_CLAIMED) || (balanceOf(newManager) > 110000 * REWARD_THRESHOLD), "New manager can't init DAO");

        _manager = newManager;

        // Hello world!
        emit ManagerChanged(newManager);
    }

    /** Unfreezes the contract. Non-admin functions can again be called. */
    function unfreeze() external {
        // Reentrancy guard.
        require(_status == RE_FROZEN);
        require(msg.sender == _manager, "Not manager");

        _status = RE_NOT_ENTERED;
    }

    /** Upgrades to the new DAO version. Can only be done when frozen. */
    function upgradeDAO(address daoAddress) external {
        // Reentrancy guard.
        require(_status == RE_FROZEN);
        _status = RE_ENTERED;

        // It must be a contract.
        uint256 codeSize;
        assembly { codeSize := extcodesize(daoAddress) }
        require(codeSize > 0, "Not a contract");

        // Make sure it hasn't been tracked yet.
        require(_isValidDAO[daoAddress] == UNTRACKED_DAO, "DAO already tracked");

        // Upgrade the DAO.
        _daoAddresses.push(daoAddress);
        _isValidDAO[daoAddress] = VALID_DAO;

        // Enable the DAO.
        IGoaldDAO(daoAddress).makeReady(_governanceStage, _goaldCount);

        // Hello world!
        emit DAOUpgraded(daoAddress);

        // By storing the original amount once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200).
        _status = RE_FROZEN;
    }

    /// Goalds ///

    /** Gets the base url for Goald metadata. */
    function getBaseTokenURI() external view returns (string memory) {
        return _baseTokenURI;
    }

    /** Gets the total number of deployed Goalds. */
    function getGoaldCount() external view returns (uint256) {
        return _goaldCount;
    }

    /** Returns the address of the DAO which deployed the Goald. */
    function getGoaldDAO(uint256 id) external view returns (address) {
        require(id < _goaldCount, "ID too large");

        uint256 addressesCount = _daoAddresses.length;
        uint256 index;
        uint256 goaldCount;
        address goaldAddress;

        for (; index < addressesCount; index ++) {
            goaldAddress = _daoAddresses[index];
            goaldCount += IGoaldDAO(goaldAddress).getGoaldCount();
            if (id <= goaldCount) {
                return goaldAddress;
            }
        }

        revert("Unknown DAO");
    }

    /**
     * Called when a deployer deploys a new Goald (via the DAO contract). Currently we use this to distribute the governance token
     * according to the following schedule. An additional 120,000 tokens will be claimable by the deployer of this proxy. This will
     * create a total supply of 210,000 tokens. Once the governance protocal is set up, 110,000 tokens will be burned to initiate that
     * mechanism. That will leave 10% ownership for the deployer of the contract, with the remaining 90% disbused on Goald creations.
     * No rewards can be paid out before the governance protocal has been initiated.
     *
     *      # Goalds    # Tokens
     *       0 -  9       1000
     *      10 - 19        900
     *      20 - 29        800
     *      30 - 39        700
     *      40 - 49        600
     *      50 - 59        500
     *      60 - 69        400
     *      70 - 79        300
     *      80 - 89        200
     *      90 - 99        100
     *       < 3600         10
     */
    function goaldDeployed(address recipient, address goaldAddress) external returns (uint256) {
        // Reentrancy guard.
        require(_daoStatus == RE_NOT_ENTERED);

        // Validate the caller.
        require(msg.sender == _daoAddresses[_daoAddresses.length - 1], "Caller not latest DAO");
        require(_isValidDAO[msg.sender] == VALID_DAO, "Caller not valid DAO");

        // Hello world!
        emit GoaldDeployed(goaldAddress);

        uint256 goaldCount = _goaldCount++;
        if (_governanceStage == STAGE_ALL_GOVERNANCE_ISSUED) {
            return 0;
        }

        // Calculate the amount of tokens issued based on the schedule.
        uint256 amount;
        if        (goaldCount <   10) {
            amount = 1000;
        } else if (goaldCount <   20) {
            amount =  900;
        } else if (goaldCount <   30) {
            amount =  800;
        } else if (goaldCount <   40) {
            amount =  700;
        } else if (goaldCount <   50) {
            amount =  600;
        } else if (goaldCount <   60) {
            amount =  500;
        } else if (goaldCount <   70) {
            amount =  400;
        } else if (goaldCount <   80) {
            amount =  300;
        } else if (goaldCount <   90) {
            amount =  200;
        } else if (goaldCount <  100) {
            amount =  100;
        } else if (goaldCount < 3600) {
            amount =   10;
        }
        
        // We have issued all tokens, so move to the last stage of governance. This will short circuit this function on future calls.
        // This will result in unnecessary gas if the DAO is never initiated and all 3600 token-earning goalds are created. But the
        // DAO should be initiated long before that.
        else if (_governanceStage == STAGE_DAO_INITIATED) {
            _governanceStage = STAGE_ALL_GOVERNANCE_ISSUED;
        }

        if (amount == 0) {
            return 0;
        }

        // Validate the recipient.
        require(_isValidDAO[recipient] == UNTRACKED_DAO, "Can't be DAO");
        require(recipient != address(0), "Can't be zero address");
        require(recipient != address(this), "Can't be Goald token");

        // Validate the amount.
        uint256 totalSupply = totalSupply();
        require(amount + totalSupply > totalSupply, "Overflow error");
        require(amount + totalSupply < MAX_SUPPLY, "Exceeds supply");
        
        // Mint the tokens.
        _mint(recipient, amount * REWARD_THRESHOLD);

        return amount;
    }

    /** Sets the base url for Goald metadata. */
    function setBaseTokenURI(string calldata baseTokenURI) external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED || _status == RE_FROZEN);
        require(msg.sender == _manager, "Not manager");

        _baseTokenURI = baseTokenURI;
    }

    /// Governance ///

    /** Claims the initial issuance of the governance token to enable bootstrapping the DAO. */
    function claimIssuance() external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED);
        require(msg.sender == _manager,            "Not manager");
        require(_governanceStage == STAGE_INITIAL, "Already claimed");

        // We are creating a new holder.
        if (balanceOf(_manager) < REWARD_THRESHOLD) {
            uint256 index;
            uint256 count = _daoAddresses.length;
            for (; index < count; index ++) {
                IGoaldDAO(_daoAddresses[index]).issuanceIncreasesHolders();
            }
        }

        // Mint the tokens.
        _mint(_manager, 120000 * REWARD_THRESHOLD);

        // Update the governance stage.
        _governanceStage = STAGE_ISSUANCE_CLAIMED;
    }

    /** Returns the address of the DAO at the given index. */
    function getDAOAddressAt(uint256 index) external view returns (address) {
        return _daoAddresses[index];
    }

    /** Returns the number of historical DAO addresses. */
    function getDAOCount() external view returns (uint256) {
        return _daoAddresses.length;
    }

    /** Returns the status of the DAO with the given address. */
    function getDAOStatus(address daoAddress) external view returns (uint256) {
        return _isValidDAO[daoAddress];
    }

    /** Gets the latest dao address, so long as it's valid. */
    function getLatestDAO() external view returns (address) {
        address daoAddress = _daoAddresses[_daoAddresses.length - 1];
        require(_isValidDAO[daoAddress] == VALID_DAO, "Latest DAO invalid");

        return daoAddress;
    }

    /** Returns the current stage of the DAO's governance. */
    function getGovernanceStage() external view returns (uint256) {
        return _governanceStage;
    }

    /** Releases management to the DAO. */
    function initializeDAO() external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED);
        _status = RE_ENTERED;

        require(msg.sender == _manager,                     "Not manager");
        require(_governanceStage == STAGE_ISSUANCE_CLAIMED, "Issuance unclaimed");

        // Burn the tokens.
        uint256 startingBalance = balanceOf(_manager);
        require(startingBalance >= 110000 * REWARD_THRESHOLD, "Not enough tokens");
        _burn(_manager, 110000 * REWARD_THRESHOLD);

        // Update the stage.
        _governanceStage = STAGE_DAO_INITIATED;

        uint256 count = _daoAddresses.length;

        // If the manager no longer is a holder we need to tell the latest DAO.
        if (count > 0 && startingBalance - (110000 * REWARD_THRESHOLD) < REWARD_THRESHOLD) {
            IGoaldDAO(_daoAddresses[count - 1]).initializeDecreasesHolders();
        }

        // Tell the DAOs so they can create rewards.
        uint256 index;
        for (; index < count; index++) {
            IGoaldDAO(_daoAddresses[index]).updateGovernanceStage();
        }

        // By storing the original amount once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200).
        _status = RE_NOT_ENTERED;
    }

    /**
     * Executes a function on the DAO. Only the manager can call this function. This guards against reentrancy so any called function
     * cannot execute a call against this contract. This code is duplicated with `unsafeCallDAO()` in place of having an internal
     * `_callDAO()` since reentrancy guarding is not guaranteed.
     *
     * @param daoAddress Which DAO is being called.
     * @param encodedData The non-packed, abi encoded calldata that will be included with the function call.
     */
    function safeCallDAO(address daoAddress, bytes calldata encodedData) external returns (bytes memory) {
        // Reentrancy guard. We check against both normal reentrancy and DAO call reentrancy.
        require(_status    == RE_NOT_ENTERED);
        require(_daoStatus == RE_NOT_ENTERED);
        _status = RE_ENTERED;
        _daoStatus = RE_ENTERED;

        require(msg.sender == _manager, "Not manager");
        // `_isValidDAO` since DAOs can be disabled. Use `unsafeCallDAO()` if a call must be made to an invalid DAO.
        require(_isValidDAO[daoAddress] == VALID_DAO, "Not a valid DAO");

        // Call the function, bubbling on errors.
        (bool success, bytes memory returnData) = daoAddress.call(encodedData);

        // By storing the original amount once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200).
        _status = RE_NOT_ENTERED;
        _daoStatus = RE_NOT_ENTERED;

        // See @OpenZeppelin.Address._functionCallWithValue()
        if (success) {
            return returnData;
        } else {
            // Look for revert reason and bubble it up if present
            if (returnData.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                // solhint-disable-next-line no-inline-assembly
                assembly {
                    let returnData_size := mload(returnData)
                    revert(add(32, returnData), returnData_size)
                }
            } else {
                revert();
            }
        }
    }

    /**
     * Executes a function on the DAO. Only the manager can call this function. This DOES NOT guard against reentrancy. Do not use
     * this unless reentrancy is needed or the call is made to an invlaid contract. Otherwise use `safeCallDAO()`. This code is
     * duplicated in place of having an internal `_callDAO()` since reentrancy guarding is not guaranteed.
     *
     * @param daoAddress Which DAO is being called.
     * @param encodedData The non-packed, abi encoded calldata that will be included with the function call.
     */
    function unsafeCallDAO(address daoAddress, bytes calldata encodedData) external returns (bytes memory) {
        // Reentrancy guard. We check against both normal reentrancy and DAO call reentrancy.
        require(_daoStatus == RE_NOT_ENTERED);
        _daoStatus = RE_ENTERED;

        require(msg.sender == _manager, "Not manager");
        // `_isValidDAO` since DAOs can be disabled.
        require(_isValidDAO[daoAddress] != UNTRACKED_DAO, "DAO not tracked");

        // Call the function, bubbling on errors.
        (bool success, bytes memory returnData) = daoAddress.call(encodedData);

        // By storing the original amount once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200).
        _daoStatus = RE_NOT_ENTERED;
        
        // See @OpenZeppelin.Address._functionCallWithValue()
        if (success) {
            return returnData;
        } else {
            // Look for revert reason and bubble it up if present
            if (returnData.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                // solhint-disable-next-line no-inline-assembly
                assembly {
                    let returnData_size := mload(returnData)
                    revert(add(32, returnData), returnData_size)
                }
            } else {
                revert();
            }
        }
    }

    /// ERC20 Overrides ///