contract GanaPreSale is Ownable {
  using SafeMath for uint256;

  GANA public gana;
  Whitelist public whitelist;
  address public wallet;
  uint256 public hardCap   = 10000 ether; //presale cap
  uint256 public weiRaised = 0;
  uint256 public minCap    = 3 ether;
  uint256 public rate      = 22000; // 1 ETH == 22,000 GANA
  uint256 public defaultRate = 20000;

  //uint256 public startTime = 1483228800; //TEST ONLY UTC 01/01/2017 00:00am
  uint256 public startTime = 1523008800; //UTC 04/06/2018 10:00am
  uint256 public endTime   = 1523786400; //UTC 04/15/2018 10:00am

  event TokenPurchase(address indexed sender, address indexed buyer, uint256 weiAmount, uint256 ganaAmount);
  event Refund(address indexed buyer, uint256 weiAmount);
  event TransferToSafe();
  event BurnAndReturnAfterEnded(uint256 burnAmount, uint256 returnAmount);

  function GanaPreSale(address _gana, address _wallet, address _whitelist) public {
    require(_wallet != address(0));
    gana = GANA(_gana);
    whitelist = Whitelist(_whitelist);
    wallet = _wallet;
  }

  modifier onlyWhitelisted() {
    require(whitelist.isWhitelist(msg.sender));
    _;
  }

  // fallback function can be used to buy tokens
  function () external payable {
    buyGana(msg.sender);
  }

  function buyGana(address buyer) public onlyWhitelisted payable {
    require(!hasEnded());
    require(afterStart());
    require(buyer != address(0));
    require(buyer == msg.sender);
    require(msg.value >= minCap);

    uint256 weiAmount = msg.value;
    //pre-calculate wei raise after buying
    uint256 preCalWeiRaised = weiRaised.add(weiAmount);
    uint256 ganaAmount;

    if(preCalWeiRaised <= hardCap){
      //the pre-calculate wei raise is less than the hard cap
      ganaAmount = weiAmount.mul(rate);
      gana.saleTransfer(buyer, ganaAmount);
      weiRaised = preCalWeiRaised;
      TokenPurchase(msg.sender, buyer, weiAmount, ganaAmount);
    }else{
      //the pre-calculate weiRaised is more than the hard cap
      uint256 refundWeiAmount = preCalWeiRaised.sub(hardCap);
      uint256 fundWeiAmount =  weiAmount.sub(refundWeiAmount);
      ganaAmount = fundWeiAmount.mul(rate);
      gana.saleTransfer(buyer, ganaAmount);
      weiRaised = weiRaised.add(fundWeiAmount);
      TokenPurchase(msg.sender, buyer, fundWeiAmount, ganaAmount);
      buyer.transfer(refundWeiAmount);
      Refund(buyer,refundWeiAmount);
    }
  }

  //Was it sold out or sale overdue
  function hasEnded() public view returns (bool) {
    bool hardCapReached = weiRaised >= hardCap; // balid cap
    return hardCapReached || afterEnded();
  }

  function afterEnded() internal constant returns (bool) {
    return now > endTime;
  }

  function afterStart() internal constant returns (bool) {
    return now >= startTime;
  }

  function transferToSafe() onlyOwner public {
    require(hasEnded());
    wallet.transfer(this.balance);
    TransferToSafe();
  }

  /**
  * @dev burn unsold token and return bonus token
  * @param reserveWallet reserve pool address
  */
  function burnAndReturnAfterEnded(address reserveWallet) onlyOwner public {
    require(reserveWallet != address(0));
    require(hasEnded());
    uint256 unsoldWei = hardCap.sub(weiRaised);
    require(unsoldWei > 0);
    uint256 unsoldGanaAmount = gana.balanceOf(this);
    uint256 burnGanaAmount = unsoldWei.mul(defaultRate);
    uint256 bonusGanaAmount = unsoldGanaAmount.sub(burnGanaAmount);
    gana.burn(burnGanaAmount);
    gana.saleTransfer(reserveWallet, bonusGanaAmount);
    BurnAndReturnAfterEnded(burnGanaAmount, bonusGanaAmount);
  }

  /**
  * @dev emergency function before sale
  * @param returnAddress return token address
  */
  function returnGanaBeforeSale(address returnAddress) onlyOwner public {
    require(returnAddress != address(0));
    require(weiRaised == 0);
    uint256 returnGana = gana.balanceOf(this);
    gana.saleTransfer(returnAddress, returnGana);
  }

}
/**
 *  GANA PRE-SALE
 */
function hasEnded() public view returns (bool) {
  bool hardCapReached = weiRaised >= hardCap; // balid cap
  return hardCapReached || afterEnded();
}

//Was it sold out or sale overdue