/*

    Copyright 2020 DODO ZOO.
    SPDX-License-Identifier: Apache-2.0

*/

pragma solidity 0.6.9;

import {IDODOApproveProxy} from "../DODOApproveProxy.sol";
import {ICloneFactory} from "../../lib/CloneFactory.sol";
import {IERC20} from "../../intf/IERC20.sol";
import {IWETH} from "../../intf/IWETH.sol";
import {InitializableOwnable} from "../../lib/InitializableOwnable.sol";
import {ICollateralVault} from "../../CollateralVault/intf/ICollateralVault.sol";
import {IDVM} from "../../DODOVendingMachine/intf/IDVM.sol";
import {IFragment} from "../../GeneralizedFragment/intf/IFragment.sol";
import {IDODONFTRegistry} from "../../Factory/Registries/DODONFTRegistry.sol";
import {SafeMath} from "../../lib/SafeMath.sol";
import {SafeERC20} from "../../lib/SafeERC20.sol";
import {DecimalMath} from "../../lib/DecimalMath.sol";
import {ReentrancyGuard} from "../../lib/ReentrancyGuard.sol";


/**
 * @title DODONFTProxy
 * @author DODO Breeder
 *
 * @notice Entrance of NFT in DODO platform
 */
contract DODONFTProxy is ReentrancyGuard, InitializableOwnable {
    using SafeMath for uint256;


    // ============ Storage ============

    address constant _ETH_ADDRESS_ = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    address public immutable _WETH_;
    address public immutable _DODO_APPROVE_PROXY_;
    address public immutable _CLONE_FACTORY_;
    address public immutable _NFT_REGISTY_;
    address public immutable _DEFAULT_MAINTAINER_;
    
    address public _MT_FEE_RATE_MODEL_;
    address public _VAULT_TEMPLATE_;
    address public _FRAG_TEMPLATE_;
    address public _DVM_TEMPLATE_;
    address public _BUYOUT_MODEL_;

    // ============ Events ============
    event ChangeVaultTemplate(address newVaultTemplate);
    event ChangeFragTemplate(address newFragTemplate);
    event ChangeDvmTemplate(address newDvmTemplate);
    event ChangeMtFeeRateTemplate(address newMtFeeRateTemplate);
    event ChangeBuyoutModel(address newBuyoutModel);
    event CreateNFTCollateralVault(address creator, address vault, string name, string baseURI);
    event CreateFragment(address vault, address fragment, address dvm);
    event Buyout(address from, address fragment, uint256 amount);

    // ============ Modifiers ============

    modifier judgeExpired(uint256 deadLine) {
        require(deadLine >= block.timestamp, "DODONFTProxy: EXPIRED");
        _;
    }

    fallback() external payable {}

    receive() external payable {}

    constructor(
        address cloneFactory,
        address payable weth,
        address dodoApproveProxy,
        address defaultMaintainer,
        address buyoutModel,
        address mtFeeRateModel,
        address vaultTemplate,
        address fragTemplate,
        address dvmTemplate,
        address nftRegistry
    ) public {
        _CLONE_FACTORY_ = cloneFactory;
        _WETH_ = weth;
        _DODO_APPROVE_PROXY_ = dodoApproveProxy;
        _DEFAULT_MAINTAINER_ = defaultMaintainer;
        _MT_FEE_RATE_MODEL_ = mtFeeRateModel;
        _BUYOUT_MODEL_ = buyoutModel;
        _VAULT_TEMPLATE_ = vaultTemplate;
        _FRAG_TEMPLATE_ = fragTemplate;
        _DVM_TEMPLATE_ = dvmTemplate;
        _NFT_REGISTY_ = nftRegistry;
    }

    function createNFTCollateralVault(string memory name, string memory baseURI) external returns (address newVault) {
        newVault = ICloneFactory(_CLONE_FACTORY_).clone(_VAULT_TEMPLATE_);
        ICollateralVault(newVault).init(msg.sender, name, baseURI);
        emit CreateNFTCollateralVault(msg.sender, newVault, name, baseURI);
    }
    
    function createFragment(
        address[] calldata addrList, //0 - quoteToken, 1 - vaultPreOwner
        uint256[] calldata params, //(DVM: 0 - lpFeeRate 1 - I, 2 - K) , (FRAG: 3 - totalSupply, 4 - ownerRatio, 5 - buyoutTimestamp, 6 - distributionRatio)
        bool isOpenTwap,
        string memory fragSymbol
    ) external returns (address newFragment, address newDvm) {
        newFragment = ICloneFactory(_CLONE_FACTORY_).clone(_FRAG_TEMPLATE_);
        address _quoteToken = addrList[0] == _ETH_ADDRESS_ ? _WETH_ : addrList[0];
        
        {
        uint256[] memory  _params = params;
        
        newDvm = ICloneFactory(_CLONE_FACTORY_).clone(_DVM_TEMPLATE_);
        IDVM(newDvm).init(
            _DEFAULT_MAINTAINER_,
            newFragment,
            _quoteToken,
            _params[0],
            _MT_FEE_RATE_MODEL_,
            _params[1],
            _params[2],
            isOpenTwap
        );
        IFragment(newFragment).init(
            newDvm, 
            addrList[1], 
            msg.sender, 
            _params[3], 
            _params[4], 
            _params[5],
            _DEFAULT_MAINTAINER_,
            _BUYOUT_MODEL_,
            _params[6],
            fragSymbol
        );
        }

        ICollateralVault(msg.sender).directTransferOwnership(newFragment);
        
        IDODONFTRegistry(_NFT_REGISTY_).addRegistry(msg.sender, newFragment, _quoteToken, newDvm);

        emit CreateFragment(msg.sender, newFragment, newDvm);
    }

    function buyout(
        address fragment,
        uint256 quoteMaxAmount,
        uint8 flag, // 0 - ERC20, 1 - quoteInETH
        uint256 deadLine
    ) external payable preventReentrant judgeExpired(deadLine) {
        if(flag == 0)
            require(msg.value == 0, "DODONFTProxy: WE_SAVED_YOUR_MONEY");
        
        address dvm = IFragment(fragment)._DVM_();
        uint256 fragTotalSupply = IFragment(fragment).totalSupply();
        uint256 buyPrice = IDVM(dvm).getMidPrice();

        uint256 curRequireQuote = DecimalMath.mulCeil(buyPrice, fragTotalSupply);

        require(curRequireQuote <= quoteMaxAmount, "DODONFTProxy: CURRENT_TOTAL_VAULE_MORE_THAN_QUOTEMAX");

        _deposit(msg.sender, fragment, IFragment(fragment)._QUOTE_(), curRequireQuote, flag == 1);
        IFragment(fragment).buyout(msg.sender);

        IDODONFTRegistry(_NFT_REGISTY_).removeRegistry(fragment);

        // refund dust eth
        if (flag == 1 && msg.value > curRequireQuote) msg.sender.transfer(msg.value - curRequireQuote);

        emit Buyout(msg.sender, fragment, curRequireQuote);
    }

    //============= Owner ===================
    function updateVaultTemplate(address newVaultTemplate) external onlyOwner {
        _VAULT_TEMPLATE_ = newVaultTemplate;
        emit ChangeVaultTemplate(newVaultTemplate);
    }

    function updateFragTemplate(address newFragTemplate) external onlyOwner {
        _FRAG_TEMPLATE_ = newFragTemplate;
        emit ChangeFragTemplate(newFragTemplate);
    }

    function updateMtFeeRateTemplate(address newMtFeeRateTemplate) external onlyOwner {
        _MT_FEE_RATE_MODEL_ = newMtFeeRateTemplate;
        emit ChangeMtFeeRateTemplate(newMtFeeRateTemplate);
    }

    function updateDvmTemplate(address newDvmTemplate) external onlyOwner {
        _DVM_TEMPLATE_ = newDvmTemplate;
        emit ChangeDvmTemplate(newDvmTemplate);
    }

    function updateBuyoutModel(address newBuyoutModel) external onlyOwner {
        _BUYOUT_MODEL_ = newBuyoutModel;
        emit ChangeBuyoutModel(newBuyoutModel);
    }


    //============= Internal ================

    function _deposit(
        address from,
        address to,
        address token,
        uint256 amount,
        bool isETH
    ) internal {
        if (isETH) {
            if (amount > 0) {
                IWETH(_WETH_).deposit{value: amount}();
                if (to != address(this)) SafeERC20.safeTransfer(IERC20(_WETH_), to, amount);
            }
        } else {
            IDODOApproveProxy(_DODO_APPROVE_PROXY_).claimTokens(token, from, to, amount);
        }
    }
}
