contract CryptualProjectToken is StandardToken, Ownable {
  using SafeMath for uint256;

  // ERC20 optional details
  string public constant name = "Cryptual Project Token"; // solium-disable-line uppercase
  string public constant symbol = "CTL"; // solium-disable-line uppercase
  uint8 public constant decimals = 0; // solium-disable-line uppercase

  // Token constants, variables
  uint256 public constant INITIAL_SUPPLY = 2480000000;
  address public wallet;

  // Private presale constants
  uint256 public constant PRESALE_OPENING_TIME = 1535382000; // Mon, 27 Aug 2018 15:00:00 +0000
  uint256 public constant PRESALE_CLOSING_TIME = 1536289200; // Fri, 07 Sep 2018 03:00:00 +0000
  uint256 public constant PRESALE_RATE = 500000;
  uint256 public constant PRESALE_WEI_CAP = 2500 ether;
  uint256 public constant PRESALE_WEI_GOAL = 100 ether;

  // Public crowdsale constants
  uint256 public constant CROWDSALE_OPENING_TIME = 1537542000; // Fri, 21 Sep 2018 15:00:00 +0000
  uint256 public constant CROWDSALE_CLOSING_TIME = 1545361200; // Fri, 21 Dec 2018 03:00:00 +0000
  uint256 public constant CROWDSALE_WEI_CAP = 20000 ether;
  uint256 public constant CROWDSALE_WEI_GOAL = 800 ether;

  // Public crowdsale parameters
  uint256[] public crowdsaleWeiAvailableLevels = [2500 ether, 5000 ether, 12500 ether];
  uint256[] public crowdsaleRates = [400000, 300000, 200000];
  uint256[] public crowdsaleMinElapsedTimeLevels = [0, 12 * 3600, 18 * 3600, 21 * 3600, 22 * 3600];
  uint256[] public crowdsaleUserCaps = [1 ether, 2 ether, 4 ether, 8 ether, CROWDSALE_WEI_CAP];
  mapping(address => uint256) public crowdsaleContributions;

  // Amount of wei raised for each token sale stage
  uint256 public presaleWeiRaised;
  uint256 public crowdsaleWeiRaised;

  /**
   * @dev Constructor that sends msg.sender the initial token supply
   */
  constructor(
    address _wallet
  ) public {
    require(_wallet != address(0));
    wallet = _wallet;

    totalSupply_ = INITIAL_SUPPLY;
    balances[msg.sender] = INITIAL_SUPPLY;
    emit Transfer(0x0, msg.sender, INITIAL_SUPPLY);
  }

  /**
   * Event for token purchase logging
   * @param purchaser who paid for the tokens
   * @param beneficiary who got the tokens
   * @param value weis paid for purchase
   * @param amount amount of tokens purchased
   */
  event TokenPurchase(address indexed purchaser, address indexed beneficiary, uint256 value, uint256 amount);

  /**
   * @dev fallback token purchase function
   */
  function () external payable {
    buyTokens(msg.sender);
  }

  /**
   * @dev token purchase function
   * @param _beneficiary Address performing the token purchase
   */
  function buyTokens(address _beneficiary) public payable {

    uint256 weiAmount = msg.value;
    require(_beneficiary != address(0));
    require(weiAmount != 0);
    bool isPresale = block.timestamp >= PRESALE_OPENING_TIME && block.timestamp <= PRESALE_CLOSING_TIME && presaleWeiRaised.add(weiAmount) <= PRESALE_WEI_CAP;
    bool isCrowdsale = block.timestamp >= CROWDSALE_OPENING_TIME && block.timestamp <= CROWDSALE_CLOSING_TIME && presaleGoalReached() && crowdsaleWeiRaised.add(weiAmount) <= CROWDSALE_WEI_CAP;
    uint256 tokens;

    if (isCrowdsale) {
      require(crowdsaleContributions[_beneficiary].add(weiAmount) <= getCrowdsaleUserCap());
      
      // calculate token amount to be created
      tokens = _getCrowdsaleTokenAmount(weiAmount);
      require(tokens != 0);

      // update state
      crowdsaleWeiRaised = crowdsaleWeiRaised.add(weiAmount);
    } else if (isPresale) {
      require(whitelist[_beneficiary]);
      
      // calculate token amount to be created
      tokens = weiAmount.mul(PRESALE_RATE).div(1 ether);
      require(tokens != 0);

      // update state
      presaleWeiRaised = presaleWeiRaised.add(weiAmount);
    } else {
      revert();
    }

    _processPurchase(_beneficiary, tokens);
    emit TokenPurchase(
      msg.sender,
      _beneficiary,
      weiAmount,
      tokens
    );

    if (isCrowdsale) {
      crowdsaleContributions[_beneficiary] = crowdsaleContributions[_beneficiary].add(weiAmount);
      crowdsaleDeposited[_beneficiary] = crowdsaleDeposited[_beneficiary].add(msg.value);
    } else if (isPresale) {
      presaleDeposited[_beneficiary] = presaleDeposited[_beneficiary].add(msg.value);
    }
  }

  /**
   * @dev Returns the current contribution cap per user in wei.
   * Note that this cap in changes with time.
   * @return The maximum wei a user may contribute in total
   */
  function getCrowdsaleUserCap() public view returns (uint256) {
    require(block.timestamp >= CROWDSALE_OPENING_TIME && block.timestamp <= CROWDSALE_CLOSING_TIME);
    // solium-disable-next-line security/no-block-members
    uint256 elapsedTime = block.timestamp.sub(CROWDSALE_OPENING_TIME);
    uint256 currentMinElapsedTime = 0;
    uint256 currentCap = 0;

    for (uint i = 0; i < crowdsaleUserCaps.length; i++) {
      if (elapsedTime < crowdsaleMinElapsedTimeLevels[i]) continue;
      if (crowdsaleMinElapsedTimeLevels[i] < currentMinElapsedTime) continue;
      currentCap = crowdsaleUserCaps[i];
    }

    return currentCap;
  }

  /**
   * @dev Function to compute output tokens from input wei
   * @param _weiAmount The value in wei to be converted into tokens
   * @return The number of tokens _weiAmount wei will buy at present time
   */
  function _getCrowdsaleTokenAmount(uint256 _weiAmount) internal view returns (uint256) {
    uint256 uncountedWeiRaised = crowdsaleWeiRaised;
    uint256 uncountedWeiAmount = _weiAmount;
    uint256 tokenAmount = 0;

    for (uint i = 0; i < crowdsaleWeiAvailableLevels.length; i++) {
      uint256 weiAvailable = crowdsaleWeiAvailableLevels[i];
      uint256 rate = crowdsaleRates[i];
      
      if (uncountedWeiRaised < weiAvailable) {
        if (uncountedWeiRaised > 0) {
          weiAvailable = weiAvailable.sub(uncountedWeiRaised);
          uncountedWeiRaised = 0;
        }

        if (uncountedWeiAmount <= weiAvailable) {
          tokenAmount = tokenAmount.add(uncountedWeiAmount.mul(rate));
          break;
        } else {
          uncountedWeiAmount = uncountedWeiAmount.sub(weiAvailable);
          tokenAmount = tokenAmount.add(weiAvailable.mul(rate));
        }
      } else {
        uncountedWeiRaised = uncountedWeiRaised.sub(weiAvailable);
      }
    }

    return tokenAmount.div(1 ether);
  }

  /**
   * @dev Executed when a purchase has been validated and is ready to be executed. Not necessarily emits/sends tokens.
   * @param _beneficiary Address receiving the tokens
   * @param _tokenAmount Number of tokens to be purchased
   */
  function _processPurchase(address _beneficiary, uint256 _tokenAmount) internal {
    totalSupply_ = totalSupply_.add(_tokenAmount);
    balances[_beneficiary] = balances[_beneficiary].add(_tokenAmount);
    emit Transfer(0x0, _beneficiary, _tokenAmount);
  }
  
  // Private presale buyer whitelist
  mapping(address => bool) public whitelist;

  /**
   * @dev Adds single address to whitelist.
   * @param _beneficiary Address to be added to the whitelist
   */
  function addToPresaleWhitelist(address _beneficiary) external onlyOwner {
    whitelist[_beneficiary] = true;
  }

  /**
   * @dev Adds list of addresses to whitelist. Not overloaded due to limitations with truffle testing.
   * @param _beneficiaries Addresses to be added to the whitelist
   */
  function addManyToPresaleWhitelist(address[] _beneficiaries) external onlyOwner {
    for (uint256 i = 0; i < _beneficiaries.length; i++) {
      whitelist[_beneficiaries[i]] = true;
    }
  }

  /**
   * @dev Removes single address from whitelist.
   * @param _beneficiary Address to be removed to the whitelist
   */
  function removeFromPresaleWhitelist(address _beneficiary) external onlyOwner {
    whitelist[_beneficiary] = false;
  }

  // Crowdsale finalization/refunding variables
  bool public isPresaleFinalized = false;
  bool public isCrowdsaleFinalized = false;
  mapping (address => uint256) public presaleDeposited;
  mapping (address => uint256) public crowdsaleDeposited;

  // Crowdsale finalization/refunding events
  event PresaleFinalized();
  event CrowdsaleFinalized();
  event RefundsEnabled();
  event Refunded(address indexed beneficiary, uint256 weiAmount);

  /**
   * @dev Must be called after presale ends, to do some extra finalization (forwarding/refunding) work.
   */
  function finalizePresale() external {
    require(!isPresaleFinalized);
    require(block.timestamp > PRESALE_CLOSING_TIME);

    if (presaleGoalReached()) {
      wallet.transfer(address(this).balance > presaleWeiRaised ? presaleWeiRaised : address(this).balance);
    } else {
      emit RefundsEnabled();
    }

    emit PresaleFinalized();
    isPresaleFinalized = true;
  }

  /**
   * @dev Must be called after crowdsale ends, to do some extra finalization (forwarding/refunding) work.
   */
  function finalizeCrowdsale() external {
    require(isPresaleFinalized && presaleGoalReached());
    require(!isCrowdsaleFinalized);
    require(block.timestamp > CROWDSALE_CLOSING_TIME);

    if (crowdsaleGoalReached()) {
      wallet.transfer(address(this).balance);
    } else {
      emit RefundsEnabled();
    }

    emit CrowdsaleFinalized();
    isCrowdsaleFinalized = true;
  }

  /**
   * @dev Investors can claim refunds here if presale/crowdsale is unsuccessful
   */
  function claimRefund() external {
    uint256 depositedValue = 0;

    if (isCrowdsaleFinalized && !crowdsaleGoalReached()) {
      require(crowdsaleDeposited[msg.sender] > 0);
      depositedValue = crowdsaleDeposited[msg.sender];
      crowdsaleDeposited[msg.sender] = 0;
    } else if (isPresaleFinalized && !presaleGoalReached()) {
      require(presaleDeposited[msg.sender] > 0);
      depositedValue = presaleDeposited[msg.sender];
      presaleDeposited[msg.sender] = 0;
    }

    require(depositedValue > 0);
    msg.sender.transfer(depositedValue);
    emit Refunded(msg.sender, depositedValue);
  }

  /**
   * @dev Checks whether presale funding goal was reached.
   * @return Whether presale funding goal was reached
   */
  function presaleGoalReached() public view returns (bool) {
    return presaleWeiRaised >= PRESALE_WEI_GOAL;
  }

  /**
   * @dev Checks whether crowdsale funding goal was reached.
   * @return Whether crowdsale funding goal was reached
   */
  function crowdsaleGoalReached() public view returns (bool) {
    return crowdsaleWeiRaised >= CROWDSALE_WEI_GOAL;
  }

}
/**
 * @title CryptualProjectToken
 * @dev Official ERC20 token contract for the Cryptual Project.
 * This contract includes both a presale and a crowdsale.
 */
function finalizePresale() external {
  require(!isPresaleFinalized);
  require(block.timestamp > PRESALE_CLOSING_TIME);

  if (presaleGoalReached()) {
    wallet.transfer(address(this).balance > presaleWeiRaised ? presaleWeiRaised : address(this).balance);
  } else {
    emit RefundsEnabled();
  }

  emit PresaleFinalized();
  isPresaleFinalized = true;
}

/**
 * @dev Must be called after presale ends, to do some extra finalization (forwarding/refunding) work.
 */