contract RouterV2 is IConversionRouterV2, Context, Ownable, Initializable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    // operation
    address public optStore;
    uint256 public optStdId;
    address public optFactory;

    // constant
    address public wUST;
    address public aUST;

    // flags
    bool public isDepositAllowed;
    bool public isRedemptionAllowed;

    function setOperationStore(address _store) public onlyOwner {
        optStore = _store;
    }

    function setOperationId(uint256 _optStdId) public onlyOwner {
        optStdId = _optStdId;
    }

    function setOperationFactory(address _factory) public onlyOwner {
        optFactory = _factory;
    }

    function setDepositAllowance(bool _allow) public onlyOwner {
        isDepositAllowed = _allow;
    }

    function setRedemptionAllowance(bool _allow) public onlyOwner {
        isRedemptionAllowed = _allow;
    }

    function _init(
        IOperation.Type _typ,
        address _operator,
        uint256 _amount,
        address _swapper,
        address _swapDest,
        bool _autoFinish
    ) internal returns (address) {
        IOperationStore store = IOperationStore(optStore);
        if (store.getAvailableOperation() == address(0x0)) {
            address instance = IOperationFactory(optFactory).build(optStdId);
            store.allocate(instance);
        }
        IOperation operation = IOperation(store.init(_autoFinish));

        // check allowance
        if (IERC20(wUST).allowance(address(this), address(operation)) == 0) {
            IERC20(wUST).safeApprove(address(operation), type(uint256).max);
            IERC20(aUST).safeApprove(address(operation), type(uint256).max);
        }

        if (_typ == IOperation.Type.DEPOSIT) {
            IERC20(wUST).safeTransferFrom(_msgSender(), address(this), _amount);
            operation.initDepositStable(
                _operator,
                _amount,
                _swapper,
                _swapDest,
                _autoFinish
            );
            return address(operation);
        }

        if (_typ == IOperation.Type.REDEEM) {
            IERC20(aUST).safeTransferFrom(_msgSender(), address(this), _amount);
            operation.initRedeemStable(
                _operator,
                _amount,
                _swapper,
                _swapDest,
                _autoFinish
            );
            return address(operation);
        }

        revert("Router: invalid operation type");
    }

    function _finish(address _opt) internal {
        IOperationStore.Status status =
            IOperationStore(optStore).getStatusOf(_opt);

        if (status == IOperationStore.Status.RUNNING_MANUAL) {
            // check sender
            require(
                IOperation(_opt).getCurrentStatus().operator == _msgSender(),
                "Router: invalid sender"
            );
        } else {
            revert("Router: invalid status for finish");
        }

        IOperation(_opt).finish();
        IOperationStore(optStore).finish(_opt);
    }

    // =================================== COMMON =================================== //

    function init(
        IOperation.Type _type,
        address _operator,
        uint256 _amount,
        address _swapper,
        address _swapDest,
        bool _autoFinish
    ) public override returns (address) {
        return
            _init(_type, _operator, _amount, _swapper, _swapDest, _autoFinish);
    }

    function finish(address _operation) public override {
        return _finish(_operation);
    }

    // =================================== DEPOSIT STABLE =================================== //

    function depositStable(uint256 _amount) public override returns (address) {
        return
            _init(
                IOperation.Type.DEPOSIT,
                _msgSender(),
                _amount,
                address(0x0),
                address(0x0),
                true
            );
    }

    function depositStable(address _operator, uint256 _amount)
        public
        override
        returns (address)
    {
        return
            _init(
                IOperation.Type.DEPOSIT,
                _operator,
                _amount,
                address(0x0),
                address(0x0),
                true
            );
    }

    function depositStable(
        address _operator,
        uint256 _amount,
        address _swapper,
        address _swapDest
    ) public override returns (address) {
        return
            _init(
                IOperation.Type.DEPOSIT,
                _operator,
                _amount,
                _swapper,
                _swapDest,
                true
            );
    }

    function initDepositStable(uint256 _amount)
        public
        override
        returns (address)
    {
        return
            _init(
                IOperation.Type.DEPOSIT,
                _msgSender(),
                _amount,
                address(0x0),
                address(0x0),
                false
            );
    }

    function initDepositStable(
        uint256 _amount,
        address _swapper,
        address _swapDest
    ) public override returns (address) {
        return
            _init(
                IOperation.Type.DEPOSIT,
                _msgSender(),
                _amount,
                _swapper,
                _swapDest,
                false
            );
    }

    function finishDepositStable(address _operation) public override {
        _finish(_operation);
    }

    // =================================== REDEEM STABLE =================================== //

    function redeemStable(uint256 _amount) public override returns (address) {
        return
            _init(
                IOperation.Type.REDEEM,
                _msgSender(),
                _amount,
                address(0x0),
                address(0x0),
                true
            );
    }

    function redeemStable(address _operator, uint256 _amount)
        public
        override
        returns (address)
    {
        return
            _init(
                IOperation.Type.REDEEM,
                _operator,
                _amount,
                address(0x0),
                address(0x0),
                true
            );
    }

    function redeemStable(
        address _operator,
        uint256 _amount,
        address _swapper,
        address _swapDest
    ) public override returns (address) {
        return
            _init(
                IOperation.Type.REDEEM,
                _operator,
                _amount,
                _swapper,
                _swapDest,
                true
            );
    }

    function initRedeemStable(uint256 _amount)
        public
        override
        returns (address)
    {
        return
            _init(
                IOperation.Type.REDEEM,
                _msgSender(),
                _amount,
                address(0x0),
                address(0x0),
                false
            );
    }

    function initRedeemStable(
        uint256 _amount,
        address _swapper,
        address _swapDest
    ) public override returns (address) {
        return
            _init(
                IOperation.Type.REDEEM,
                _msgSender(),
                _amount,
                _swapper,
                _swapDest,
                false
            );
    }

    function finishRedeemStable(address _operation) public override {
        _finish(_operation);
    }
}
function depositStable(uint256 _amount) public override returns (address) {
    return
        _init(
            IOperation.Type.DEPOSIT,
            _msgSender(),
            _amount,
            address(0x0),
            address(0x0),
            true
        );
}

// =================================== DEPOSIT STABLE =================================== //