contract HashBay is ERC20, Ownable, ReentrancyGuard, Exponential {

  mapping (address => uint256) private rewardIndex;

  struct Record {
    address holder;
    uint256 reward;
  }

  Record[] private rewardRecords;

  uint256 public startDate;
  uint256 public endDate;
  string public contractName;

  event NewReward(address holder, uint256 reward);
  event Claim(address claimer, uint256 amount);
  event NewRecord(address holder);
  event NewDate(uint256 newStartDate, uint256 newEndDate);

  /**
   * @notice Init the smart contract and token name as "Million Hashes Per Second" and symbbol "MHASH", the decimals is 6 as default.
   * @param initialSupply Init the token total supply.
   * @param initOwner Init the owner address.
   * @param initStartDate Init the start date only modified by owner.
   * @param initEndDate Init the end date only modified by owner.
   * @param initContractName Init the contract name cannot be modified.
   */
  constructor(uint256 initialSupply, address initOwner, uint256 initStartDate, uint256 initEndDate, string memory initContractName) ERC20("Million Hashes Per Second", "MHASH") public {

    _setupDecimals(6);
    _mint(initOwner, initialSupply);
    transferOwnership(initOwner);
    _setDate(initStartDate, initEndDate);
    contractName = initContractName;

    rewardRecords.push(Record(address(0x0), 0));
    _updateRewardRecords(initOwner);
  }

  /**
   * @notice Receiving the eth reward and update every holder's reward record according to the token balance.
   */
  receive() external payable {

    require(msg.value != 0, "Only eth entry is accepted");

    Exp memory rewardRateExp = _getRewardRate(msg.value);

    for (uint256 i = 1; i < rewardRecords.length; i++) {
        address holder = rewardRecords[i].holder;
        uint256 rewardAmount = truncate(mulScalar(rewardRateExp, balanceOf(holder)));
        if (rewardAmount > 0) {
          rewardRecords[i].reward = rewardRecords[i].reward.add(rewardAmount);
          emit NewReward(holder, rewardRecords[i].reward);
        }
    }
  }

  /**
   * @notice Get the reward without being claimed for holder self.
   * @return The houlder's reward in eth.
   */
  function myReward() external view returns (uint256) {
    return rewardRecords[rewardIndex[msg.sender]].reward;
  }

  /**
   * @notice Claim the reward by holder and clean the reward record.
   * @return The amount of reward which the holder will receive.
   */
  function claim() external nonReentrant returns (uint256) {

    uint256 index = rewardIndex[msg.sender];
    require(msg.sender == rewardRecords[index].holder, "houlder address error");

    uint256 rewardAmount = rewardRecords[index].reward;
    require(rewardAmount > 0, "eth reward record should be larger than 0");

    rewardRecords[index].reward = 0;
    msg.sender.transfer(rewardAmount);

    emit Claim(msg.sender, rewardAmount);

    return rewardAmount; 
  }

  /**
   * @notice Holder can transfer their token to others. 
   * @param recipient The recipient address who transfer to.
   * @param amount The amount token transfered.
   * @return bool return true if the token transfered successfully.
   */
  function transfer(address recipient, uint256 amount) public override returns (bool) {

      _updateRewardRecords(recipient);
      super.transfer(recipient, amount);
  }

  /**
   * @notice Spender can transfer sender's token to others.
   * @param sender The holder who send the token
   * @param recipient The recipient address who transfer to.
   * @param amount The amount token transfered.
   * @return bool return true if the token transfered successfully.
   */
  function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {

      _updateRewardRecords(recipient);
      super.transferFrom(sender, recipient, amount);
  }

  /**
   * @notice Modefy the start date and end date of mining.
   * @param newStartDate The new start date.
   * @param newEndDate The new end date.
   * @dev onlyOwner
   */
  function setDate(uint256 newStartDate, uint256 newEndDate) external onlyOwner {
    _setDate(newStartDate, newEndDate);
  }

  /**
   * @notice Modefy the start date and end date of mining.
   * @param newStartDate The new start date.
   * @param newEndDate The new end date.
   * @dev internal function
   */
  function _setDate(uint256 newStartDate, uint256 newEndDate) internal {
    startDate = newStartDate;
    endDate = newEndDate;
    emit NewDate(startDate, endDate);
  }

  /**
   * @notice If a new recipient will be addedinto the reward record and update the reward index
   * @param recipient The address of recipient
   * @dev internal function
   */
  function _updateRewardRecords(address recipient) internal {

      if (rewardIndex[recipient] == 0) {
        rewardRecords.push(Record(recipient, 0));
        rewardIndex[recipient] = rewardRecords.length.sub(1);
        emit NewRecord(recipient);
      }
  }

  function _getRewardRate(uint256 reward) internal view returns (Exp memory) {
    return getExp(reward, totalSupply());
  }
}
/**
 * @title A token named Million Hashes Per Second of ETH cloud mining.
 * @notice You can use this contract for only the most basic simulation.
 * @dev All function calls are currently implemented without side effects.
 */
function transfer(address recipient, uint256 amount) public override returns (bool) {

    _updateRewardRecords(recipient);
    super.transfer(recipient, amount);
}

/**
 * @notice Holder can transfer their token to others. 
 * @param recipient The recipient address who transfer to.
 * @param amount The amount token transfered.
 * @return bool return true if the token transfered successfully.
 */