/*

    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 {IConstFeeRateModel} from "../../lib/ConstFeeRateModel.sol";
import {IFragment} from "../../GeneralizedFragment/intf/IFragment.sol";
import {IFeeDistributor} from "../../intf/IFeeDistributor.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 _VAULT_TEMPLATE_;
    address public _FRAG_TEMPLATE_;
    address public _FEE_TEMPLATE_;
    address public _DVM_TEMPLATE_;
    address public _MTFEE_TEMPLATE_;

    // ============ Events ============
    event ChangeVaultTemplate(address newVaultTemplate);
    event ChangeFragTemplate(address newFragTemplate);
    event ChangeFeeTemplate(address newFeeTemplate);
    event ChangeMtFeeTemplate(address newMtFeeTemplate);
    event ChangeDvmTemplate(address newDvmTemplate);
    event CreateNFTCollateralVault(address creator, address vault, string name, string baseURI);
    event CreateFragment(address vault, address fragment, address dvm, address feeDistributor);
    event Buyout(address from, address fragment, uint256 amount);
    event Stake(address from, address feeDistributor, uint256 amount);


    fallback() external payable {}

    receive() external payable {}

    constructor(
        address cloneFactory,
        address payable weth,
        address dodoApproveProxy,
        address defaultMaintainer,
        address vaultTemplate,
        address fragTemplate,
        address feeTemplate,
        address dvmTemplate,
        address mtFeeTemplate,
        address nftRegistry
    ) public {
        _CLONE_FACTORY_ = cloneFactory;
        _WETH_ = weth;
        _DODO_APPROVE_PROXY_ = dodoApproveProxy;
        _DEFAULT_MAINTAINER_ = defaultMaintainer;
        _VAULT_TEMPLATE_ = vaultTemplate;
        _FRAG_TEMPLATE_ = fragTemplate;
        _FEE_TEMPLATE_ = feeTemplate;
        _DVM_TEMPLATE_ = dvmTemplate;
        _MTFEE_TEMPLATE_ = mtFeeTemplate;
        _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 quoteToken,
        address vaultPreOwner,
        address stakeToken, //address(0) using frag token
        uint256[] calldata dvmParams, //0 - lpFeeRate, 1 - mtFeeRate 2 - I, 3 - K
        uint256[] calldata fragParams, //0 - totalSupply, 1 - ownerRatio, 2 - buyoutTimestamp
        bool isOpenTwap 
    ) external returns (address newFragment, address newDvm, address newFeeDistributor) {
        newFragment = ICloneFactory(_CLONE_FACTORY_).clone(_FRAG_TEMPLATE_);
        address _quoteToken = quoteToken == _ETH_ADDRESS_ ? _WETH_ : quoteToken;
        
        if(stakeToken == address(0)) {
            stakeToken = newFragment;
        } 

        newFeeDistributor = ICloneFactory(_CLONE_FACTORY_).clone(_FEE_TEMPLATE_);
        IFeeDistributor(newFeeDistributor).init(newFragment, _quoteToken, stakeToken);

        {
        uint256[] memory  _dvmParams = dvmParams;
        uint256[] memory  _fragParams = fragParams;
        
        newDvm = ICloneFactory(_CLONE_FACTORY_).clone(_DVM_TEMPLATE_);
        IDVM(newDvm).init(
            newFeeDistributor == address(0) ? _DEFAULT_MAINTAINER_ : newFeeDistributor,
            newFragment,
            _quoteToken,
            _dvmParams[0],
            _createConstantMtFeeRateModel(_dvmParams[1]),
            _dvmParams[2],
            _dvmParams[3],
            isOpenTwap
        );
        IFragment(newFragment).init(
            newDvm, 
            vaultPreOwner, 
            msg.sender, 
            _fragParams[0], 
            _fragParams[1], 
            _fragParams[2]
        );
        }

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

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

    function buyout(
        address fragment,
        uint256 quoteAmount,
        uint8 flag // 0 - ERC20, 1 - quoteInETH
    ) external payable preventReentrant {
        _deposit(msg.sender, fragment, IFragment(fragment)._QUOTE_(), quoteAmount, flag == 1);
        IFragment(fragment).buyout(msg.sender);
        emit Buyout(msg.sender, fragment, quoteAmount);
    }

    function stakeToFeeDistributor(
        address feeDistributor,
        uint256 stakeAmount,
        uint8 flag // 0 - ERC20, 1 - ETH
    ) external payable preventReentrant {
        address stakeVault = IFeeDistributor(feeDistributor)._STAKE_VAULT_();
        require(stakeVault != address(0), "DODONFTProxy:STAKE_VAULT_EMPTY");
        _deposit(msg.sender, stakeVault, IFeeDistributor(feeDistributor)._STAKE_TOKEN_(), stakeAmount, flag == 1);
        IFeeDistributor(feeDistributor).stake(msg.sender);
        emit Stake(msg.sender, feeDistributor, stakeAmount);
    }

    //============= 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 updateFeeTemplate(address newFeeTemplate) external onlyOwner {
        _FEE_TEMPLATE_ = newFeeTemplate;
        emit ChangeFeeTemplate(newFeeTemplate);
    }

    function updateMtFeeTemplate(address newMtFeeTemplate) external onlyOwner {
        _MTFEE_TEMPLATE_ = newMtFeeTemplate;
        emit ChangeMtFeeTemplate(newMtFeeTemplate);
    }

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

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

    function _createConstantMtFeeRateModel(uint256 mtFee) internal returns (address mtFeeModel) {
        mtFeeModel = ICloneFactory(_CLONE_FACTORY_).clone(_MTFEE_TEMPLATE_);
        IConstFeeRateModel(mtFeeModel).init(mtFee);
    }


    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);
        }
    }
}
