contract Quadron is IQuadron, ERC20, ReserveFormula {
    // Declare state variables
    mapping(address => bool) private approvedToken;  // Only parent contract may buy Quadrons
    bool private setApprovedTokenOnce = false;  // Admin can only set the approved token once
    address private admin;  // Contract admin
    uint256 private fixedPrice;  // Price of the Quadrons during the fixed price phase
    address private wallet01;
    address private wallet02;
    uint256 private tokenEpoch; // Start time of the contract
    uint8 public contractPhase = 1; // Phase of the contract, i.e. 0, 1, 2, 3
    bool private mintedBonus = false;

    // Declare constants
    uint32 internal constant WEIGHT = 213675; // reserve ratio = weight / 1e6.  200e3 corresponds to 20%
    uint32 internal constant PHASE_1_DURATION = 7889400; // time in seconds for the phase 1 period
    uint32 internal constant PHASE_2_DURATION = 15778800; // time in seconds for the phase 2 period
    uint256 internal constant PHASE_1_END_SUPPLY = 65e6 * 1e18; // supply of quadrons at the end of phase 1
    uint256 internal constant PHASE_3_START_SUPPLY = 68333333333333333333333333; // supply of quadrons at the start of phase 3
    uint256 internal constant PRICE_FLOOR = 1e14; // minimum price for quadrons

    /**  @dev Quadron Constructor
     *
     *   @param _admin Address of the contract admin
     *   @param _fixedPrice Price of the Quadron during the fixed price phase
     *   @param _wallet01 Address of wallet01
     *   @param _wallet02 Address of wallet02
     */     
    constructor(
        address _admin,
        uint _fixedPrice,
        address _wallet01,
        address _wallet02
    ) ERC20("Quadron", "QUAD") payable {
        admin = _admin;
        
        // Mint 30M tokens
        _mint(_wallet01, 20e6 * 1e18); // Mint 20M tokens to wallet01
        _mint(_wallet02, 10e6 * 1e18); // Mint 10M tokens to wallet02
        
        tokenEpoch = block.timestamp;
        fixedPrice = _fixedPrice;
        wallet01 = _wallet01;
        wallet02 = _wallet02;
    }
    
    /** @dev Allows admin to add an approved token, once
     *  @param tokenAddress Address of the new contract owner
     */ 
    function setApprovedToken(address tokenAddress) external {
        require(msg.sender == admin, "Not authorized");
        require(setApprovedTokenOnce == false, "Can only set the approved token once.");
        setApprovedTokenOnce = true;
        approvedToken[tokenAddress] = true;
    }
    
    /**  @dev Buy tokens through minting.  Assumes the buyer is the Quadron contract owner.
     *
     *   @param minTokensRequested The minimum amount of quadrons to mint.  If this
     *   minimum amount of tokens can't be minted, the function reverts
     */
    function buy(uint256 minTokensRequested) external override payable {
        require(approvedToken[msg.sender] == true, "Address not authorized to mint tokens");
        
        uint256 originalBalance = address(this).balance - msg.value;
        uint256 costTokens = quotePriceForTokens(minTokensRequested, originalBalance);
        require(msg.value >= costTokens, "Insufficient Ether sent to the purchase the minimum requested tokens");

        // Transfer Phase 1 funds before minting
        phase1Transfer(minTokensRequested, costTokens);

        _mint(msg.sender, minTokensRequested);

        updatePhase();

        // Refund any leftover ETH
        (bool success,) = msg.sender.call{ value: (msg.value - costTokens) }("");        
        require(success, "Refund failed");
    }

    /** @dev Sell tokens.  The amount of Ether the seller receivers is based on the amount of 
     *  tokens sent with the sell() method.  The amount of Ether received (target) is based on 
     *  the total supply of the quadrons (_supply), the balance of Ether in the quadron 
     *  (_reserveBalance), the reserve ratio (_reserveWeight), and the requested amount of tokens
     *  to be sold (_amount)
     *
     *  Formula:
     *  target = _reserveBalance * (1 - (1 - _amount / _supply) ^ (1000000 / _reserveWeight))
     *
     *  @param amount Tokens to sell
     */
    function sell(uint256 amount) external override {
        require(contractPhase == 3, "The token cannot be sold until the blackout period has expired.");
        require(balanceOf(msg.sender) >= amount, "Cannot sell more than it owns");

        uint256 startSupply = totalSupply();
        _burn(msg.sender, amount);

        if(address(this).balance > 0) {
            uint256 proceeds = 0;

            if(approvedToken[msg.sender]) {
                proceeds = saleTargetAmount(startSupply, address(this).balance, WEIGHT, amount);
            } else {
                // bonus token holders
                proceeds = amount * address(this).balance / startSupply;
            }

            (bool success,) = msg.sender.call{ value: proceeds }("");
            require(success, "Transfer failed");            
        }
    }

    /**  @dev Calculates the effective price (wei) of the Quadron
     *
     *   @return effectivePrice Approximately the mid-price in wei for one token
     */
    function quoteEffectivePrice() public view returns (uint256 effectivePrice) {
        if(contractPhase == 3) {
            // In phase 3
            
            uint256 _effectivePrice = 0;
            
            if(totalSupply() > 0) {
                _effectivePrice = address(this).balance * (1e18 * 1e6 / WEIGHT) / totalSupply();
            }
            
            if(_effectivePrice < PRICE_FLOOR) {
                return PRICE_FLOOR;
            } else {
                return _effectivePrice;
            }
        } else {
            // In phase 1 or 2
            return fixedPrice;
        }
    }

    /** @dev Calculates the average price (in wei) per Quadron issued for some
     * some amount of Ethers.
     *   @param amount Amount of Ethers (in wei)
     *   @return avgPriceForTokens Average price per Quadron (in wei)
     */
    function quoteAvgPriceForTokens(uint256 amount) external view override returns (uint256 avgPriceForTokens) {
        require(amount > 0, "The amount must be greater than zero.");
        
        if(contractPhase == 3) {
            // In phase 3
            uint256 numberTokens = purchaseTargetAmount(totalSupply(), address(this).balance, WEIGHT, amount);
            uint256 avgPrice = 1e18 * amount / numberTokens;

            if(avgPrice < PRICE_FLOOR) {
                return PRICE_FLOOR;
            } else {
                return avgPrice;
            }
        } else {
            // In phase 1 or 2
            return fixedPrice;
        }
    }

    /** @dev Calculates the cost (in Wei) to buy some amount of Quadron
     *   @param amount Amount of Quadrons
     *   @param originalBalance The amount of ethers in this contract with msg.value subtracted
     *   @return price Price total price to purchase (amount) of Quadrons
     */
    function quotePriceForTokens(uint256 amount, uint256 originalBalance) internal view returns (uint256 price) {
        require(amount > 0, "The amount must be greater than zero.");
        
        if(contractPhase == 3) {
            // In phase 3
            uint256 _price = quoteTargetAmount(totalSupply(), originalBalance, WEIGHT, amount);

            if(_price < PRICE_FLOOR * amount / 1e18) {
                return PRICE_FLOOR * amount / 1e18;
            } else {
                return _price;
            }
        } else {
            // In phase 1 or 2
            return fixedPrice * amount / 1e18;
        }        
    }

    /** @dev Updates the contract's phase
     */
    function updatePhase() public override {
        if((block.timestamp >= (tokenEpoch + PHASE_2_DURATION) ||
            totalSupply() >= PHASE_3_START_SUPPLY) && contractPhase < 3) {
            
            // In phase 3
            contractPhase = 3;

        } else if((block.timestamp >= (tokenEpoch + PHASE_1_DURATION) ||
            totalSupply() >= PHASE_1_END_SUPPLY) && contractPhase < 2) {
            
            // In phase 2
            contractPhase = 2;
        }
    }

    /** @dev Getter method for the contractPhase variable
     */
    function getPhase() external view override returns (uint8 phase) {
        return contractPhase;
    }

    /** @dev Mints bonus Quadrons
     */
    function mintBonus() external override {
        require(approvedToken[msg.sender] == true, "Not authorized.");
        require(mintedBonus == false, "Bonus already minted.");
        mintedBonus = true;
        _mint(msg.sender,  15e6 * 1e18); // Mint 15M tokens as the early buyer bonus
    }

    /** @dev Transfer phase 1 funds to wallet
     *  @param newTokens The amount of new tokens to be minted
     *  @param costTokens The cost (in wei) of the new tokens to be minted
     */
    function phase1Transfer(uint256 newTokens, uint256 costTokens) internal {
        if(contractPhase == 1) {
            if(totalSupply() + newTokens <= PHASE_1_END_SUPPLY) {
                // the value of all newTokens should be transferred to wallet
                (bool success,) = wallet01.call{ value: (costTokens) }("");
                require(success, "Failed to transfer phase 1 funds");
            } else if (totalSupply() < PHASE_1_END_SUPPLY) {
                // newTokens will cause the phase to change to Phase 2.
                // Calculate ratio of funds to be sent to wallet
                (bool success,) = wallet01.call{ value: (costTokens * (PHASE_1_END_SUPPLY - totalSupply()) / newTokens) }("");
                require(success, "Failed to transfer phase 1 funds");
            }
        }
    }

    /** @dev Allows an approved token to add a new approved token
     *  @param newTokenAddress The address of the new approved token
     */
    function addApprovedToken(address newTokenAddress) external override {
        require(approvedToken[msg.sender] == true, "Only approved token can approve new tokens");
        approvedToken[newTokenAddress] = true;
    }

    /** @dev Fallback function that executes when no data is sent
     */    
    receive() external payable {
    }
    
    /** @dev Fallback is a fallback function that executes when no other method matches
     */     
    fallback() external payable {
    }
}
function mintBonus() external override {
    require(approvedToken[msg.sender] == true, "Not authorized.");
    require(mintedBonus == false, "Bonus already minted.");
    mintedBonus = true;
    _mint(msg.sender,  15e6 * 1e18); // Mint 15M tokens as the early buyer bonus
}

/** @dev Mints bonus Quadrons
 */