contract IiinoCoin is MintableToken, PausableToken, ReentrancyGuard {
    event RewardMint(address indexed to, uint256 amount);
    event RewardMintingAmt(uint256 _amountOfTokensMintedPreCycle);
    event ResetReward();
    event RedeemReward(address indexed to, uint256 value);

    event CreatedEscrow(bytes32 _tradeHash);
    event ReleasedEscrow(bytes32 _tradeHash);
    event Dispute(bytes32 _tradeHash);
    event CancelledBySeller(bytes32 _tradeHash);
    event CancelledByBuyer(bytes32 _tradeHash);
    event BuyerArbitratorSet(bytes32 _tradeHash);
    event SellerArbitratorSet(bytes32 _tradeHash);
    event DisputeResolved (bytes32 _tradeHash);
    event IcoContractAddressSet (address _icoContractAddress);
    
    using SafeMath for uint256;
    
    // Mapping of rewards to beneficiaries of the reward
    mapping(address => uint256) public reward;
  
    string public name;
    string public symbol;
    uint8 public decimals;

    uint256 public amountMintPerDuration; // amount to mint during one minting cycle
    uint256 public durationBetweenRewardMints; // reward miniting cycle duration
    uint256 public previousDistribution; //EPOCH time of the previous distribution
    uint256 public totalRewardsDistributed; //Total amount of the rewards distributed
    uint256 public totalRewardsRedeemed; //Total amount of the rewards redeemed
    uint256 public minimumRewardWithdrawalLimit; //The minimum limit of rewards that can be withdrawn
    uint256 public rewardAvailableCurrentDistribution; //The amount of rewards available for the current Distribution.

    function IiinoCoin(
        string _name, 
        string _symbol, 
        uint8 _decimals, 
        uint256 _amountMintPerDuration, 
        uint256 _durationBetweenRewardMints 
    ) public {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        amountMintPerDuration = _amountMintPerDuration;
        durationBetweenRewardMints = _durationBetweenRewardMints;
        previousDistribution = now; // To initialize the previous distribution to the time of the creation of the contract
        totalRewardsDistributed = 0;
        totalRewardsRedeemed = 0;
        minimumRewardWithdrawalLimit = 10 ether; //Defaulted to 10 iiinos represented in iii
        rewardAvailableCurrentDistribution = amountMintPerDuration;
        icoContractAddress = msg.sender; 
    }
    
    /**
    * @dev set the icoContractAddress in the token so that the ico Contract can mint the token
    * @param _icoContractAddress array of address. The address to which the reward needs to be distributed
    */
    function setIcoContractAddress(
        address _icoContractAddress
    ) external nonReentrant onlyOwner whenNotPaused {
        require (_icoContractAddress != address(0));
        emit IcoContractAddressSet(_icoContractAddress);
        icoContractAddress = _icoContractAddress;    
    }

    /**
    * @dev distribute reward tokens to the list of addresses based on their proportion
    * @param _rewardAdresses array of address. The address to which the reward needs to be distributed
    */
    function batchDistributeReward(
        address[] _rewardAdresses,
        uint256[] _amountOfReward, 
        uint256 _timestampOfDistribution
    ) external nonReentrant onlyOwner whenNotPaused {
        require(_timestampOfDistribution > previousDistribution.add(durationBetweenRewardMints)); //To only allow a distribution to happen 30 days (2592000 seconds) after the previous distribution
        require(_timestampOfDistribution < now); // To only allow a distribution time in the past
        require(_rewardAdresses.length == _amountOfReward.length); // To verify the length of the arrays are the same.
        
        uint256 rewardDistributed = 0;

        for (uint j = 0; j < _rewardAdresses.length; j++) {
            rewardMint(_rewardAdresses[j], _amountOfReward[j]);
            rewardDistributed = rewardDistributed.add(_amountOfReward[j]);
        }
        require(rewardAvailableCurrentDistribution >= rewardDistributed);
        totalRewardsDistributed = totalRewardsDistributed.add(rewardDistributed);
        rewardAvailableCurrentDistribution = rewardAvailableCurrentDistribution.sub(rewardDistributed);
    }
    
    /**
    * @dev distribute reward tokens to a addresse based on the proportion
    * @param _rewardAddress The address to which the reward needs to be distributed
    */
    function distributeReward(
        address _rewardAddress, 
        uint256 _amountOfReward, 
        uint256 _timestampOfDistribution
    ) external nonReentrant onlyOwner whenNotPaused {
        
        require(_timestampOfDistribution > previousDistribution);
        require(_timestampOfDistribution < previousDistribution.add(durationBetweenRewardMints)); //To only allow a distribution to happen 30 days (2592000 seconds) after the previous distribution
        require(_timestampOfDistribution < now); // To only allow a distribution time in the past
        //reward[_rewardAddress] = reward[_rewardAddress].add(_amountOfReward);
        rewardMint(_rewardAddress, _amountOfReward);
        
    }

    /**
    * @dev reset reward tokensfor the new duration
    */
    function resetReward() external nonReentrant onlyOwner whenNotPaused {
        require(now > previousDistribution.add(durationBetweenRewardMints)); //To only allow a distribution to happen 30 days (2592000 seconds) after the previous distribution
        previousDistribution = previousDistribution.add(durationBetweenRewardMints); // To set the new distribution period as the previous distribution timestamp
        rewardAvailableCurrentDistribution = amountMintPerDuration;
        emit ResetReward();
    }

    /**
   * @dev Redeem Reward tokens from one rewards array to balances array
   * @param _beneficiary address The address which contains the reward as well as the address to which the balance will be transferred
   * @param _value uint256 the amount of tokens to be redeemed
   */
    function redeemReward(
        address _beneficiary, 
        uint256 _value
    ) external nonReentrant whenNotPaused{
        //Need to consider what happens to rewards after the stopping of minting process
        require(msg.sender == _beneficiary);
        require(_value >= minimumRewardWithdrawalLimit);
        require(reward[_beneficiary] >= _value);
        reward[_beneficiary] = reward[_beneficiary].sub(_value);
        balances[_beneficiary] = balances[_beneficiary].add(_value);
        totalRewardsRedeemed = totalRewardsRedeemed.add(_value);
        emit RedeemReward(_beneficiary, _value);
    }

    function rewardMint(
        address _to, 
        uint256 _amount
    ) onlyOwner canMint whenNotPaused internal returns (bool) {
        require(_amount > 0);
        require(_to != address(0));
        require(rewardAvailableCurrentDistribution >= _amount);
        totalSupply_ = totalSupply_.add(_amount);
        reward[_to] = reward[_to].add(_amount);
        totalRewardsDistributed = totalRewardsDistributed.add(_amount);
        rewardAvailableCurrentDistribution = rewardAvailableCurrentDistribution.sub(_amount);
        emit RewardMint(_to, _amount);
        //Transfer(address(0), _to, _amount); //balance of the user will only be updated on claiming the coin
        return true;
    }
    function userRewardAccountBalance(
        address _address
    ) whenNotPaused external view returns (uint256) {
        require(_address != address(0));
        return reward[_address];
    }
    function changeRewardMintingAmount(
        uint256 _newRewardMintAmt
    ) whenNotPaused nonReentrant onlyOwner external {
        require(_newRewardMintAmt < amountMintPerDuration);
        amountMintPerDuration = _newRewardMintAmt;
        emit RewardMintingAmt(_newRewardMintAmt);
    }

    function transferFrom(address _from, address _to, uint256 _value) external nonReentrant returns (bool) {
        return transferFromInternal(_from, _to, _value);
    }
    function approve(address _spender, uint256 _value) external nonReentrant returns (bool) {
        return approveInternal(_spender, _value);
    }
    function allowance(address _owner, address _spender) external view returns (uint256) {
        return allowanceInternal(_owner, _spender);
    }
    function increaseApproval(address _spender, uint _addedValue) external nonReentrant returns (bool) {
        return increaseApprovalInternal(_spender, _addedValue);
    }
    function decreaseApproval(address _spender, uint _subtractedValue) external nonReentrant returns (bool) {
        return decreaseApprovalInternal(_spender, _subtractedValue);
    }
    function transfer(address _to, uint256 _value) external nonReentrant returns (bool) {
        return transferInternal(_to, _value);
    } 
}
function resetReward() external nonReentrant onlyOwner whenNotPaused {
    require(now > previousDistribution.add(durationBetweenRewardMints)); //To only allow a distribution to happen 30 days (2592000 seconds) after the previous distribution
    previousDistribution = previousDistribution.add(durationBetweenRewardMints); // To set the new distribution period as the previous distribution timestamp
    rewardAvailableCurrentDistribution = amountMintPerDuration;
    emit ResetReward();
}

/**
 * @dev reset reward tokensfor the new duration
 */