contract DPPFactory is InitializableOwnable {
    // ============ Templates ============

    address public immutable _CLONE_FACTORY_;
    address public immutable _DEFAULT_MAINTAINER_;
    address public immutable _DEFAULT_MT_FEE_RATE_MODEL_;
    address public immutable _DODO_APPROVE_PROXY_;
    address public _DPP_TEMPLATE_;
    address public _DPP_ADMIN_TEMPLATE_;

    // ============ Registry ============

    // base -> quote -> DPP address list
    mapping(address => mapping(address => address[])) public _REGISTRY_;
    // creator -> DPP address list
    mapping(address => address[]) public _USER_REGISTRY_;

    // ============ Events ============

    event NewDPP(
        address baseToken,
        address quoteToken,
        address creator,
        address dpp
    );


    event RemoveDPP(address dpp);

    constructor(
        address cloneFactory,
        address dppTemplate,
        address dppAdminTemplate,
        address defaultMaintainer,
        address defaultMtFeeRateModel,
        address dodoApproveProxy
    ) public {
        _CLONE_FACTORY_ = cloneFactory;
        _DPP_TEMPLATE_ = dppTemplate;
        _DPP_ADMIN_TEMPLATE_ = dppAdminTemplate;
        _DEFAULT_MAINTAINER_ = defaultMaintainer;
        _DEFAULT_MT_FEE_RATE_MODEL_ = defaultMtFeeRateModel;
        _DODO_APPROVE_PROXY_ = dodoApproveProxy;
    }

    // ============ Functions ============

    function createDODOPrivatePool() external returns (address newPrivatePool) {
        newPrivatePool = ICloneFactory(_CLONE_FACTORY_).clone(_DPP_TEMPLATE_);
    }

    function initDODOPrivatePool(
        address dppAddress,
        address creator,
        address baseToken,
        address quoteToken,
        uint256 lpFeeRate,
        uint256 k,
        uint256 i,
        bool isOpenTwap
    ) external {
        {
            address _dppAddress = dppAddress;
            address adminModel = _createDPPAdminModel(
                creator,
                _dppAddress,
                creator,
                _DODO_APPROVE_PROXY_
            );
            IDPP(_dppAddress).init(
                adminModel,
                _DEFAULT_MAINTAINER_,
                baseToken,
                quoteToken,
                lpFeeRate,
                _DEFAULT_MT_FEE_RATE_MODEL_,
                k,
                i,
                isOpenTwap
            );
        }

        _REGISTRY_[baseToken][quoteToken].push(dppAddress);
        _USER_REGISTRY_[creator].push(dppAddress);
        emit NewDPP(baseToken, quoteToken, creator, dppAddress);
    }

    function _createDPPAdminModel(
        address owner,
        address dpp,
        address operator,
        address dodoApproveProxy
    ) internal returns (address adminModel) {
        adminModel = ICloneFactory(_CLONE_FACTORY_).clone(_DPP_ADMIN_TEMPLATE_);
        IDPPAdmin(adminModel).init(owner, dpp, operator, dodoApproveProxy);
    }

    // ============ Admin Operation Functions ============
    
    function updateAdminTemplate(address _newDPPAdminTemplate) external onlyOwner {
        _DPP_ADMIN_TEMPLATE_ = _newDPPAdminTemplate;
    }

    function updateDppTemplate(address _newDPPTemplate) external onlyOwner {
        _DPP_TEMPLATE_ = _newDPPTemplate;
    }

    function addPoolByAdmin(
        address creator,
        address baseToken, 
        address quoteToken,
        address pool
    ) external onlyOwner {
        _REGISTRY_[baseToken][quoteToken].push(pool);
        _USER_REGISTRY_[creator].push(pool);
        emit NewDPP(baseToken, quoteToken, creator, pool);
    }

    function removePoolByAdmin(
        address creator,
        address baseToken, 
        address quoteToken,
        address pool
    ) external onlyOwner {
        address[] memory registryList = _REGISTRY_[baseToken][quoteToken];
        for (uint256 i = 0; i < registryList.length; i++) {
            if (registryList[i] == pool) {
                registryList[i] = registryList[registryList.length - 1];
                break;
            }
        }
        _REGISTRY_[baseToken][quoteToken] = registryList;
        _REGISTRY_[baseToken][quoteToken].pop();
        address[] memory userRegistryList = _USER_REGISTRY_[creator];
        for (uint256 i = 0; i < userRegistryList.length; i++) {
            if (userRegistryList[i] == pool) {
                userRegistryList[i] = userRegistryList[userRegistryList.length - 1];
                break;
            }
        }
        _USER_REGISTRY_[creator] = userRegistryList;
        _USER_REGISTRY_[creator].pop();
        emit RemoveDPP(pool);
    }

    // ============ View Functions ============

    function getDODOPool(address baseToken, address quoteToken)
        external
        view
        returns (address[] memory pools)
    {
        return _REGISTRY_[baseToken][quoteToken];
    }

    function getDODOPoolBidirection(address token0, address token1)
        external
        view
        returns (address[] memory baseToken0Pool, address[] memory baseToken1Pool)
    {
        return (_REGISTRY_[token0][token1], _REGISTRY_[token1][token0]);
    }

    function getDODOPoolByUser(address user) 
        external
        view
        returns (address[] memory pools)
    {
        return _USER_REGISTRY_[user];
    }
}
/**
 * @title DODO PrivatePool Factory
 * @author DODO Breeder
 *
 * @notice Create And Register DPP Pools 
 */
function getDODOPool(address baseToken, address quoteToken)
    external
    view
    returns (address[] memory pools)
{
    return _REGISTRY_[baseToken][quoteToken];
}

// ============ View Functions ============