contract MultistageCrowdsale is DealsSupport, Ownable {
    using SafeMath for uint256;

    /**
     * Event for token purchase logging
     * @param purchaser who paid for the tokens
     * @param affiliate address, if any
     * @param value weis paid for purchase
     * @param amount amount of tokens purchased
     * @param orderID to be used with fiat payments
     */
    event TokenPurchase(address indexed purchaser, address indexed affiliate, uint256 value, uint256 amount, bytes4 indexed orderID);

    struct Stage {
        uint32 time;
        uint64 rate;
        uint256 minInv;
        uint256 maxInv;
    }

    struct Timelock {
        uint256 timestamp;
        uint8 amount;
        uint8 relative;
    }

    Stage[] stages;
    Timelock[] timeLocks;

    mapping(address => uint256) invested;

    address wallet;
    address token;
    address signer;
    uint32 saleEndTime;
    bool tokenIsTILM;

    /**
     * @dev The constructor that takes all parameters
     * @param _timesAndRates An array that defines the stages of the contract. the first entry being the start time of the sale, followed by pairs of rates ond close times of consequitive stages.
     *             Example 1: [10000, 99, 12000]
     *                 A single stage sale that starts at unix time 10000 and ends 2000 seconds later.
     *                 This sale gives 99 tokens for each Gwei invested.
     *             Example 2: [10000, 99, 12000, 88, 14000]
     *                 A 2 stage sale that starts at unix time 10000 and ends 4000 seconds later.
     *                 The sale reduces the rate at mid time
     *                 This sale gives 99 tokens for each Gwei invested in first stage.
     *                 The sale gives 88 tokens for each Gwei invested in second stage.
     * @param _wallet The address of the wallet where invested Ether will be send to
     * @param _token The tokens that the investor will receive
     * @param _signer The address of the key that whitelists investor (operator key)
     */
    constructor(
        uint256[] _timesAndRates,
        address _wallet,
        address _token,
        address _signer,
        address _admin,
        address _merchant
    )
        public
        DealsSupport(_admin, _merchant)
    {
        require(_wallet != address(0), "Wallet address can not be 0.");
        require(_token != address(0), "Token address can not be 0.");

        storeStages(_timesAndRates);

        saleEndTime = uint32(_timesAndRates[_timesAndRates.length - 1]);
        // check sale ends after last stage opening time
        require(saleEndTime > stages[stages.length - 1].time, "Sale end time should be after last stage opening time.");

        wallet = _wallet;
        token = _token;
        signer = _signer;
        tokenIsTILM = false;
    }

    function setTokenIsTilm(bool _isTilm) public onlyOwner {
        require(tokenIsTILM != _isTilm, "That is the current tokenIsTILM status");
        tokenIsTILM = _isTilm;
    }

    function getTokenIsTilm() public view returns (bool isTilm) {
        return (tokenIsTILM);
    }

    function setTimelockLogic(uint256[] _timeLocks) public onlyOwner {
        require(_timeLocks.length % 3 == 0, "Wrong number of parameters in timeLocks.");
        delete timeLocks;
        for (uint256 i = 0; i < _timeLocks.length / 3; i++) {
            require(_timeLocks[(i * 3) + 2]==0 || _timeLocks[(i * 3) + 2]==1, "Only 0 or 1 are valid values for relative flag");
            timeLocks.push(Timelock(uint256(_timeLocks[i * 3]), uint8(_timeLocks[(i * 3) + 1]), uint8(_timeLocks[(i * 3) + 2])));
        }
    }

    function getTimelockLogic() public view returns (uint256[] _timeLocks) {
        _timeLocks = new uint256[](timeLocks.length * 3);
        for (uint256 i = 0; i < timeLocks.length; i++) {
            _timeLocks[i * 3] = uint256(timeLocks[i].timestamp);
            _timeLocks[i * 3 + 1] = uint256(timeLocks[i].amount);
            _timeLocks[i * 3 + 2] = uint256(timeLocks[i].relative);
        }
        return (_timeLocks);
    }

    function setMinInvestmentAtStage(uint32 _stage, uint256 _value) public onlyOwner {
        require(_stage < stages.length, "Stage does not exist");
        require(_value != getMinInvAtStage(_stage),"That is the current minimum investment");
        require(getMaxInvAtStage(_stage) == 0 || _value <= getMaxInvAtStage(_stage),"Minimum should be lower than the maximum investment when it is not 0");
        stages[_stage].minInv = _value;
    }

    function setMaxInvestmentAtStage(uint32 _stage, uint256 _value) public onlyOwner {
        require(_stage < stages.length, "Stage does not exist");
        require(_value != getMaxInvAtStage(_stage),"That is the current minimum investment");
        require(_value == 0 || _value >= getMinInvAtStage(_stage),"Maximum should be either 0, or higher than the minimum investment");
        stages[_stage].maxInv = _value;
    }

    /**
     * @dev called by investors to purchase tokens
     * @param _r part of receipt signature
     * @param _s part of receipt signature
     * @param _a first payload of signed receipt.
     * @param _b second payload of signed receipt.
     *     The receipt commits to the follwing inputs:
     *         56 bits - sale contract address, to prevent replay of receipt
     *         32 bits - orderID for fiat payments
     *         160 bits - beneficiary address - address whitelisted to receive tokens
     *         32 bits - time - when receipt was signed
     *         64 bits - outOfBandPaymentAmount - out of band payment amount, for fiat investments
     *         32 bits - reference hash
     */

    function invest(bytes32 _r, bytes32 _s, bytes32 _a, bytes32 _b) public payable {
        // parse inputs
        uint32 time = uint32(_b >> 224);
        address beneficiary = address(_a);
        uint256 outOfBandPaymentAmount = uint64(_b >> 160);
        uint256 referenceHash = uint256(_b << 96 >> 224);

        // verify inputs
        require(uint56(_a >> 192) == uint56(this), "Destination contract address does not match this contract address.");
        if (outOfBandPaymentAmount == 0) {
            outOfBandPaymentAmount = msg.value;
        } else {
            outOfBandPaymentAmount = outOfBandPaymentAmount.mul(1000000000);    // convert outOfBandPaymentAmount to wei
        }
        bytes4 orderID = bytes4(uint32(_a >> 160));
        /* solium-disable-next-line arg-overflow */
        require(ecrecover(keccak256(abi.encodePacked(uint8(0), uint248(_a), _b)), uint8(_a >> 248), _r, _s) == signer, "Signer address not matching.");
        require(beneficiary != address(0), "Beneficiary can not be 0.");

        // check investment is above current minimum investment
        require(outOfBandPaymentAmount>=getMinInvAt(now),"Investment below minimum required");
        // check investment is under above current maximum investment
        require(outOfBandPaymentAmount+invested[beneficiary]<=getMaxInvAt(now) || getMaxInvAt(now)==0,"Investment over maximum allowed in current stage");
        // calculate token amount to be created
        uint256 rate = getRateAt(now); // solium-disable-line security/no-block-members
        // at the time of signing the receipt the rate should have been the same as now
        require(rate == getRateAt(time), "rate not matching current stage rate.");
        // multiply rate with Gwei of investment
        // uint256 purchasedTokenAmount = rate.mul(outOfBandPaymentAmount).div(1000000000);
        // multiply rate with Gwei of investment and ceil to avoid weird amounts
        uint256 purchasedTokenAmount = ceil(rate.mul(outOfBandPaymentAmount).div(1000000000), 10**3);
        // check that msg.value > 0
        require(purchasedTokenAmount > 0, "purchasedTokenAmount can not be 0.");

        // pocket Ether
        if (msg.value > 0) {
            wallet.transfer(outOfBandPaymentAmount);
        }

        // if user came from affiliate then reward affiliate
        address affiliate = address(0);
        if (referenceHash != 0) {
            affiliate = affiliates[referenceHash].affiliateAddress;
            _rewardAffiliate(referenceHash, purchasedTokenAmount);
        }

        // do token transfer
        invested[beneficiary] += outOfBandPaymentAmount;
        // send tokens and emit event
        sendTokens(beneficiary, purchasedTokenAmount);
        emit TokenPurchase(beneficiary, affiliate, outOfBandPaymentAmount, purchasedTokenAmount, orderID);
    }

    function sendTokens(address _beneficiary, uint256 _amount) internal {
        // if the token is not TILM or the logic is not defined, normal transfer
        if(!tokenIsTILM || timeLocks.length == 0) {
            ERC20(token).transferFrom(wallet, _beneficiary, _amount);
        } else {
            // transfer and lock according to logic defined in variables "amounts" and "locktimes"
            uint256[] memory purchasedLockAmounts = new uint256[](timeLocks.length);
            uint256[] memory lockTimes = new uint256[](timeLocks.length);
            for(uint256 i = 0; i<timeLocks.length; i++){
                purchasedLockAmounts[i] = _amount * timeLocks[i].amount / 100;
                lockTimes[i] = timeLocks[i].relative * now + timeLocks[i].timestamp;
            }
            LockedController(token).transferListOfLockedFundsFrom(wallet, _beneficiary, purchasedLockAmounts, lockTimes);
        }
    }

    function getParams() public view returns (uint256[] _times, uint256[] _rates, uint256[] _minInvs, uint256[] _maxInvs, address _wallet, address _token, address _signer) {
        _times = new uint256[](stages.length + 1);
        _rates = new uint256[](stages.length);
        _minInvs = new uint256[](stages.length);
        _maxInvs = new uint256[](stages.length);
        for (uint256 i = 0; i < stages.length; i++) {
            _times[i] = stages[i].time;
            _rates[i] = stages[i].rate;
            _minInvs[i] = stages[i].minInv;
            _maxInvs[i] = stages[i].maxInv;
        }
        _times[stages.length] = saleEndTime;
        _wallet = wallet;
        _token = token;
        _signer = signer;
    }

    function storeStages(uint256[] _timesAndRates) internal {
        // check odd amount of array elements, groups of time, rate, minInv and maxInv + saleEndTime
        require(_timesAndRates.length % 4 == 1, "Wrong number of parameters in times and rates.");
        // check that at least 1 stage provided
        require(_timesAndRates.length >= 5, "There should be at least 1 complete stage, check number of parameters.");

        for (uint256 i = 0; i < _timesAndRates.length / 4; i++) {
            require(uint64(_timesAndRates[(i * 4) + 3]) == 0 || uint64(_timesAndRates[(i * 4) + 2]) <= uint64(_timesAndRates[(i * 4) + 3]), "Maximum should be higher than minimum, or 0");
            stages.push(Stage(uint32(_timesAndRates[i * 4]), uint64(_timesAndRates[(i * 4) + 1]), uint64(_timesAndRates[(i * 4) + 2]), uint64(_timesAndRates[(i * 4) + 3])));
            if (i > 0) {
                // check that each time higher than previous time
                require(stages[i-1].time < stages[i].time, "Starting time should be higher than previous stage starting time.");
                // check that each rate is lower than previous rate
                require(stages[i-1].rate > stages[i].rate, "rate can not be higher than previous stage rate.");
            }
        }

        // check that opening time in the future
        require(stages[0].time > now, "Sale start time should be in the future."); // solium-disable-line security/no-block-members

        // check final rate > 0
        require(stages[stages.length - 1].rate > 0, "Final rate can not be 0.");
    }

    function getStartTimeAtStage (uint256 _stage) public view returns (uint256 time) {
        // if before first stage, return 0
        require(_stage < stages.length, "Stage does not exist");
        return stages[_stage].time;
    }

    function updateStartTimeAtStage (uint256 _stage, uint256 _newTime) public onlyOwner {
        // if before first stage, return 0
        require(_stage < stages.length, "Stage does not exist");
        require(_newTime != getStartTimeAtStage(_stage),"That is the current time");
        require(_newTime > now,"That newTime is in the past");
        // check that each rate is lower than previous rate
        if (_stage > 0) {
            require(getStartTimeAtStage(_stage-1) > _newTime, "New start time can not be before previous stage start time.");
        }
        stages[_stage].time = uint32(_newTime);
    }

    function getRateAt(uint256 _now) internal view returns (uint256 rate) {
        // if before first stage, return 0
        if (_now < stages[0].time) {
            return 0;
        }
        for (uint i = 1; i < stages.length; i++) {
            if (_now < stages[i].time)
                return stages[i - 1].rate;
        }
        // handle last stage
        if (_now < saleEndTime)
            return stages[stages.length - 1].rate;
        // sale already closed
        return 0;
    }

    function getRateAtStage (uint256 _stage) public view returns (uint256 rate) {
        // if before first stage, return 0
        require(_stage < stages.length, "Stage does not exist");
        return stages[_stage].rate;
    }

    function updateRateAtStage (uint256 _stage, uint256 _newRate) public onlyOwner {
        // if before first stage, return 0
        require(_stage < stages.length, "Stage does not exist");
        require(_newRate != getRateAtStage(_stage),"That is the current rate");
        // check that each rate is lower than previous rate
        if (_stage > 0) {
            require(getRateAtStage(_stage-1) > _newRate, "New rate can not be higher than previous stage rate.");
        }
        stages[_stage].rate = uint64(_newRate);
    }

    function getMinInvAt(uint256 _now) public view returns (uint256 inv) {
        // if before first stage, return 0
        require(_now >= stages[0].time, "Sale not started");
        require(_now < saleEndTime, "Sale finished");

        for (uint i = 1; i < stages.length; i++) {
            if (_now < stages[i].time)
                return stages[i - 1].minInv;
        }

        // handle last stage
        if (_now < saleEndTime)
            return stages[stages.length - 1].minInv;
    }

    function getMinInvAtStage(uint32 _stage) public view returns (uint256 inv) {
        require(_stage < stages.length, "Stage does not exist");
        return stages[_stage].minInv;
    }

    function getMaxInvAt(uint256 _now) public view returns (uint256 inv) {
        // if before first stage, return 0
        require(_now >= stages[0].time, "Sale not started");
        require(_now < saleEndTime, "Sale finished");

        for (uint i = 1; i < stages.length; i++) {
            if (_now < stages[i].time)
                return stages[i - 1].maxInv;
        }

        // handle last stage
        if (_now < saleEndTime)
            return stages[stages.length - 1].maxInv;
    }

    function getMaxInvAtStage(uint32 _stage) public view returns (uint256 inv) {
        require(_stage < stages.length, "Stage does not exist");
        return stages[_stage].maxInv;
    }

    function ceil(uint _value, uint _dec) private pure returns (uint) {
        return((_value + _dec - 1) / _dec) * _dec;
    }
// Deals overrides:
    /*
    function isMerchant(address _a) public view returns(bool){
        // TODO: implement
        // Who is Merchnat in this case? Who deployed current contract or who owns current contract?
        return false;
    }
    */
}
function invest(bytes32 _r, bytes32 _s, bytes32 _a, bytes32 _b) public payable {
        // parse inputs
        uint32 time = uint32(_b >> 224);
        address beneficiary = address(_a);
        uint256 outOfBandPaymentAmount = uint64(_b >> 160);
        uint256 referenceHash = uint256(_b << 96 >> 224);

        // verify inputs
        require(uint56(_a >> 192) == uint56(this), "Destination contract address does not match this contract address.");
        if (outOfBandPaymentAmount == 0) {
            outOfBandPaymentAmount = msg.value;
        } else {
            outOfBandPaymentAmount = outOfBandPaymentAmount.mul(1000000000);    // convert outOfBandPaymentAmount to wei
        }
        bytes4 orderID = bytes4(uint32(_a >> 160));
        /* solium-disable-next-line arg-overflow */
        require(ecrecover(keccak256(abi.encodePacked(uint8(0), uint248(_a), _b)), uint8(_a >> 248), _r, _s) == signer, "Signer address not matching.");
        require(beneficiary != address(0), "Beneficiary can not be 0.");

        // check investment is above current minimum investment
        require(outOfBandPaymentAmount>=getMinInvAt(now),"Investment below minimum required");
        // check investment is under above current maximum investment
        require(outOfBandPaymentAmount+invested[beneficiary]<=getMaxInvAt(now) || getMaxInvAt(now)==0,"Investment over maximum allowed in current stage");
        // calculate token amount to be created
        uint256 rate = getRateAt(now); // solium-disable-line security/no-block-members
        // at the time of signing the receipt the rate should have been the same as now
        require(rate == getRateAt(time), "rate not matching current stage rate.");
        // multiply rate with Gwei of investment
        // uint256 purchasedTokenAmount = rate.mul(outOfBandPaymentAmount).div(1000000000);
        // multiply rate with Gwei of investment and ceil to avoid weird amounts
        uint256 purchasedTokenAmount = ceil(rate.mul(outOfBandPaymentAmount).div(1000000000), 10**3);
        // check that msg.value > 0
        require(purchasedTokenAmount > 0, "purchasedTokenAmount can not be 0.");

        // pocket Ether
        if (msg.value > 0) {
            wallet.transfer(outOfBandPaymentAmount);
        }

        // if user came from affiliate then reward affiliate
        address affiliate = address(0);
        if (referenceHash != 0) {
            affiliate = affiliates[referenceHash].affiliateAddress;
            _rewardAffiliate(referenceHash, purchasedTokenAmount);
        }

        // do token transfer
        invested[beneficiary] += outOfBandPaymentAmount;
        // send tokens and emit event
        sendTokens(beneficiary, purchasedTokenAmount);
        emit TokenPurchase(beneficiary, affiliate, outOfBandPaymentAmount, purchasedTokenAmount, orderID);
    }
/**
 * @dev called by investors to purchase tokens
 * @param _r part of receipt signature
 * @param _s part of receipt signature
 * @param _a first payload of signed receipt.
 * @param _b second payload of signed receipt.
 *     The receipt commits to the follwing inputs:
 *         56 bits - sale contract address, to prevent replay of receipt
 *         32 bits - orderID for fiat payments
 *         160 bits - beneficiary address - address whitelisted to receive tokens
 *         32 bits - time - when receipt was signed
 *         64 bits - outOfBandPaymentAmount - out of band payment amount, for fiat investments
 *         32 bits - reference hash
 */