contract NokuCustomCrowdsale is TokenCappedCrowdsale {
    using AddressUtils for address;
    using SafeMath for uint256;

    event LogNokuCustomCrowdsaleCreated(
        address sender,
        uint256 indexed startBlock,
        uint256 indexed endBlock,
        address indexed wallet
    );
    event LogThreePowerAgesChanged(
        address indexed sender,
        uint256 indexed platinumAgeEndBlock,
        uint256 indexed goldenAgeEndBlock,
        uint256 silverAgeEndBlock,
        uint256 platinumAgeRate,
        uint256 goldenAgeRate,
        uint256 silverAgeRate
    );
    event LogTwoPowerAgesChanged(
        address indexed sender,
        uint256 indexed platinumAgeEndBlock,
        uint256 indexed goldenAgeEndBlock,
        uint256 platinumAgeRate,
        uint256 goldenAgeRate
    );
    event LogOnePowerAgeChanged(address indexed sender, uint256 indexed platinumAgeEndBlock, uint256 indexed platinumAgeRate);

    // The end block of the 'platinum' age interval
    uint256 public platinumAgeEndBlock;

    // The end block of the 'golden' age interval
    uint256 public goldenAgeEndBlock;

    // The end block of the 'silver' age interval
    uint256 public silverAgeEndBlock;

    // The conversion rate of the 'platinum' age
    uint256 public platinumAgeRate;

    // The conversion rate of the 'golden' age
    uint256 public goldenAgeRate;

    // The conversion rate of the 'silver' age
    uint256 public silverAgeRate;

    // The wallet address or contract
    address public wallet;

    constructor(
        uint256 _startBlock,
        uint256 _endBlock,
        uint256 _rate,
        uint256 _minDeposit,
        uint256 _maxWhitelistLength,
        uint256 _whitelistThreshold,
        address _token,
        uint256 _tokenMaximumSupply,
        address _wallet
    )
    Crowdsale(
        _startBlock,
        _endBlock,
        _rate,
        _minDeposit,
        _maxWhitelistLength,
        _whitelistThreshold
    )
    public {
        require(_token.isContract(), "_token is not contract");
        require(_tokenMaximumSupply > 0, "_tokenMaximumSupply is zero");

        platinumAgeRate = _rate;
        goldenAgeRate = _rate;
        silverAgeRate = _rate;

        token = NokuCustomERC20(_token);
        wallet = _wallet;

        // Assume predefined token supply has been minted and calculate the maximum number of tokens that can be sold
        tokenCap = _tokenMaximumSupply.sub(token.totalSupply());

        emit LogNokuCustomCrowdsaleCreated(msg.sender, startBlock, endBlock, _wallet);
    }

    function setThreePowerAges(
        uint256 _platinumAgeEndBlock,
        uint256 _goldenAgeEndBlock,
        uint256 _silverAgeEndBlock,
        uint256 _platinumAgeRate,
        uint256 _goldenAgeRate,
        uint256 _silverAgeRate
    )
    external onlyOwner beforeStart
    {
        require(startBlock < _platinumAgeEndBlock, "_platinumAgeEndBlock not greater than start block");
        require(_platinumAgeEndBlock < _goldenAgeEndBlock, "_platinumAgeEndBlock not lower than _goldenAgeEndBlock");
        require(_goldenAgeEndBlock < _silverAgeEndBlock, "_silverAgeEndBlock not greater than _goldenAgeEndBlock");
        require(_silverAgeEndBlock <= endBlock, "_silverAgeEndBlock greater than end block");
        require(_platinumAgeRate > _goldenAgeRate, "_platinumAgeRate not greater than _goldenAgeRate");
        require(_goldenAgeRate > _silverAgeRate, "_goldenAgeRate not greater than _silverAgeRate");
        require(_silverAgeRate > rate, "_silverAgeRate not greater than nominal rate");

        platinumAgeEndBlock = _platinumAgeEndBlock;
        goldenAgeEndBlock = _goldenAgeEndBlock;
        silverAgeEndBlock = _silverAgeEndBlock;

        platinumAgeRate = _platinumAgeRate;
        goldenAgeRate = _goldenAgeRate;
        silverAgeRate = _silverAgeRate;

        emit LogThreePowerAgesChanged(
            msg.sender,
            _platinumAgeEndBlock,
            _goldenAgeEndBlock,
            _silverAgeEndBlock,
            _platinumAgeRate,
            _goldenAgeRate,
            _silverAgeRate
        );
    }

    function setTwoPowerAges(
        uint256 _platinumAgeEndBlock,
        uint256 _goldenAgeEndBlock,
        uint256 _platinumAgeRate,
        uint256 _goldenAgeRate
    )
    external onlyOwner beforeStart
    {
        require(startBlock < _platinumAgeEndBlock, "_platinumAgeEndBlock not greater than start block");
        require(_platinumAgeEndBlock < _goldenAgeEndBlock, "_platinumAgeEndBlock not lower than _goldenAgeEndBlock");
        require(_goldenAgeEndBlock <= endBlock, "_goldenAgeEndBlock greater than end block");
        require(_platinumAgeRate > _goldenAgeRate, "_platinumAgeRate not greater than _goldenAgeRate");
        require(_goldenAgeRate > rate, "_goldenAgeRate not greater than nominal rate");

        platinumAgeEndBlock = _platinumAgeEndBlock;
        goldenAgeEndBlock = _goldenAgeEndBlock;

        platinumAgeRate = _platinumAgeRate;
        goldenAgeRate = _goldenAgeRate;
        silverAgeRate = rate;

        emit LogTwoPowerAgesChanged(
            msg.sender,
            _platinumAgeEndBlock,
            _goldenAgeEndBlock,
            _platinumAgeRate,
            _goldenAgeRate
        );
    }

    function setOnePowerAge(uint256 _platinumAgeEndBlock, uint256 _platinumAgeRate)
    external onlyOwner beforeStart
    {
        require(startBlock < _platinumAgeEndBlock, "_platinumAgeEndBlock not greater than start block");
        require(_platinumAgeEndBlock <= endBlock, "_platinumAgeEndBlock greater than end block");
        require(_platinumAgeRate > rate, "_platinumAgeRate not greater than nominal rate");

        platinumAgeEndBlock = _platinumAgeEndBlock;

        platinumAgeRate = _platinumAgeRate;
        goldenAgeRate = rate;
        silverAgeRate = rate;

        emit LogOnePowerAgeChanged(msg.sender, _platinumAgeEndBlock, _platinumAgeRate);
    }

    function grantTokenOwnership(address _client) external onlyOwner returns(bool granted) {
        require(!_client.isContract(), "_client is contract");
        require(hasEnded(), "crowdsale not ended yet");

        // Transfer NokuCustomERC20 ownership back to the client
        token.transferOwnership(_client);

        return true;
    }

    // Overriding Crowdsale#calculateTokens to apply age discounts to token calculus.
    function calculateTokens(uint256 amount) internal view returns(uint256 tokenAmount) {
        uint256 conversionRate = block.number <= platinumAgeEndBlock ? platinumAgeRate :
            block.number <= goldenAgeEndBlock ? goldenAgeRate :
            block.number <= silverAgeEndBlock ? silverAgeRate :
            rate;

        return amount.mul(conversionRate);
    }

    /**
     * @dev Overriding Crowdsale#distributeTokens to apply age rules to token distributions.
     */
    function distributeTokens(address beneficiary, uint256 tokenAmount) internal {
        if (block.number <= platinumAgeEndBlock) {
            NokuCustomERC20(token).mintLocked(beneficiary, tokenAmount);
        }
        else {
            super.distributeTokens(beneficiary, tokenAmount);
        }
    }

    /**
     * @dev Overriding Crowdsale#forwardFunds to split net/fee payment.
     */
    function forwardFunds(uint256 amount) internal {
        wallet.transfer(amount);
    }
}
/**
 * @title NokuCustomCrowdsale
 * @dev Extension of TokenCappedCrowdsale using values specific for Noku Custom ICO crowdsale
 */
function calculateTokens(uint256 amount) internal view returns(uint256 tokenAmount) {
    uint256 conversionRate = block.number <= platinumAgeEndBlock ? platinumAgeRate :
        block.number <= goldenAgeEndBlock ? goldenAgeRate :
        block.number <= silverAgeEndBlock ? silverAgeRate :
        rate;

    return amount.mul(conversionRate);
}

// Overriding Crowdsale#calculateTokens to apply age discounts to token calculus.