contract MateriaOrchestrator is Proxy, IMateriaOrchestrator {
    
    IDoubleProxy public override doubleProxy;
    
    IMateriaOperator public override swapper;
    IMateriaOperator public override liquidityAdder;
    IMateriaOperator public override liquidityRemover;
    
    IMateriaFactory public override factory;
    IERC20WrapperV1 public override erc20Wrapper;
    IERC20 public override bridgeToken;
    uint public override ETHEREUM_OBJECT_ID;
    
    constructor(
        address initialFactory,
        address initialBridgeToken,
        address initialErc20Wrapper,
        address initialDoubleProxy
    ) {
        factory = IMateriaFactory(initialFactory);
        bridgeToken = IERC20(initialBridgeToken);
        erc20Wrapper = IERC20WrapperV1(initialErc20Wrapper);
        ETHEREUM_OBJECT_ID = uint(keccak256(bytes("THE ETHEREUM OBJECT IT")));
        doubleProxy = IDoubleProxy(initialDoubleProxy);
    }
    
    function setDoubleProxy(
        address newDoubleProxy
    ) onlyDFO external override {
        doubleProxy = IDoubleProxy(newDoubleProxy);
    }
    
    function setBridgeToken(
        address newBridgeToken
    ) onlyDFO external override {
        bridgeToken = IERC20(newBridgeToken);
    }
    
    function setErc20Wrapper(
        address newErc20Wrapper
    ) onlyDFO external override {
        erc20Wrapper = IERC20WrapperV1(newErc20Wrapper);
    }
    
    function setFactory(
        address newFactory
    ) onlyDFO external override {
        factory = IMateriaFactory(newFactory);
    }
    
    function setEthereumObjectId(
        uint newEthereumObjectId
    ) onlyDFO external override {
        ETHEREUM_OBJECT_ID = newEthereumObjectId;
    }
    
    function setSwapper(
        address _swapper,
        bool destroyOld
    ) external override {
        if (destroyOld)
            IMateriaOperator(swapper).destroy(msg.sender);
        swapper = IMateriaOperator(_swapper);
    }
    
    function setLiquidityAdder(
        address _adder,
        bool destroyOld
    ) external override {
        if (destroyOld)
            IMateriaOperator(liquidityAdder).destroy(msg.sender);
        liquidityAdder = IMateriaOperator(_adder);
    }
    
    function setLiquidityRemover(
        address _remover,
        bool destroyOld
    ) external override {
        if (destroyOld)
            IMateriaOperator(liquidityRemover).destroy(msg.sender);
        liquidityRemover = IMateriaOperator(_remover);
    }
    
    function retire(
        address newOrchestrator,
        bool destroyOld,
        address receiver
    ) onlyDFO external override {
        liquidityAdder.setOrchestrator(newOrchestrator);
        liquidityRemover.setOrchestrator(newOrchestrator);
        swapper.setOrchestrator(newOrchestrator);
        factory.transferOwnership(newOrchestrator);
        if (destroyOld) selfdestruct(payable(receiver));
    }
    
    function setFees(
        address token,
        uint materiaFee,
        uint swapFee
    ) onlyDFO external override {
        factory.setFees(
            MateriaLibrary.pairFor(address(factory), address(bridgeToken), token),
            materiaFee,
            swapFee
        );
    }
    
    function setDefaultFees(
        uint materiaFee,
        uint swapFee
    ) onlyDFO external override {
        factory.setDefaultFees(
            materiaFee,
            swapFee
        );
    }
    
    //better be safe than sorry
    function getCrumbs(
        address token,
        uint amount,
        address receiver
    ) onlyDFO external override {
        TransferHelper.safeTransfer(token, receiver, amount);
    }
    
     modifier onlyDFO() {
        require(IMVDFunctionalitiesManager(IMVDProxy(doubleProxy.proxy()).getMVDFunctionalitiesManagerAddress()).isAuthorizedFunctionality(msg.sender), "Unauthorized");
        _;
    }
    
    receive() external payable {
        require(msg.sender == address(erc20Wrapper),'Only EthItem can send ETH to this contract');
    }

    // as ERC1155 receiver

    function onERC1155Received(
        address,
        address,
        uint256,
        uint256,
        bytes calldata payload
    ) external override returns(bytes4) {
        (uint operation,) = abi.decode(payload, (uint, bytes));
        if (operation == 1) { //Adding liquidity
            _delegate(address(liquidityAdder));
        } else if (operation == 2 || operation == 3 || operation == 4 || operation == 5) {
            _delegate(address(swapper)); //Swapping
        } else {
            revert();   
        }
        
        return this.onERC1155Received.selector;
    }

    function onERC1155BatchReceived(
        address,
        address,
        uint256[] calldata,
        uint256[] calldata,
        bytes calldata
    ) external pure override returns(bytes4) {
        revert();
        //return this.onERC1155BatchReceived.selector;
    }
    
    //Liquidity adding
    
    function addLiquidity(
        address token,
        uint tokenAmountDesired,
        uint bridgeAmountDesired,
        uint tokenAmountMin,
        uint bridgeAmountMin,
        address to,
        uint deadline
    ) external override returns (uint amountA, uint amountB, uint liquidity) {
        _delegate(address(liquidityAdder));
    }
    
    function addLiquidityETH(
        uint bridgeAmountDesired,
        uint EthAmountMin,
        uint bridgeAmountMin,
        address to,
        uint deadline
    ) external override payable returns (uint amountToken, uint amountETH, uint liquidity)  {
        _delegate(address(liquidityAdder));
    }
    
    //Liquidity removing
    
    function removeLiquidity(
        address token,
        uint liquidity,
        uint tokenAmountMin,
        uint bridgeAmountMin,
        address to,
        uint deadline
    ) public override {
        _delegate(address(liquidityRemover));
    }
    
    function removeLiquidityETH(
        uint liquidity,
        uint bridgeAmountMin,
        uint EthAmountMin,
        address to,
        uint deadline
    ) public override {
        _delegate(address(liquidityRemover));
    }
    
    function removeLiquidityWithPermit(
        address token,
        uint liquidity,
        uint tokenAmountMin,
        uint bridgeAmountMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) public override {
        _delegate(address(liquidityRemover));
    }
    
    function removeLiquidityETHWithPermit(
        uint liquidity,
        uint tokenAmountMin,
        uint bridgeAmountMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) public override {
        _delegate(address(liquidityRemover));
    }
    
    //Swapping
    
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] memory path,
        address to,
        uint deadline
    ) public override returns (uint[] memory amounts) {
        _delegate(address(swapper));
    }
    
    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] memory path,
        address to,
        uint deadline
    ) public override returns (uint[] memory amounts) {
        _delegate(address(swapper));
    }
    
     function swapExactETHForTokens(
        uint amountOutMin,
        address[] memory path,
        address to,
        uint deadline
    ) public override payable {
        _delegate(address(swapper));
    }
   
    function swapTokensForExactETH(
        uint amountOut,
        uint amountInMax,
        address[] memory path,
        address to,
        uint deadline
    ) public override {
        _delegate(address(swapper));
    }
    
    function swapExactTokensForETH(
        uint amountIn,
        uint amountOutMin,
        address[] memory path,
        address to,
        uint deadline
    ) public override {
        _delegate(address(swapper));
    }
    
    function swapETHForExactTokens(
        uint amountOut,
        address[] memory path,
        address to,
        uint deadline
    ) public override payable {
        _delegate(address(swapper));
    }
    
    //Materia Library interface
    
    function quote(
        uint amountA,
        uint reserveA,
        uint reserveB
    ) public pure override returns (uint amountB) {
        return MateriaLibrary.quote(amountA, reserveA, reserveB);
    }

    function getAmountOut(
        uint amountIn,
        uint reserveIn,
        uint reserveOut
    ) public pure override returns (uint amountOut) {
        return MateriaLibrary.getAmountOut(amountIn, reserveIn, reserveOut);
    }

    function getAmountIn(
        uint amountOut, 
        uint reserveIn, 
        uint reserveOut
    ) public pure override returns (uint amountIn) {
        return MateriaLibrary.getAmountIn(amountOut, reserveIn, reserveOut);
    }

    function getAmountsOut(
        uint amountIn,
        address[] memory path
    ) public view override returns (uint[] memory amounts) {
        return MateriaLibrary.getAmountsOut(address(factory), amountIn, path);
    }

    function getAmountsIn(
        uint amountOut,
        address[] memory path
    ) public view override returns (uint[] memory amounts) {
        return MateriaLibrary.getAmountsIn(address(factory), amountOut, path);
    }
    
    // Utilities for the interface
    
    function isEthItem(
        address token
    ) public view override returns(address collection, bool ethItem, uint256 itemId) {
        if (token == address(0)) {
            return(address(0), false, 0);
        } else {
           try IEthItemInteroperableInterface(token).mainInterface() returns(address mainInterface) {
                return(mainInterface, true, IEthItemInteroperableInterface(token).objectId());
           } catch {
                return(address(0), false, 0);
           }
        }
    }
}
function removeLiquidity(
    address token,
    uint liquidity,
    uint tokenAmountMin,
    uint bridgeAmountMin,
    address to,
    uint deadline
) public override {
    _delegate(address(liquidityRemover));
}

//Liquidity removing