// SPDX-License-Identifier: MIT

pragma solidity 0.7.6;
pragma abicoder v2;

import "@solv/v2-solidity-utils/contracts/access/AdminControl.sol";
import "@solv/v2-solidity-utils/contracts/misc/BokkyPooBahsDateTimeLibrary.sol";
import "@solv/v2-solidity-utils/contracts/misc/StringConvertor.sol";
import "@solv/v2-vnft-core/contracts/interface/IVNFT.sol";
import "../interface/IPriceOracleManager.sol";
import "../interface/IPriceOracle.sol";
import "../interface/IBondVoucher.sol";

contract PriceOracleManager is IPriceOracleManager, AdminControl {

    event NewVoucherOracle(
        address voucher,
        address oldOracle,
        address newOracle
    );

    event NewDefaultOracle(
        address oldOracle, 
        address newOracle
    );

    event SetDefaultPricePeriod(
        uint64 oldPricePeriod,
        uint64 newPricePeriod
    );

    event SetPricePeriod(
        address voucher, 
        uint64 oldPricePeriod,
        uint64 newPricePeriod
    );

    uint64 public defaultPricePeriod;
    IPriceOracle public defaultOracle;

    //voucher => pricePeriod
    mapping(address => uint64) internal _pricePeriods;

    //voucher => IPriceOracle
    mapping(address => IPriceOracle) internal _oracles;

    function initialize(IPriceOracle oracle_) external {
        AdminControl.__AdminControl_init(_msgSender());
        defaultPricePeriod = 7 * 86400;
        defaultOracle = oracle_;
        emit NewDefaultOracle(address(0), address(oracle_));
    }

    function _setVoucherOracle(address voucher_, IPriceOracle oracle_)
        external
        onlyAdmin
    {
        address old = address(_oracles[voucher_]);
        _oracles[voucher_] = oracle_;
        emit NewVoucherOracle(voucher_, old, address(oracle_));
    }

    function _setDefaultOracle(IPriceOracle newOracle_) external onlyAdmin {
        address old = address(defaultOracle);
        defaultOracle = newOracle_;
        emit NewDefaultOracle(old, address(newOracle_));
    }

    function _setDefaultPricePeriod(uint64 pricePeriod_) external onlyAdmin {
        emit SetDefaultPricePeriod(defaultPricePeriod, pricePeriod_);
        defaultPricePeriod = pricePeriod_;
    }

    function _setPricePeriod(address voucher_, uint64 pricePeriod_)
        external
        onlyAdmin
    {
        emit SetPricePeriod(voucher_, _pricePeriods[voucher_], pricePeriod_);
        _pricePeriods[voucher_] = pricePeriod_;
    }

    function getPricePeriod(address voucher_) external view returns (uint64) {
        return _pricePeriods[voucher_];
    }

    function refreshUnderlyingPriceOfTokenId(address voucher_, uint256 tokenId_)
        external
    {
        uint256 slot = IVNFT(voucher_).slotOf(tokenId_);
        refreshUnderlyingPriceOfSlot(voucher_, slot);
    }

    function refreshUnderlyingPriceOfSlot(address voucher_, uint256 slot_)
        public
    {
        IBondPool.SlotDetail memory slotDetail = IBondVoucher(voucher_).getSlotDetail(slot_);

        address currency = slotDetail.fundCurrency;
        uint64 maturity = slotDetail.maturity;

        address underlying = IBondVoucher(voucher_).underlying();
        (uint64 fromDate, uint64 toDate) = _getPeriod(voucher_, maturity);

        getOracle(voucher_).refreshPrice(underlying, currency , fromDate, toDate);
    }

    function getOracle(address voucher_) public view returns (IPriceOracle) {
        return
            address(_oracles[voucher_]) != address(0)
                ? _oracles[voucher_]
                : defaultOracle;
    }

    function _getPeriod(address voucher_, uint64 maturity_)
        internal
        view
        returns (uint64 fromDate_, uint64 toDate_)
    {
        uint64 pricePeriod = _pricePeriods[voucher_] == 0
            ? defaultPricePeriod
            : _pricePeriods[voucher_];
        toDate_ = maturity_;
        fromDate_ = toDate_ - pricePeriod;
    }

    function getPriceOfMaturity(address voucher_, address fundCurrency_, uint64 maturity_)
        public
        view
        virtual
        override
        returns (int256 price_)
    {
        address underlying = IBondVoucher(voucher_).underlying();
        (uint64 fromDate, uint64 toDate) = _getPeriod(voucher_, maturity_);
        return getOracle(voucher_).getPrice(underlying, fundCurrency_, fromDate, toDate);
    }

    function getPriceOfSlot(address voucher_, uint256 slot_) 
        public 
        view 
        virtual
        override
        returns (int256 price_) 
    {
        IBondPool.SlotDetail memory slotDetail = IBondVoucher(voucher_).getSlotDetail(slot_);
        return getPriceOfMaturity(voucher_, slotDetail.fundCurrency, slotDetail.maturity);
    }

    function getPriceOfTokenId(address voucher_, uint256 tokenId_)
        external
        view
        virtual
        override
        returns (int256 price_)
    {
        uint256 slot = IVNFT(voucher_).slotOf(tokenId_);
        return getPriceOfSlot(voucher_, slot);
    }
}
[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"NewAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldOracle","type":"address"},{"indexed":false,"internalType":"address","name":"newOracle","type":"address"}],"name":"NewDefaultOracle","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldPendingAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newPendingAdmin","type":"address"}],"name":"NewPendingAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"voucher","type":"address"},{"indexed":false,"internalType":"address","name":"oldOracle","type":"address"},{"indexed":false,"internalType":"address","name":"newOracle","type":"address"}],"name":"NewVoucherOracle","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"oldPricePeriod","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newPricePeriod","type":"uint64"}],"name":"SetDefaultPricePeriod","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"voucher","type":"address"},{"indexed":false,"internalType":"uint64","name":"oldPricePeriod","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newPricePeriod","type":"uint64"}],"name":"SetPricePeriod","type":"event"},{"inputs":[{"internalType":"contract IPriceOracle","name":"newOracle_","type":"address"}],"name":"_setDefaultOracle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"pricePeriod_","type":"uint64"}],"name":"_setDefaultPricePeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"voucher_","type":"address"},{"internalType":"uint64","name":"pricePeriod_","type":"uint64"}],"name":"_setPricePeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"voucher_","type":"address"},{"internalType":"contract IPriceOracle","name":"oracle_","type":"address"}],"name":"_setVoucherOracle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"acceptAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"defaultOracle","outputs":[{"internalType":"contract IPriceOracle","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"defaultPricePeriod","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"voucher_","type":"address"}],"name":"getOracle","outputs":[{"internalType":"contract IPriceOracle","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"voucher_","type":"address"},{"internalType":"address","name":"fundCurrency_","type":"address"},{"internalType":"uint64","name":"maturity_","type":"uint64"}],"name":"getPriceOfMaturity","outputs":[{"internalType":"int256","name":"price_","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"voucher_","type":"address"},{"internalType":"uint256","name":"slot_","type":"uint256"}],"name":"getPriceOfSlot","outputs":[{"internalType":"int256","name":"price_","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"voucher_","type":"address"},{"internalType":"uint256","name":"tokenId_","type":"uint256"}],"name":"getPriceOfTokenId","outputs":[{"internalType":"int256","name":"price_","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"voucher_","type":"address"}],"name":"getPricePeriod","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IPriceOracle","name":"oracle_","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pendingAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"voucher_","type":"address"},{"internalType":"uint256","name":"slot_","type":"uint256"}],"name":"refreshUnderlyingPriceOfSlot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"voucher_","type":"address"},{"internalType":"uint256","name":"tokenId_","type":"uint256"}],"name":"refreshUnderlyingPriceOfTokenId","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newPendingAdmin_","type":"address"}],"name":"setPendingAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"}]