contract Component is ComponentStorage {

    event Approval(address indexed _owner, address indexed spender, uint256 value);

    event ParametersSet(uint256 alpha, uint256 beta, uint256 delta, uint256 epsilon, uint256 lambda);

    event AssetIncluded(address indexed numeraire, address indexed reserve, uint weight);

    event AssimilatorIncluded(address indexed derivative, address indexed numeraire, address indexed reserve, address assimilator);

    event PartitionRedeemed(address indexed token, address indexed redeemer, uint value);

    event PoolPartitioned(bool partitioned);

    event OwnershipTransfered(address indexed previousOwner, address indexed newOwner);

    event FrozenSet(bool isFrozen);

    event Trade(address indexed trader, address indexed origin, address indexed target, uint256 originAmount, uint256 targetAmount);

    event Transfer(address indexed from, address indexed to, uint256 value);

    IFreeFromUpTo public constant chi = IFreeFromUpTo(0x0000000000004946c0e9F43F4Dee607b0eF1fA1c);

    modifier discountCHI {
        uint256 gasStart = gasleft();
        _;
        uint256 gasSpent = 21000 + gasStart - gasleft() + 16 * msg.data.length;
        chi.freeFromUpTo(msg.sender, (gasSpent + 14154) / 41130);
    }

    modifier onlyOwner() {

        require(msg.sender == owner, "Component/caller-is-not-owner");
        _;

    }

    modifier nonReentrant() {

        require(notEntered, "Component/re-entered");
        notEntered = false;
        _;
        notEntered = true;

    }

    modifier transactable () {

        require(!frozen, "Component/frozen-only-allowing-proportional-withdraw");
        _;

    }

    modifier unpartitioned () {

        require(!partitioned, "Component/pool-partitioned");
        _;

    }

    modifier isPartitioned () {

        require(partitioned, "Component/pool-not-partitioned");
        _;

    }

    modifier deadline (uint _deadline) {

        require(block.timestamp < _deadline, "Component/tx-deadline-passed");
        _;

    }

    constructor (
        address[] memory _assets,
        uint[] memory _assetWeights,
        address[] memory _derivativeAssimilators
    ) public {

        owner = msg.sender;
        emit OwnershipTransfered(address(0), msg.sender);

        Orchestrator.initialize(
            component,
            numeraires,
            reserves,
            derivatives,
            _assets,
            _assetWeights,
            _derivativeAssimilators
        );

    }

    /// @notice sets the parameters for the pool
    /// @param _alpha the value for alpha (halt threshold) must be less than or equal to 1 and greater than 0
    /// @param _beta the value for beta must be less than alpha and greater than 0
    /// @param _feeAtHalt the maximum value for the fee at the halt point
    /// @param _epsilon the base fee for the pool
    /// @param _lambda the value for lambda must be less than or equal to 1 and greater than zero
    /// @param _sigma the protocol fee for the pool
    /// @param _protocol the protocol fee distribution address
    function setParams (
        uint _alpha,
        uint _beta,
        uint _feeAtHalt,
        uint _epsilon,
        uint _lambda,
        uint _sigma,
        address _protocol
    ) external onlyOwner {

        Orchestrator.setParams(component, _alpha, _beta, _feeAtHalt, _epsilon, _lambda, _sigma, _protocol);

    }

    /// @notice excludes an assimilator from the component
    /// @param _derivative the address of the assimilator to exclude
    function excludeDerivative (
        address _derivative
    ) external onlyOwner {

        for (uint i = 0; i < numeraires.length; i++) {

            if (_derivative == numeraires[i]) revert("Component/cannot-delete-numeraire");
            if (_derivative == reserves[i]) revert("Component/cannot-delete-reserve");

        }

        delete component.assimilators[_derivative];

    }

    /// @notice view the current parameters of the component
    /// @return alpha_ the current alpha value
    /// @return beta_ the current beta value
    /// @return delta_ the current delta value
    /// @return epsilon_ the current epsilon value
    /// @return lambda_ the current lambda value
    /// @return omega_ the current omega value
    function viewComponent() external view returns (
        uint alpha_,
        uint beta_,
        uint delta_,
        uint epsilon_,
        uint lambda_
    ) {

        return Orchestrator.viewComponent(component);

    }

    function setFrozen (bool _toFreezeOrNotToFreeze) external onlyOwner {

        emit FrozenSet(_toFreezeOrNotToFreeze);

        frozen = _toFreezeOrNotToFreeze;

    }

    function transferOwnership (address _newOwner) external onlyOwner {

        emit OwnershipTransfered(owner, _newOwner);

        owner = _newOwner;

    }

    /// @author james foley http://github.com/realisation
    /// @notice swap a dynamic origin amount for a fixed target amount
    /// @param _origin the address of the origin
    /// @param _target the address of the target
    /// @param _originAmount the origin amount
    /// @param _minTargetAmount the minimum target amount
    /// @param _deadline deadline in block number after which the trade will not execute
    /// @return targetAmount_ the amount of target that has been swapped for the origin amount
    function originSwap (
        address _origin,
        address _target,
        uint _originAmount,
        uint _minTargetAmount,
        uint _deadline
    ) external deadline(_deadline) transactable nonReentrant returns (
        uint targetAmount_
    ) {

        targetAmount_ = Swaps.originSwap(component, _origin, _target, _originAmount, msg.sender);

        require(targetAmount_ > _minTargetAmount, "Component/below-min-target-amount");

    }

    function originSwapDiscountCHI (
        address _origin,
        address _target,
        uint _originAmount,
        uint _minTargetAmount,
        uint _deadline
    ) external deadline(_deadline) transactable nonReentrant discountCHI returns (
        uint targetAmount_
    ) {

        targetAmount_ = Swaps.originSwap(component, _origin, _target, _originAmount, msg.sender);

        require(targetAmount_ > _minTargetAmount, "Component/below-min-target-amount");

    }

    /// @author james foley http://github.com/realisation
    /// @notice view how much target amount a fixed origin amount will swap for
    /// @param _origin the address of the origin
    /// @param _target the address of the target
    /// @param _originAmount the origin amount
    /// @return targetAmount_ the target amount that would have been swapped for the origin amount
    function viewOriginSwap (
        address _origin,
        address _target,
        uint _originAmount
    ) external view transactable returns (
        uint targetAmount_
    ) {

        targetAmount_ = Swaps.viewOriginSwap(component, _origin, _target, _originAmount);

    }

    /// @author james foley http://github.com/realisation
    /// @notice swap a dynamic origin amount for a fixed target amount
    /// @param _origin the address of the origin
    /// @param _target the address of the target
    /// @param _maxOriginAmount the maximum origin amount
    /// @param _targetAmount the target amount
    /// @param _deadline deadline in block number after which the trade will not execute
    /// @return originAmount_ the amount of origin that has been swapped for the target
    function targetSwap (
        address _origin,
        address _target,
        uint _maxOriginAmount,
        uint _targetAmount,
        uint _deadline
    ) external deadline(_deadline) transactable nonReentrant returns (
        uint originAmount_
    ) {

        originAmount_ = Swaps.targetSwap(component, _origin, _target, _targetAmount, msg.sender);

        require(originAmount_ < _maxOriginAmount, "Component/above-max-origin-amount");

    }

    /// @author james foley http://github.com/realisation
    /// @notice view how much of the origin currency the target currency will take
    /// @param _origin the address of the origin
    /// @param _target the address of the target
    /// @param _targetAmount the target amount
    /// @return originAmount_ the amount of target that has been swapped for the origin
    function viewTargetSwap (
        address _origin,
        address _target,
        uint _targetAmount
    ) external view transactable returns (
        uint originAmount_
    ) {

        originAmount_ = Swaps.viewTargetSwap(component, _origin, _target, _targetAmount);

    }

    /// @author james foley http://github.com/realisation
    /// @notice selectively deposit any supported stablecoin flavor into the contract in return for corresponding amount of component tokens
    /// @param _derivatives an array containing the addresses of the flavors being deposited into
    /// @param _amounts an array containing the values of the flavors you wish to deposit into the contract. each amount should have the same index as the flavor it is meant to deposit
    /// @param _minComponents minimum acceptable amount of components
    /// @param _deadline deadline for tx
    /// @return componentsToMint_ the amount of components to mint for the deposited stablecoin flavors
    function selectiveDeposit (
        address[] calldata _derivatives,
        uint[] calldata _amounts,
        uint _minComponents,
        uint _deadline
    ) external deadline(_deadline) transactable nonReentrant returns (
        uint componentsMinted_
    ) {

        componentsMinted_ = SelectiveLiquidity.selectiveDeposit(component, _derivatives, _amounts, _minComponents);

    }

    /// @author james folew http://github.com/realisation
    /// @notice view how many component tokens a deposit will mint
    /// @param _derivatives an array containing the addresses of the flavors being deposited into
    /// @param _amounts an array containing the values of the flavors you wish to deposit into the contract. each amount should have the same index as the flavor it is meant to deposit
    /// @return componentsToMint_ the amount of components to mint for the deposited stablecoin flavors
    function viewSelectiveDeposit (
        address[] calldata _derivatives,
        uint[] calldata _amounts
    ) external view transactable returns (
        uint componentsToMint_
    ) {

        componentsToMint_ = SelectiveLiquidity.viewSelectiveDeposit(component, _derivatives, _amounts);

    }

    /// @author james foley http://github.com/realisation
    /// @notice deposit into the pool with no slippage from the numeraire assets the pool supports
    /// @param  _deposit the full amount you want to deposit into the pool which will be divided up evenly amongst the numeraire assets of the pool
    /// @return componentsToMint_ the amount of components you receive in return for your deposit
    /// @return deposits_ the amount deposited for each numeraire
    function proportionalDeposit (
        uint _deposit,
        uint _deadline
    ) external deadline(_deadline) transactable nonReentrant returns (
        uint componentsMinted_,
        uint[] memory deposits_
    ) {

        return ProportionalLiquidity.proportionalDeposit(component, _deposit);

    }

    /// @author james foley http://github.com/realisation
    /// @notice view deposits and components minted a given deposit would return
    /// @param _deposit the full amount of stablecoins you want to deposit. Divided evenly according to the prevailing proportions of the numeraire assets of the pool
    /// @return componentsToMint_ the amount of components you receive in return for your deposit
    /// @return deposits_ the amount deposited for each numeraire
    function viewProportionalDeposit (
        uint _deposit
    ) external view transactable returns (
        uint componentsToMint_,
        uint[] memory depositsToMake_
    ) {

        return ProportionalLiquidity.viewProportionalDeposit(component, _deposit);

    }

    /// @author james foley http://github.com/realisation
    /// @notice selectively withdrawal any supported stablecoin flavor from the contract by burning a corresponding amount of component tokens
    /// @param _derivatives an array of flavors to withdraw from the reserves
    /// @param _amounts an array of amounts to withdraw that maps to _flavors
    /// @param _maxComponents the maximum amount of components you want to burn
    /// @param _deadline timestamp after which the transaction is no longer valid
    /// @return componentsBurned_ the corresponding amount of component tokens to withdraw the specified amount of specified flavors
    function selectiveWithdraw (
        address[] calldata _derivatives,
        uint[] calldata _amounts,
        uint _maxComponents,
        uint _deadline
    ) external deadline(_deadline) transactable nonReentrant returns (
        uint componentsBurned_
    ) {

        componentsBurned_ = SelectiveLiquidity.selectiveWithdraw(component, _derivatives, _amounts, _maxComponents);

    }

    /// @author james foley http://github.com/realisation
    /// @notice view how many component tokens a withdraw will consume
    /// @param _derivatives an array of flavors to withdraw from the reserves
    /// @param _amounts an array of amounts to withdraw that maps to _flavors
    /// @return componentsBurned_ the corresponding amount of component tokens to withdraw the specified amount of specified flavors
    function viewSelectiveWithdraw (
        address[] calldata _derivatives,
        uint[] calldata _amounts
    ) external view transactable returns (
        uint componentsToBurn_
    ) {

        componentsToBurn_ = SelectiveLiquidity.viewSelectiveWithdraw(component, _derivatives, _amounts);

    }

    /// @author  james foley http://github.com/realisation
    /// @notice  withdrawas amount of component tokens from the the pool equally from the numeraire assets of the pool with no slippage
    /// @param   _componentsToBurn the full amount you want to withdraw from the pool which will be withdrawn from evenly amongst the numeraire assets of the pool
    /// @return withdrawals_ the amonts of numeraire assets withdrawn from the pool
    function proportionalWithdraw (
        uint _componentsToBurn,
        uint _deadline
    ) external deadline(_deadline) unpartitioned nonReentrant returns (
        uint[] memory withdrawals_
    ) {

        return ProportionalLiquidity.proportionalWithdraw(component, _componentsToBurn);

    }

    function supportsInterface (
        bytes4 _interface
    ) public pure returns (
        bool supports_
    ) {

        supports_ = this.supportsInterface.selector == _interface  // erc165
            || bytes4(0x7f5828d0) == _interface                   // eip173
            || bytes4(0x36372b07) == _interface;                 // erc20

    }

    /// @author  james foley http://github.com/realisation
    /// @notice  withdrawals amount of component tokens from the the pool equally from the numeraire assets of the pool with no slippage
    /// @param   _componentsToBurn the full amount you want to withdraw from the pool which will be withdrawn from evenly amongst the numeraire assets of the pool
    /// @return withdrawalsToHappen_ the amonts of numeraire assets withdrawn from the pool
    function viewProportionalWithdraw (
        uint _componentsToBurn
    ) external view unpartitioned returns (
        uint[] memory withdrawalsToHappen_
    ) {

        return ProportionalLiquidity.viewProportionalWithdraw(component, _componentsToBurn);

    }

    function partition () external onlyOwner {

        require(frozen, "Component/must-be-frozen");

        PartitionedLiquidity.partition(component, partitionTickets);

        partitioned = true;

    }

    /// @author  james foley http://github.com/realisation
    /// @notice  withdraws amount of component tokens from the the pool equally from the numeraire assets of the pool with no slippage
    /// @param _tokens an array of the numeraire assets you will withdraw
    /// @param _amounts an array of the amounts in terms of partitioned shels you want to withdraw from that numeraire partition
    /// @return withdrawals_ the amounts of the numeraire assets withdrawn
    function partitionedWithdraw (
        address[] calldata _tokens,
        uint256[] calldata _amounts
    ) external isPartitioned returns (
        uint256[] memory withdrawals_
    ) {

        return PartitionedLiquidity.partitionedWithdraw(component, partitionTickets, _tokens, _amounts);

    }

    /// @author  james foley http://github.com/realisation
    /// @notice  views the balance of the users partition ticket
    /// @param _addr the address whose balances in partitioned components to be seen
    /// @return claims_ the remaining claims in terms of partitioned components the address has in its partition ticket
    function viewPartitionClaims (
        address _addr
    ) external view isPartitioned returns (
        uint[] memory claims_
    ) {

        return PartitionedLiquidity.viewPartitionClaims(component, partitionTickets, _addr);

    }

    /// @notice transfers component tokens
    /// @param _recipient the address of where to send the component tokens
    /// @param _amount the amount of component tokens to send
    /// @return success_ the success bool of the call
    function transfer (
        address _recipient,
        uint _amount
    ) public nonReentrant returns (
        bool success_
    ) {

        require(!partitionTickets[msg.sender].initialized, "Component/no-transfers-once-partitioned");

        success_ = Components.transfer(component, _recipient, _amount);

    }

    /// @notice transfers component tokens from one address to another address
    /// @param _sender the account from which the component tokens will be sent
    /// @param _recipient the account to which the component tokens will be sent
    /// @param _amount the amount of component tokens to transfer
    /// @return success_ the success bool of the call
    function transferFrom (
        address _sender,
        address _recipient,
        uint _amount
    ) public nonReentrant returns (
        bool success_
    ) {

        require(!partitionTickets[_sender].initialized, "Component/no-transfers-once-partitioned");

        success_ = Components.transferFrom(component, _sender, _recipient, _amount);

    }

    /// @notice approves a user to spend component tokens on their behalf
    /// @param _spender the account to allow to spend from msg.sender
    /// @param _amount the amount to specify the spender can spend
    /// @return success_ the success bool of this call
    function approve (address _spender, uint _amount) public nonReentrant returns (bool success_) {

        success_ = Components.approve(component, _spender, _amount);

    }

    /// @notice view the component token balance of a given account
    /// @param _account the account to view the balance of
    /// @return balance_ the component token ballance of the given account
    function balanceOf (
        address _account
    ) public view returns (
        uint balance_
    ) {

        balance_ = component.balances[_account];

    }

    /// @notice views the total component supply of the pool
    /// @return totalSupply_ the total supply of component tokens
    function totalSupply () public view returns (uint totalSupply_) {

        totalSupply_ = component.totalSupply;

    }

    /// @notice views the total allowance one address has to spend from another address
    /// @param _owner the address of the owner
    /// @param _spender the address of the spender
    /// @return allowance_ the amount the owner has allotted the spender
    function allowance (
        address _owner,
        address _spender
    ) public view returns (
        uint allowance_
    ) {

        allowance_ = component.allowances[_owner][_spender];

    }

    /// @notice views the total amount of liquidity in the component in numeraire value and format - 18 decimals
    /// @return total_ the total value in the component
    /// @return individual_ the individual values in the component
    function liquidity () public view returns (
        uint total_,
        uint[] memory individual_
    ) {

        return ViewLiquidity.viewLiquidity(component);

    }

    /// @notice view the assimilator address for a derivative
    /// @return assimilator_ the assimilator address
    function assimilator (
        address _derivative
    ) public view returns (
        address assimilator_
    ) {

        assimilator_ = component.assimilators[_derivative].addr;

    }

}
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
function assimilator (
    address _derivative
) public view returns (
    address assimilator_
) {

    assimilator_ = component.assimilators[_derivative].addr;

}

/// @notice view the assimilator address for a derivative
/// @return assimilator_ the assimilator address