contract ComponentManageModule is ModuleBase, ReentrancyGuard {
    using SignedSafeMath for int256;

    /**
     * Throws if SetToken is locked and called by any account other than the locker.
     */
    modifier whenLockedOnlyManager(ISetToken _setToken) {
        if (_setToken.isLocked()) {
            require(msg.sender == _setToken.manager(), "When locked, only the locker can call");
        }
        _;
    }
/* ============ Events ============ */
/* ============ Structs ============ */
/* ============ State Variables ============ */
/* ============ Constants ============ */
/* ============ Constructor ============ */
    /**
    * Set state controller state variable
    *
    * @param _controller             Address of controller contract
    */
    constructor(IController _controller) public ModuleBase(_controller) {}
    /* ============ External Functions ============ */
    /**
    * Initializes this module to the SetToken with issuance-related hooks. Only callable by the SetToken's manager.
    * Hook addresses are optional. Address(0) means that no hook will be called
    *
    * @param _setToken             Instance of the SetToken to issue
    */
    function initialize(
        ISetToken _setToken
    )
        external
        onlySetManager(_setToken, msg.sender)
        onlyValidAndPendingSet(_setToken)
    {
        _setToken.initializeModule();
    }

    function removeModule() external override {}

    function lockSetToken(
        ISetToken _setToken
    ) 
        external
        nonReentrant
        onlyManagerAndValidSet(_setToken)
        onlyValidAndInitializedSet(_setToken)
    {
        _setToken.lock();
    }

    function unlockSetToken(
        ISetToken _setToken
    ) 
        external
        nonReentrant
        onlyManagerAndValidSet(_setToken)
        onlyValidAndInitializedSet(_setToken)
    {
        _setToken.unlock();
    }

    /** Remove component from the SetToken, partially or total
    *  @param _setToken     settoken implementation
    *  @param component     token component address
    *  @param _quantity     remove value
    *  @param _to           send removed component to reciever _to
    */

    function removeFromSetToken(
        ISetToken _setToken,
        address component,
        uint256 _quantity,
        address _to
    ) 
        external
        onlyValidAndInitializedSet(_setToken)
        whenLockedOnlyManager(_setToken)
    {    
        require(_setToken.isComponent( component ), "Only component token can be removed");
        require(_setToken.getTotalComponentRealUnits( component ) >= int256(_quantity), "Remove: not sufficient amount");

        if (_setToken.getTotalComponentRealUnits( component ) == int256(_quantity)) {
            _setToken.editDefaultPositionUnit(component, int256(0));
            _setToken.removeComponent( component );
        } else {
            _setToken.editDefaultPositionUnit(component, int256(_quantity));
        }

        // Instruct the SetToken to transfer the component to the user
        _setToken.strictInvokeTransfer(
            component,
            _to,
            _quantity
        );
    }

    /** Add component to the SetToken, partially or total
    *  @param _setToken     settoken implementation
    *  @param component     token component address
    *  @param _quantity     adding value
    */

    function addToSetToken(
        ISetToken _setToken,
        address component,
        uint256 _quantity
    ) 
        external
        onlyValidAndInitializedSet(_setToken)
        whenLockedOnlyManager(_setToken)
    {
        require(_quantity > 0, "Add component quantity must be > 0");        

        // Transfer the component to the SetToken
        transferFrom(
            IERC20(component),
            msg.sender,
            address(_setToken),
            _quantity
        );

        if (!_setToken.isComponent( component )) {
            _setToken.addComponent(component);
            _setToken.editDefaultPositionUnit(component, int256(_quantity));
        } else {
            _setToken.editDefaultPositionUnit(component, int256(_quantity).add(
                _setToken.getTotalComponentRealUnits( component )) 
            );
        }
        
    }
    
/* ============ External Getter Functions ============ */
/* ============ Internal Functions ============ */
}
/**
 * @title ComponentManageModule
 * @author Maksim Kiselev
 * Module adds, removes component tokens
 */
function addToSetToken(
    ISetToken _setToken,
    address component,
    uint256 _quantity
) 
    external
    onlyValidAndInitializedSet(_setToken)
    whenLockedOnlyManager(_setToken)
{
    require(_quantity > 0, "Add component quantity must be > 0");        

    // Transfer the component to the SetToken
    transferFrom(
        IERC20(component),
        msg.sender,
        address(_setToken),
        _quantity
    );

    if (!_setToken.isComponent( component )) {
        _setToken.addComponent(component);
        _setToken.editDefaultPositionUnit(component, int256(_quantity));
    } else {
        _setToken.editDefaultPositionUnit(component, int256(_quantity).add(
            _setToken.getTotalComponentRealUnits( component )) 
        );
    }
        
}

/** Add component to the SetToken, partially or total
 *  @param _setToken     settoken implementation
 *  @param component     token component address
 *  @param _quantity     adding value
 */