contract LamboToken is ERC20, NitroProtocol, Ownable, Pausable {
    using SafeMath for uint256;

    /// @notice Scale factor for NITRO calculations
    uint256 public constant scaleFactor = 1e18;

    /// @notice Total supply
    uint256 public constant total_supply = 2049 ether;

    /// @notice uniswap listing rate
    uint256 public constant INITIAL_TOKENS_PER_ETH = 2.27272727 * 1 ether;

    /// @notice WETH token address
    address public constant WETH = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

    /// @notice self-explanatory
    address public constant uniswapV2Factory = address(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f);

    address public immutable initialDistributionAddress;    

    address public stakingContractAddress;

    address public presaleContractAddress;

    uint256 public presaleInitFunds; 

    /// @notice liquidity sources (e.g. UniswapV2Router) 
    mapping(address => bool) public whitelistedSenders;

    /// @notice exchange addresses (tokens sent here will count as sell orders in NITRO Protocol)
    mapping(address => bool) public exchangeAddresses;

    /// @notice uniswap pair for LAMBO/ETH
    address public uniswapPair;

    /// @notice Whether or not this token is first in uniswap LAMBO<>ETH pair
    bool public isThisToken0;


    /// @notice last TWAP update time (Short calculation)
    uint32 public blockTimestampLast;

    /// @notice last TWAP cumulative price (Short calculation)
    uint256 public priceCumulativeLast;

    /// @notice last TWAP average price (Short calculation)
    uint256 public priceAverageLast;


    /// @notice last TWAP update time
    uint32 public blockTimestampLastLong;

    /// @notice last TWAP cumulative price
    uint256 public priceCumulativeLastLong;

    /// @notice last TWAP average price
    uint256 public priceAverageLastLong;

    /// @notice TWAP min delta (48-hour)
    uint256 public minDeltaTwapLong;

    /// @notice TWAP min delta (Short)
    uint256 public minDeltaTwapShort;

    /// @notice The minimum amount of blocks that must be mined before releasing bonus tokens
    uint public bonusReleaseTime;

    /// @notice percent of the removed funds from sell orders that goes to mechanics
    uint256 public MECHANIC_PCT;

    //Lets us check to see if the user account is moving lambo at this address' request
    address public uniswapv2RouterAddress; 

    //Emittable Events

    event TwapUpdated(uint256 priceCumulativeLast, uint256 blockTimestampLast, uint256 priceAverageLast);

    event LongTwapUpdated(uint256 priceCumulativeLastLong, uint256 blockTimestampLastLong, uint256 priceAverageLastLong);

    event MechanicPercentUpdated(uint256 new_mechanic_PCT);

    event StakingContractAddressUpdated(address newStakingAddress);

    event MaxSellRemovalUpdated(uint256 new_MSR);

    event MaxBuyBonusUpdated(uint256 new_MBB);

    event ExchangeListUpdated(address exchangeAddress, bool isExchange);

    event BonusBalanceUpdated(address userAddress, uint256 newAmount);

    event BonusReleaseTimeUpdated(uint blockDelta);

    event BuyerBonusPaid(address receiver, uint256 bonusAmount);

//                  ------------------ Contract Start Functions ---------------------                //
    constructor(
        uint256 _minDeltaTwapLong,
        uint256 _minDeltaTwapShort,
        uint256 _MECHANIC_PCT
    ) 
    public
    Ownable()
    ERC20("LamboToken", "LAMBO")
    {
        bonusReleaseTime = 13041; 
        setMinDeltaTwap(_minDeltaTwapLong, _minDeltaTwapShort);
        _setMaxBuyBonusPercentage(25);
        _changeMaxSellRemoval(25);
        initialDistributionAddress = owner(); //The contract owner handles all initial distribution, except for presale
        setMechanicPercent(_MECHANIC_PCT);
        _distributeTokens(owner());
        _initializePair();
        _pause();
        setUniswapRouterAddress(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
    }

    modifier whenNotPausedOrInitialDistribution(address tokensender) { //Only used on transfer function
        require(!paused() || msg.sender == initialDistributionAddress || _isWhitelistedSender(msg.sender) || (msg.sender == uniswapv2RouterAddress && tokensender == owner()), "!paused && !initialDistributionAddress !InitialLiquidityProvider");
        _;
    }

    modifier onlyInitialDistributionAddress() { //Only used to initialize twap
        require(msg.sender == initialDistributionAddress, "!initialDistributionAddress");
        _;
    }
    
    function _distributeTokens(
        address _initialDistributionAddress
    ) 
    internal
    {
        //Define the initial distribution of funds:
        //Giveaway funds (50) + Uniswap liquidity (320) + Moderator payments (10) = 370
        uint256 initDistributionFunds = 380 ether;
        // 535.6 LAMBO to the presale contract (455.6 whitelist + 80 dev)
        //         We don't know the presale address yet, so just give these tokens to this contract and transfer them later
        //1133.4 to the Nitro Protocol + the 535.6 to the presale contract = 
        presaleInitFunds = 535.6 ether;
        uint256 initContractFunds = total_supply.sub(initDistributionFunds);

        require((initContractFunds+initDistributionFunds)==total_supply, "Fund distribution doesn't match total supply.");

        _mint(address(_initialDistributionAddress), initDistributionFunds);
        setWhitelistedSender(_initialDistributionAddress, true);

        _mint(address(this), initContractFunds);
        setWhitelistedSender(address(this), true);
    }

    /*
     * Initialize the uniswap pair address to predict it and define it as an exchange address.
     */
    function _initializePair() internal {
        (address token0, address token1) = UniswapV2Library.sortTokens(address(this), address(WETH));
        isThisToken0 = (token0 == address(this));
        uniswapPair = UniswapV2Library.pairFor(uniswapV2Factory, token0, token1);
        setExchangeAddress(uniswapPair, true);
    }

    function setUniswapRouterAddress(address newUniRouterAddy) public onlyOwner {
        uniswapv2RouterAddress = newUniRouterAddy;
    }

//////////////////---------------- Administrative Functions ----------------///////////////
    /**
     * @dev Unpauses all transfers from the distribution address (initial liquidity pool).
     */
    function unpause() external virtual onlyOwner {
        super._unpause();
    }

//////////////////----------------Modify Nitro Protocol Variables----------------///////////////

    //Modify the maxSellRemoval 
    function changeMaxSellRemoval(uint256 maxSellRemoval) public onlyOwner {
        require(maxSellRemoval < 100, "Max Sell Removal is too high!");
        require(maxSellRemoval > 0, "Max Sell Removal is too small!");
        //Send it to the NitroProtocol
        _changeMaxSellRemoval(maxSellRemoval);

        //Emit this transaction
        emit MaxSellRemovalUpdated(maxSellRemoval);
    }

    /*
     * Sets the address of the staking contract ; required for project to work properly. 
     * Setting stakingContract to be the zero address will pause emissions to the staking contract.
     */
    function setStakingContractAddress(address stakingContract) public onlyOwner {
        stakingContractAddress = stakingContract;

        emit StakingContractAddressUpdated(stakingContract);
    }
    
    /*
     * Sets the address of the presale contract ; required for project to work properly.
     * The presale contract address can only be set one time, to prevent re-sending of the 508 lambo. 
     */
    function setPresaleContractAddress(address presaleContract) public onlyOwner {
        //We only want this to fire off once so the dev can't do any shady shit
        if(presaleContractAddress==address(0)){
            //Store address for posterity
            presaleContractAddress = presaleContract;

            //Whitelist the presale contract so that it can transfer tokens while contract is paused
            setWhitelistedSender(presaleContractAddress, true);

            //Send the tokens to the presale contract. presale Tokens should equal 
            super._transfer(address(this), presaleContractAddress, presaleInitFunds);
        }
    }

    /**
     * @dev Set the maximum percent order volume of bonus tokens for buyers
     */
    function setMaxBuyBonusPercentage(uint256 _maxBuyBonus) public onlyOwner {
        require(_maxBuyBonus < 100, "Max Buy Bonus is too high!");
        require(_maxBuyBonus > 0, "Max Buy Bonus is too small!");
        _setMaxBuyBonusPercentage(_maxBuyBonus);

        //Emit Buy Bonus was updated
        emit MaxBuyBonusUpdated(_maxBuyBonus);
    }

    /**
     * @dev Set the percentage that goes to the mechanics. Implicitly, (1-MECHANIC_PCT) = how much goes to Nitro.
     */
    function setMechanicPercent(uint256 _MECHANIC_PCT) public onlyOwner {
        require(_MECHANIC_PCT < 100, "Percent going to mechanics is too high!");
        require(_MECHANIC_PCT > 0, "Percent going to mechanics is too small!");
        MECHANIC_PCT = _MECHANIC_PCT;

        //Emit Mechanic Percent was updated
        emit MechanicPercentUpdated(MECHANIC_PCT);
    }

    /**
     * Set the minimum number of blocks that have to pass for a bonus to be claimable
     */
    function setBonusReleaseTime(uint releasetime) public onlyOwner {
        bonusReleaseTime = releasetime;
        
        //Emit that the bonus release time was updated
        emit BonusReleaseTimeUpdated(bonusReleaseTime);
    }

    /*
     * Sets the bonus tokens amount for a given address. 
     */
    function addBonusTokensBalance(address bonusAddress, uint256 bonus_tokens_amount) internal {
        //Get the current block, and add the delta block number for reward release
        uint releaseBlock = block.number + bonusReleaseTime;

        //Tell Nitro protocol to update token balance for this address
        _addToTimelockedBonus(bonusAddress, bonus_tokens_amount, releaseBlock);
    }

//////////////////----------------Modify Contract Variables----------------///////////////

    /**
     * @dev Min time elapsed before twap is updated.
     */
    function setMinDeltaTwap(uint256 _minDeltaTwapLong, uint256 _minDeltaTwapShort) public onlyOwner {
        require(_minDeltaTwapLong > 1 seconds, "Minimum delTWAP (Long) is too small!");
        require(_minDeltaTwapShort > 1 seconds, "Minimum delTWAP (Short) is too small!");
        require(_minDeltaTwapLong > _minDeltaTwapShort, "Long delta is smaller than short delta!");
        minDeltaTwapLong = _minDeltaTwapLong;
        minDeltaTwapShort = _minDeltaTwapShort;
    }

    /**
     * @dev Sets a whitelisted sender/receiver (nitro protocol does not apply).
     */
    function setWhitelistedSender(address _address, bool _whitelisted) public onlyOwner {
        whitelistedSenders[_address] = _whitelisted;
    }

    /**
     * @dev Sets a known exchange address (tokens sent from these addresses will count as buy orders, tokens sent to these addresses count as sell orders)
     */
    function setExchangeAddress(address _address, bool _isexchange) public onlyOwner {
        exchangeAddresses[_address] = _isexchange;

        emit ExchangeListUpdated(_address, _isexchange);
    }


    function _isWhitelistedSender(address _sender) internal view returns (bool) {
        return whitelistedSenders[_sender];
    }    

    //Public to allow us to easily update exchange addresses in the future
    function isExchangeAddress(address _sender) public view returns (bool) {
        return exchangeAddresses[_sender];
    }

//                  ------------------ Nitro Implementation ---------------------                //

    function _transfer(address sender, address recipient, uint256 amount)
        internal
        virtual
        override
        whenNotPausedOrInitialDistribution(sender)
    {
        //If this isn't a whitelisted sender(such as, this contract itself, the distribution address, or the router)
        if(!_isWhitelistedSender(sender)){

            //if msg sender is an exchange, then this was a buy
            if(isExchangeAddress(sender)){
                _updateShortTwap();
                _updateLongTwap();

                //Calculate how many bonus tokens they've received
                uint256 currentNitro = calculateCurrentNitroRate(true);
                uint256 bonus_tokens_amount = currentNitro.mul(amount).div(scaleFactor);

                //These bonus tokens have to be saved in the timelockedBonuses
                //call nitro function for adding bonus tokens to this address
                addBonusTokensBalance(recipient, bonus_tokens_amount);

                //Emit a bonus tokens balance update
                emit BonusBalanceUpdated(recipient, getBonusAmount(recipient));

            //if recipient is an exchange, then this was a sell
            }else if(isExchangeAddress(recipient)) {
                _updateShortTwap();
                _updateLongTwap();

                //Calculate how many tokens need to be removed from the order
                uint256 currentNitro = calculateCurrentNitroRate(false);
                uint256 removed_tokens_amount = currentNitro.mul(amount).div(scaleFactor);
                //Remove the tokens from the amount to be sent
                amount = amount.sub(removed_tokens_amount);

                //Split the removed tokens amount between mechanics and nitro protocol
                uint256 mechanics_tokens = MECHANIC_PCT.mul(removed_tokens_amount).div(100);
                uint256 nitro_tokens = removed_tokens_amount.sub(mechanics_tokens);

                //Send the nitro tokens to this contract
                super._transfer(sender, address(this), nitro_tokens);

                //Send the mechanics tokens to the staking contract, if there is one
                if(stakingContractAddress!=address(0)){
                    super._transfer(sender, address(stakingContractAddress), mechanics_tokens); //TEST address wrapper on stakingcontractaddress crashing transfer function
                }

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


//                  ------------------ TWAP Functions ---------------------                //  
    /*
     * This function updates the long TWAP, if minDeltaTwapLong has passed
     */
    function _updateLongTwap() internal virtual returns (uint256) {
        (uint price0Cumulative, uint price1Cumulative, uint32 blockTimestamp) = 
            UniswapV2OracleLibrary.currentCumulativePrices(uniswapPair);
        uint32 timeElapsed = blockTimestamp - blockTimestampLastLong; // overflow is desired

        if (timeElapsed > minDeltaTwapLong) {
            uint256 priceCumulative = isThisToken0 ? price1Cumulative : price0Cumulative;

            // cumulative price is in (uq112x112 price * seconds) units so we simply wrap it after division by time elapsed
            FixedPoint.uq112x112 memory priceAverage = FixedPoint.uq112x112(
                uint224((priceCumulative - priceCumulativeLastLong) / timeElapsed)
            );

            priceCumulativeLastLong = priceCumulative;
            blockTimestampLastLong = blockTimestamp;

            priceAverageLastLong = FixedPoint.decode144(FixedPoint.mul(priceAverage, 1 ether));

            emit LongTwapUpdated(priceCumulativeLastLong, blockTimestampLastLong, priceAverageLastLong);
        }

        return priceAverageLastLong;
    }

    /*  
     * This function updates the most realtime price you can possibly get, given a short mindeltatwapshort (5-10 minutes)
     */
    function getCurrentShortTwap() public view returns (uint256) {
        (uint price0Cumulative, uint price1Cumulative, uint32 blockTimestamp) = 
            UniswapV2OracleLibrary.currentCumulativePrices(uniswapPair);
        uint32 timeElapsed = blockTimestamp - blockTimestampLast;

        uint256 priceCumulative = isThisToken0 ? price1Cumulative : price0Cumulative;

        FixedPoint.uq112x112 memory priceAverage = FixedPoint.uq112x112(
            uint224((priceCumulative - priceCumulativeLast) / timeElapsed)
        );

        return FixedPoint.decode144(FixedPoint.mul(priceAverage, 1 ether));
    }

    /*
     * Use this function to get the current short TWAP
     */
    function getLastShortTwap() public view returns (uint256) {
        return priceAverageLast;
    }

    /*
     * Use this function to get the current 48-hour TWAP
     */
    function getLastLongTwap() public view returns (uint256) {
        return priceAverageLastLong;
    }
    
    /*
     * This function updates the short TWAP Given the short TWAP period has passed
     */
    function _updateShortTwap() internal virtual returns (uint256) {
        (uint price0Cumulative, uint price1Cumulative, uint32 blockTimestamp) = 
            UniswapV2OracleLibrary.currentCumulativePrices(uniswapPair);
        uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired

        if (timeElapsed > minDeltaTwapShort) {
            uint256 priceCumulative = isThisToken0 ? price1Cumulative : price0Cumulative;

            // cumulative price is in (uq112x112 price * seconds) units so we simply wrap it after division by time elapsed
            FixedPoint.uq112x112 memory priceAverage = FixedPoint.uq112x112(
                uint224((priceCumulative - priceCumulativeLast) / timeElapsed)
            );

            priceCumulativeLast = priceCumulative;
            blockTimestampLast = blockTimestamp;

            priceAverageLast = FixedPoint.decode144(FixedPoint.mul(priceAverage, 1 ether));

            emit TwapUpdated(priceCumulativeLast, blockTimestampLast, priceAverageLast);
        }

        return priceAverageLast;
    }

    /** 
     * @dev Initializes the TWAP cumulative values for the burn curve.
     */
    function initializeTwap() external onlyInitialDistributionAddress {
        require(blockTimestampLast == 0, "Both TWAPS already initialized");
        (uint256 price0Cumulative, uint256 price1Cumulative, uint32 blockTimestamp) = 
            UniswapV2OracleLibrary.currentCumulativePrices(uniswapPair);

        uint256 priceCumulative = isThisToken0 ? price1Cumulative : price0Cumulative;
        
        //Initialize the short TWAP values
        blockTimestampLast = blockTimestamp;
        priceCumulativeLast = priceCumulative;
        priceAverageLast = INITIAL_TOKENS_PER_ETH;

        //Initialize the long TWAP values
        blockTimestampLastLong = blockTimestamp;
        priceCumulativeLastLong = priceCumulative;
        priceAverageLastLong = INITIAL_TOKENS_PER_ETH;
    }
//                  ------------------ User Functions ---------------------                //

    /**
     * Public function that allows users to claim their bonus $LAMBO.
     * We need to ensure we only interact with msg.sender to make sure no one can claim another's tokens by submitting an address
     */
    function claimBonusTokens() public {

        //Save bonus amount
        uint256 bonusTokens = getBonusAmount(msg.sender);

        //Assert that the bonus tokens amount is not zero
        require(bonusTokens > 0, "There are no bonus tokens to be claimed");
        //Assert that the current block number is 
        require(getBonusUnlockTime(msg.sender) <= block.number, "The token release time has not been reached yet.");
        //Assert that this contrat can actually afford to give this user their bonus tokens
        require(balanceOf(address(this)) > bonusTokens, "The contract can't afford to pay this bonus.");

        /////////Contract is cleared to transfer the bonus tokens

        //Remove the bonus tokens from the nitro protocol
        _removeTimelockedBonus(msg.sender);

        //Emit a bonus tokens balance update
        emit BonusBalanceUpdated(msg.sender, getBonusAmount(msg.sender));

        //Transfer the removed bonus tokens 
        _transfer(address(this), msg.sender, bonusTokens);

        //Emit a paid bonus balance
        emit BuyerBonusPaid(msg.sender, bonusTokens);
    }

    /*
     * Function if for some reason the predicted trading pair address doesn't match real life trading pair address.
     */
    function setUniswapPair(address newUniswapPair) public onlyOwner {
        setExchangeAddress(uniswapPair, false);

        uniswapPair = newUniswapPair;

        setExchangeAddress(uniswapPair, true);
    }

    /*
     * Calculates the current running % for the Nitro protocol. That is,
     * The percent bonus tokens for any buyers at the current moment
     * The percent tokens removed for any sellers at the current moment
     * This is calculated using the TWAP and the realtimeprice. Calling this DOESN'T Update the TWAP. 
     *
     * Returns a uint256 of 0.XX * 1 eth units, where XX is the current % (6% will return 0.06*1ether)
     */
    function calculateCurrentNitroRate(bool isBuy) public view returns (uint256) {
        //The units on both of these is tokens per eth
        uint256 currentRealTimePrice = getLastShortTwap(); 
        
        uint256 currentTwap = getLastLongTwap();
        uint256 nitro;

        //Calculate the Nitro rate based on which is larger to keep it positive
        if(currentRealTimePrice > currentTwap){
            //Calculation explanation:
            //(RTP-TWAP)*scaleFactor/TWAP is typical percent calc but with the scaleFactor moved up b/c uint256
            // The *scaleFactor.dv has to cancel out the scaleFactor to get back to fractions of 100, but in units of ether
            nitro = (currentRealTimePrice.sub(currentTwap).mul(scaleFactor).div(currentTwap))*scaleFactor.div(scaleFactor);
        }
        else{
            //Simply the above calculation * -1 to offset the negative
            nitro = (currentTwap.sub(currentRealTimePrice).mul(scaleFactor).div(currentTwap))*scaleFactor.div(scaleFactor);
        }

        //Validate that the nitro value is within the defined bounds
        uint256 refBuyBonus = (maxBuyBonus()*scaleFactor.div(100));
        uint256 refMaxSell  = (maxSellRemoval()*scaleFactor.div(100));
        if(isBuy && nitro > refBuyBonus){
            return refBuyBonus;
        }else if (!isBuy && nitro > refMaxSell){
            return refMaxSell;
        }
        return nitro;
    }
}
// $LAMBO (LamboToken)
// @dev DegenerateGameTheorist
function setStakingContractAddress(address stakingContract) public onlyOwner {
    stakingContractAddress = stakingContract;

    emit StakingContractAddressUpdated(stakingContract);
}

/*
 * Sets the address of the staking contract ; required for project to work properly.
 * Setting stakingContract to be the zero address will pause emissions to the staking contract.
 */