contract Minter is Auth {

    uint internal constant week = 86400 * 7; // allows minting once per week (reset every Thursday 00:00 UTC)
    uint internal emission = 98;
    uint internal tail_emission = 2;
    uint internal constant target_base = 100; // 2% per week target emission
    uint internal constant tail_base = 1000; // 0.2% per week target emission
    underlying public immutable _token;
    voter public _voter;
    ve public immutable _ve;
    ve_dist public immutable _ve_dist;
    uint public weekly = 625_000e18;
    uint public active_period;
    uint internal constant lock = 86400 * 7 * 52 * 2; //2 year lock

    address internal initializer;
    address internal airdrop;

    event Mint(address indexed sender, uint weekly, uint circulating_supply, uint circulating_emission);

    constructor(
        address GOVERNANCE_,
        address AUTHORITY_,
        address __voter, // the voting & distribution system
        address  __ve, // the veAPHRA system that will be locked into
        address __ve_dist // the distribution system that ensures users aren't diluted after unlock
    ) Auth(GOVERNANCE_, Authority(AUTHORITY_)) {
        initializer = msg.sender;
        _token = underlying(ve(__ve).token());
        _voter = voter(__voter);
        _ve = ve(__ve);
        _ve_dist = ve_dist(__ve_dist);
        active_period = (block.timestamp + (1 * week)) / week * week;
    }

    function initialize(
        address[] memory initVeLocks,
        uint[] memory initVeAmounts,
        address[] memory initToken,
        uint[] memory initTokenAmounts,
        uint max // sum amounts / max = % ownership of top protocols, so if initial 20m is distributed, and target is 25% protocol ownership, then max - 4 x 20m = 80m
    ) external {
        //setup initial mint params here, lock team as ve nft's
        //setup fund team vesting locks
        require(initializer == msg.sender);
        _token.mint(address(this), max);
        _token.approve(address(_ve), type(uint).max);

        for (uint i = 0; i < initVeLocks.length; i++) {
            _ve.create_lock_for(initVeAmounts[i], lock, initVeLocks[i]);
        }

        for (uint i = 0; i < initToken.length; i++) {
            _token.transfer(initToken[i], initTokenAmounts[i]);
        }

        //set to the last item in the initToken array as it is the airdrop and we want to exclude the airdrops balance
        // for supply emission calculations as it can only enter into ve when claimed
        airdrop = address(initToken[initToken.length - 1]);
        initializer = address(0);
        active_period = (block.timestamp + (1 * week)) / week * week;
    }

    function setEmission(uint newEmission_) external requiresAuth {
        emission = newEmission_;
    }

    function setTailEmission(uint newTailEmission_) external requiresAuth {
        tail_emission = newTailEmission_;
    }

    function setWeeklyRate(uint newWeeklyRate_) external requiresAuth {
        weekly = newWeeklyRate_;
    }

    function setVoter(address newVoter_) external requiresAuth {
        _voter = voter(newVoter_);
    }

    //for guarded launch
    function tokenMigrateMinter(address newMinter_) external requiresAuth {
        _token.setMinter(newMinter_);
    }
    //for guarded launch
    function voterMigrateMinter(address newMinter_) external requiresAuth {
        _voter.migrateMinter(newMinter_);
    }

    //for guarded launch
    function ve_distMigrateDepositor(address newMinter_) external requiresAuth {
        _ve_dist.setDepositor(newMinter_);
    }

    // calculate circulating supply as total token supply - locked supply
    function circulating_supply() public view returns (uint) {
        return _token.totalSupply() - _ve.totalSupply() - _token.balanceOf(airdrop) - _token.balanceOf(owner);
    }

    // emission calculation is 2% of available supply to mint adjusted by circulating / total supply
    function calculate_emission() public view returns (uint) {
        return weekly * emission * circulating_supply() / target_base / _token.totalSupply();
    }

    // weekly emission takes the max of calculated (aka target) emission versus circulating tail end emission
    function weekly_emission() public view returns (uint) {
        if(!ve(_ve).isUnlocked()) {
            return weekly;
        }
        return Math.max(calculate_emission(), circulating_emission());
    }

    // calculates tail end (infinity) emissions as 0.2% of total supply
    function circulating_emission() public view returns (uint) {
        return circulating_supply() * tail_emission / tail_base;
    }

    // calculate inflation and adjust ve balances accordingly
    function calculate_growth(uint _minted) public view returns (uint) {
        if(!ve(_ve).isUnlocked()) {
            return 0;
        }
        return _ve.totalSupply() * _minted / _token.totalSupply();
    }

    // update period can only be called once per cycle (1 week)
    function update_period() external returns (uint) {
        uint _period = active_period;
        if (block.timestamp >= _period + week && initializer == address(0)) { // only trigger if new week
            _period = block.timestamp / week * week;
            active_period = _period;
            weekly = weekly_emission();

            uint _growth = calculate_growth(weekly);
            uint _required = _growth + weekly;
            uint _balanceOf = _token.balanceOf(address(this));
            if (_balanceOf < _required) {
                _token.mint(address(this), _required-_balanceOf);
            }

            require(_token.transfer(address(_ve_dist), _growth));
            _ve_dist.checkpoint_token(); // checkpoint token balance that was just minted in ve_dist
            _ve_dist.checkpoint_total_supply(); // checkpoint supply

            _token.approve(address(_voter), weekly);
            _voter.notifyRewardAmount(weekly);

            emit Mint(msg.sender, weekly, circulating_supply(), circulating_emission());
        }
        return _period;
    }

}
// codifies the minting rules as per ve(3,3), abstracted from the token to support any token that allows minting
//add safetransferlib
function voterMigrateMinter(address newMinter_) external requiresAuth {
    _voter.migrateMinter(newMinter_);
}

//for guarded launch