contract oracle_pWING_USD {
    using SafeMath for uint256;
    using FixedPoint for *;

    uint public constant HOURLY = 1 hours;  // 1 hour price updates

    uint PERIOD; //not used but needed for interface requirments since some oneTokens call update period

    struct UniswapPair {
        IUniswapV2Pair pair;
        address token0;
        address token1;
        uint price0CumulativeLast;
        uint price1CumulativeLast;
        uint32 blockTimestampLast;
        FixedPoint.uq112x112 price0Average;
        FixedPoint.uq112x112 price1Average;
        uint PERIOD;
    }

    uint constant numUniswapPairs = 2;
    mapping (uint => UniswapPair) pairs;

    enum PairTypes{ pWING_ETH_HOURLY, ETH_USDC_HOURLY }

    address public pWING; 
    address public USDC; 
    address public WETH;

    address public owner;  //oneToken address
    uint256 public outputDecimals;

    address public spotOracle;
    uint256 public spotOracle_decimals;

    /*
     *  oneToken - will be the owner of this oracle which can run update
     *  factory - sushi factory - mainnet 0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac
     *  pWING - pWING token
     *  USDC - USDC token
     *  WETH - WETH token
     *  chainlink - spotPrice - mainnet 0x134fE0a225Fb8e6683617C13cEB6B3319fB4fb82
     */
    constructor(
        address oneToken, 
        address factory, 
        address pWING_, 
        address USDC_,
        address WETH_, 
        uint256 outputDecimals_,
        address spotOracle_) 
        public {

            // spot check price oracle

            spotOracle = spotOracle_;
            spotOracle_decimals = AggregatorV3Interface(spotOracle)
                .decimals();


            IUniswapV2Pair _pair = IUniswapV2Pair(IUniswapV2Factory(factory).getPair(pWING_, WETH_));
            uint112 reserve0;
            uint112 reserve1;
            uint32 blockTimestampLast;
         
            (reserve0, reserve1, blockTimestampLast) = _pair.getReserves();
            require(reserve0 != 0 && reserve1 != 0, 'oracle_pWING_WETH: NO_RESERVES');

            pairs[uint(PairTypes.pWING_ETH_HOURLY)] = UniswapPair(
                _pair,
                pWING_,
                WETH_,
                _pair.price0CumulativeLast(),
                _pair.price1CumulativeLast(),
                blockTimestampLast,
                FixedPoint.uq112x112(0),
                FixedPoint.uq112x112(0),
                HOURLY
            );

            _pair = IUniswapV2Pair(IUniswapV2Factory(factory).getPair(USDC_, WETH_));
            (reserve0, reserve1, blockTimestampLast) = _pair.getReserves();
            require(reserve0 != 0 && reserve1 != 0, 'oracle_USDC_WETH: NO_RESERVES');

            pairs[uint(PairTypes.ETH_USDC_HOURLY)] = UniswapPair(
                _pair,
                USDC_,
                WETH_,
                _pair.price0CumulativeLast(),
                _pair.price1CumulativeLast(),
                blockTimestampLast,
                FixedPoint.uq112x112(0),
                FixedPoint.uq112x112(0),
                HOURLY
            );

            pWING = pWING_;
            USDC = USDC_;
            WETH = WETH_;
            owner = oneToken;
            outputDecimals = outputDecimals_;
           
    }

    function changeInterval(uint256 period_) external {
        require(msg.sender == owner, "unauthorized");
        PERIOD = period_;
    }

    function update() external {
        for (uint i = 0; i < numUniswapPairs; i++) {
            (uint price0Cumulative, uint price1Cumulative, uint32 blockTimestamp) =
            UniswapV2OracleLibrary.currentCumulativePrices(address(pairs[i].pair));
            uint32 timeElapsed = blockTimestamp - pairs[i].blockTimestampLast; // overflow is desired

            if (timeElapsed >= pairs[i].PERIOD || pairs[i].price0Average.mul(1).decode144() == uint(0)) {
                pairs[i].price0Average = FixedPoint.uq112x112(uint224((price0Cumulative - pairs[i].price0CumulativeLast) / timeElapsed));
                pairs[i].price1Average = FixedPoint.uq112x112(uint224((price1Cumulative - pairs[i].price1CumulativeLast) / timeElapsed));

                pairs[i].price0CumulativeLast = price0Cumulative;
                pairs[i].price1CumulativeLast = price1Cumulative;
                
                pairs[i].blockTimestampLast = blockTimestamp;
            }
        }
    }

    // note this will always return 0 before update has been called successfully for the first time.
    function consult(uint i, address token, uint amountIn) internal view returns (uint256) {
        uint256 amount;
        if (token == pairs[i].token0) {
            amount = pairs[i].price0Average.mul(amountIn).decode144();
        } else {
            require(token == pairs[i].token1, 'oracle_pWING_USDC: INVALID_TOKEN');
            amount = pairs[i].price1Average.mul(amountIn).decode144();
        }
       return amount;
    }

    function getHourlyPrice() public view returns (uint256) {
        uint256 pWING_ETH_HOURLY = consult(uint(PairTypes.pWING_ETH_HOURLY),WETH,10**18).div(10 ** 18);
        uint256 ETH_USDC_HOURLY = consult(uint(PairTypes.ETH_USDC_HOURLY),WETH,10**18).mul(10 ** 3);

        uint256 pWING_USDC_HOURLY = pWING_ETH_HOURLY.mul(ETH_USDC_HOURLY).div(10 ** 9);

        return pWING_USDC_HOURLY;

    }

    function getSpotPrice() public view returns (uint256) {
        (, int256 price, , , ) = AggregatorV3Interface(spotOracle)
            .latestRoundData();
        uint256 price_ = uint256(price);
        if (outputDecimals > spotOracle_decimals) {
            price_ = price_.mul(
                10**(outputDecimals - spotOracle_decimals)
            );
        }
        if (outputDecimals < spotOracle_decimals) {
            price_ = price_.div(
                10**(spotOracle_decimals - outputDecimals)
            );
        }
        return price_;
    }

    function getLatestPrice() public view returns (uint256) {
        uint256 pWING_USDC_HOURLY = getHourlyPrice(); 
        uint256 sport_price = getSpotPrice();

        if (pWING_USDC_HOURLY < sport_price) return pWING_USDC_HOURLY;

        return sport_price;
    }

}
/* 
 *  This will be used with sushi pWING/ETH to get pWING/USDC price.
 *  It will use a TWAP of 1 hr and chainlink sport price to determine of there is recent
 *  change in price.  It chooses the lower price between spot and twap. 
 */
function consult(uint i, address token, uint amountIn) internal view returns (uint256) {
    uint256 amount;
    if (token == pairs[i].token0) {
        amount = pairs[i].price0Average.mul(amountIn).decode144();
    } else {
        require(token == pairs[i].token1, 'oracle_pWING_USDC: INVALID_TOKEN');
        amount = pairs[i].price1Average.mul(amountIn).decode144();
    }
   return amount;
}

// note this will always return 0 before update has been called successfully for the first time.