contract Orchestrator is Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    struct Transaction {
        bool enabled;
        address destination;
        bytes data;
    }

    event TransactionFailed(address indexed destination, uint index, bytes data);
    event BoostUp(address requester, uint256 rebaseLag, uint256 amount);
    event BoostDown(address requester, uint256 rebaseLag, uint256 amount);

    // Stable ordering is not guaranteed.
    Transaction[] public transactions;

    UFragmentsPolicy public policy;

    address private feeWallet;
    uint256 public feeUnit = 0.1 ether;

    uint256 public boost;
    uint256 public maxRebaseLag;
    uint256 public minRebaseLag;
    uint256 public initRebaseLag;

    /**
     * @param policy_ Address of the UFragments policy.
     */
    constructor(address policy_, address feeWallet_) public {
        Ownable.initialize(msg.sender);
        policy = UFragmentsPolicy(policy_);
        boost = 1;
        maxRebaseLag = 15;
        minRebaseLag = 5;
        initRebaseLag = 10;
        feeWallet = feeWallet_;
    }

    /**
     * @notice Main entry point to initiate a rebase operation.
     *         The Orchestrator calls rebase on the policy and notifies downstream applications.
     *         Contracts are guarded from calling, to avoid flash loan attacks on liquidity
     *         providers.
     *         If a transaction in the transaction list reverts, it is swallowed and the remaining
     *         transactions are executed.
     */
    function rebase()
        external
    {
        require(msg.sender == tx.origin);  // solhint-disable-line avoid-tx-origin

        policy.rebase();
        policy.setRebaseLag(initRebaseLag);
        boost = 1;

        for (uint i = 0; i < transactions.length; i++) {
            Transaction storage t = transactions[i];
            if (t.enabled) {
                bool result =
                    externalCall(t.destination, t.data);
                if (!result) {
                    emit TransactionFailed(t.destination, i, t.data);
                    revert("Transaction Failed");
                }
            }
        }
    }

    /**
     * @notice Adds a transaction that gets called for a downstream receiver of rebases
     * @param destination Address of contract destination
     * @param data Transaction data payload
     */
    function addTransaction(address destination, bytes data)
        external
        onlyOwner
    {
        transactions.push(Transaction({
            enabled: true,
            destination: destination,
            data: data
        }));
    }

    /**
     * @param index Index of transaction to remove.
     *              Transaction ordering may have changed since adding.
     */
    function removeTransaction(uint index)
        external
        onlyOwner
    {
        require(index < transactions.length, "index out of bounds");

        if (index < transactions.length - 1) {
            transactions[index] = transactions[transactions.length - 1];
        }

        transactions.length--;
    }

    /**
     * @param index Index of transaction. Transaction ordering may have changed since adding.
     * @param enabled True for enabled, false for disabled.
     */
    function setTransactionEnabled(uint index, bool enabled)
        external
        onlyOwner
    {
        require(index < transactions.length, "index must be in range of stored tx list");
        transactions[index].enabled = enabled;
    }

    /**
     * @return Number of transactions, both enabled and disabled, in transactions list.
     */
    function transactionsSize()
        external
        view
        returns (uint256)
    {
        return transactions.length;
    }

    /**
     * @dev wrapper to call the encoded transactions on downstream consumers.
     * @param destination Address of destination contract.
     * @param data The encoded data payload.
     * @return True on success
     */
    function externalCall(address destination, bytes data)
        internal
        returns (bool)
    {
        bool result;
        assembly {  // solhint-disable-line no-inline-assembly
            // "Allocate" memory for output
            // (0x40 is where "free memory" pointer is stored by convention)
            let outputAddress := mload(0x40)

            // First 32 bytes are the padded length of data, so exclude that
            let dataAddress := add(data, 32)

            result := call(
                // 34710 is the value that solidity is currently emitting
                // It includes callGas (700) + callVeryLow (3, to pay for SUB)
                // + callValueTransferGas (9000) + callNewAccountGas
                // (25000, in case the destination address does not exist and needs creating)
                sub(gas, 34710),


                destination,
                0, // transfer value in wei
                dataAddress,
                mload(data),  // Size of the input, in bytes. Stored in position 0 of the array.
                outputAddress,
                0  // Output is ignored, therefore the output size is zero
            )
        }
        return result;
    }

    function boostUp() external payable {
        uint256 rebaseLag = policy.getRebaseLag();
        require(rebaseLag.add(1) <= maxRebaseLag, 'rebaseLag is over max');
        require(msg.value == feeUnit.mul(boost), 'Invalid amount');

        feeWallet.transfer(msg.value);
        
        boost = boost.add(1);
        policy.setRebaseLag(rebaseLag.add(1));

        emit BoostUp(msg.sender, rebaseLag.add(1), msg.value);
    }

    function boostDown() external payable {
        uint256 rebaseLag = policy.getRebaseLag();
        require(rebaseLag.sub(1) >= minRebaseLag, 'rebaseLag is under min');
        require(msg.value == feeUnit.mul(boost), 'Invalid amount');

        feeWallet.transfer(msg.value);
        boost = boost.add(1);
        policy.setRebaseLag(rebaseLag.sub(1));

        emit BoostUp(msg.sender, rebaseLag.sub(1), msg.value);
    }

    function setRebaseLag(uint256 _initRebaseLag, uint256 _maxRebaseLag, uint256 _minRebaseLag)
        external
        onlyOwner
    {
        initRebaseLag = _initRebaseLag;
        maxRebaseLag = _maxRebaseLag;
        minRebaseLag = _minRebaseLag;
    }

    function setFeeWallet(address _wallet) external onlyOwner {
        feeWallet = _wallet;
    }
}
/**
 * @title Orchestrator
 * @notice The orchestrator is the main entry point for rebase operations. It coordinates the policy
 * actions with external consumers.
 */
function transactionsSize()
    external
    view
    returns (uint256)
{
    return transactions.length;
}

/**
 * @return Number of transactions, both enabled and disabled, in transactions list.
 */