// File: contracts/SafeMath.sol

pragma solidity ^0.5.0;


/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a * b;
    assert(a == 0 || c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}

// File: contracts/Ownable.sol

pragma solidity ^0.5.0;


/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {
  address public owner;


  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender account.
   */
  constructor () public {
    owner = msg.sender;
  }

  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }

  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function transferOwnership(address newOwner) public onlyOwner {
    if (newOwner != address(0)) {
      owner = newOwner;
    }
  }

}

// File: contracts/Pausable.sol

pragma solidity ^0.5.0;



/**
 * @title Pausable
 * @dev Base contract which allows children to implement an emergency stop mechanism.
 */
contract Pausable is Ownable {
  event Pause();
  event Unpause();

  bool public paused = false;

  constructor() public {}

  /**
   * @dev modifier to allow actions only when the contract IS paused
   */
  modifier whenNotPaused() {
    require(!paused);
    _;
  }

  /**
   * @dev modifier to allow actions only when the contract IS NOT paused
   */
  modifier whenPaused {
    require(paused);
    _;
  }

  /**
   * @dev called by the owner to pause, triggers stopped state
   */
  function pause() public onlyOwner whenNotPaused returns (bool) {
    paused = true;
    emit Pause();
    return true;
  }

  /**
   * @dev called by the owner to unpause, returns to normal state
   */
  function unpause() public onlyOwner whenPaused returns (bool) {
    paused = false;
    emit Unpause();
    return true;
  }
}

// File: contracts/Controllable.sol

pragma solidity ^0.5.0;


/**
 * @title Controllable
 * @dev The Controllable contract has an controller address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Controllable {
  address public controller;


  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender account.
   */
  constructor() public {
    controller = msg.sender;
  }

  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyController() {
    require(msg.sender == controller);
    _;
  }

  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newController The address to transfer ownership to.
   */
  function transferControl(address newController) public onlyController {
    if (newController != address(0)) {
      controller = newController;
    }
  }

}

// File: contracts/TokenInterface.sol

pragma solidity ^0.5.0;


/**
 * @title Token (WIRA)
 * Standard Mintable ERC20 Token
 * https://github.com/ethereum/EIPs/issues/20
 * Based on code by FirstBlood:
 * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
 */
contract TokenInterface is Controllable {

  event Mint(address indexed to, uint256 amount);
  event MintFinished();
  event ClaimedTokens(address indexed _token, address indexed _owner, uint _amount);
  event NewCloneToken(address indexed _cloneToken, uint _snapshotBlock);
  event Approval(address indexed _owner, address indexed _spender, uint256 _amount);
  event Transfer(address indexed from, address indexed to, uint256 value);

  function totalSupply() public view returns (uint);
  function totalSupplyAt(uint _blockNumber) public view returns(uint);
  function balanceOf(address _owner) public view returns (uint256 balance);
  function balanceOfAt(address _owner, uint _blockNumber) public view returns (uint);
  function transfer(address _to, uint256 _amount) public returns (bool success);
  function transferFrom(address _from, address _to, uint256 _amount) public returns (bool success);
  function approve(address _spender, uint256 _amount) public returns (bool success);
  function allowance(address _owner, address _spender) public view returns (uint256 remaining);
  function mint(address _owner, uint _amount) public returns (bool);
  function enableTransfers() public returns (bool);
  function finishMinting() public returns (bool);
}

// File: contracts/WiraTokenSale.sol

pragma solidity ^0.5.0;



/**
 * @title WiraTokenSale
 * Tokensale allows investors to make token purchases and assigns them tokens based

 * on a token per ETH rate. Funds collected are forwarded to a wallet as they arrive.
 */
 contract WiraTokenSale is Pausable {
   using SafeMath for uint256;

   TokenInterface public token;
   uint256 public totalWeiRaised;
   uint256 public tokensMinted;
   uint256 public contributors;

   bool public teamTokensMinted = false;
   bool public finalized = false;

   address payable tokenSaleWalletAddress;
   address public tokenWalletAddress;
   uint256 public constant FIRST_ROUND_CAP = 20000000 * 10 ** 18;
   uint256 public constant SECOND_ROUND_CAP = 70000000 * 10 ** 18;
   uint256 public constant TOKENSALE_CAP = 122500000 * 10 ** 18;
   uint256 public constant TOTAL_CAP = 408333334 * 10 ** 18;
   uint256 public constant TEAM_TOKENS = 285833334 * 10 ** 18; //TOTAL_CAP - TOKENSALE_CAP

   uint256 public conversionRateInCents = 15000; // 1ETH = 15000 cents by default - can be updated
   uint256 public firstRoundStartDate;
   uint256 public firstRoundEndDate;
   uint256 public secondRoundStartDate;
   uint256 public secondRoundEndDate;
   uint256 public thirdRoundStartDate;
   uint256 public thirdRoundEndDate;

   event TokenPurchase(address indexed purchaser, address indexed beneficiary, uint256 value, uint256 amount);
   event Finalized();

   constructor(
     address _tokenAddress,
     uint256 _startDate,
     address _tokenSaleWalletAddress,
     address _tokenWalletAddress
   ) public {
     require(_tokenAddress != address(0));

      token = TokenInterface(_tokenAddress);

      //Hardcoded to conform to the current tokensale plan with firstRoundStartDate = _startDate = 1556668800
      //firstRoundStartDate = 1556668800;  //1st May 2019 @ 00:00 GMT
      //firstRoundEndDate = 1557187200; // 7th May 2019 @ 00:00 GMT
      //secondRoundStartDate = 1557273600; // 8th May 2019 @ 00:00 GMT
      //secondRoundEndDate = 1557792000; // 14th May 2019 @ 00:00 GMT
      //thirdRoundStartDate = 1557878400; // 15th May 2019 @ 00:00 GMT
      //thirdRoundEndDate = 1561939200; // 1st July 2019 @ 00:00 GMT
      firstRoundStartDate = _startDate;
      firstRoundEndDate = _startDate + 518400;
      secondRoundStartDate = _startDate + 604800;
      secondRoundEndDate = _startDate + 1123200;
      thirdRoundStartDate = _startDate + 1209600;
      thirdRoundEndDate = _startDate + 5270400;

      tokenSaleWalletAddress = address(uint160(_tokenSaleWalletAddress));
      tokenWalletAddress = _tokenWalletAddress;
   }

   /**
    * High level token purchase function
    */
   function() external payable {
     buyTokens(msg.sender);
   }


   /**
    * Mint team tokens
    */
   function mintTeamTokens() public onlyOwner {
     require(!teamTokensMinted);
     token.mint(tokenWalletAddress, TEAM_TOKENS);
     teamTokensMinted = true;
   }

   /**
    * Low level token purchase function
    * @param _beneficiary will receive the tokens.
    */
   function buyTokens(address _beneficiary) public payable whenNotPaused whenNotFinalized {
     require(_beneficiary != address(0));
     validatePurchase();

     uint256 current = now;
     uint256 tokens;

     totalWeiRaised = totalWeiRaised.add(msg.value);

     if (now >= firstRoundStartDate && now <= firstRoundEndDate) {
      tokens = (msg.value * conversionRateInCents) / 10;
     } else if (now >= secondRoundStartDate && now <= secondRoundEndDate) {
       tokens = (msg.value * conversionRateInCents) / 15;
     } else if (now >= thirdRoundStartDate && now <= thirdRoundEndDate) {
       tokens = (msg.value * conversionRateInCents) / 20;
     }

    contributors = contributors.add(1);
    tokensMinted = tokensMinted.add(tokens);

    /*
    *@info: msg.value can stay in Wei as long as decimals for the tokens are the same as Ethereum (18 decimals)
    */
    bool earlyBirdSale = (current >= firstRoundStartDate && current <= firstRoundEndDate);
    bool prelaunchSale = (current >= secondRoundStartDate && current <= secondRoundEndDate);
    bool mainSale = (current >= thirdRoundStartDate && current <= thirdRoundEndDate);

    if (earlyBirdSale) require(tokensMinted < FIRST_ROUND_CAP);
    if (prelaunchSale) require(tokensMinted < SECOND_ROUND_CAP);
    if (mainSale) require(tokensMinted < TOKENSALE_CAP);

    token.mint(_beneficiary, tokens);
    emit TokenPurchase(msg.sender, _beneficiary, msg.value, tokens);
    forwardFunds();
   }

   function updateConversionRate(uint256 _conversionRateInCents) onlyOwner public {
     conversionRateInCents = _conversionRateInCents;
   }

   /**
   * Forwards funds to the tokensale wallet
   */
   function forwardFunds() internal {
     address(tokenSaleWalletAddress).transfer(msg.value);
   }

   function currentDate() public view returns (uint256) {
     return now;
   }

   /**
   * Validates the purchase (period, minimum amount, within cap)
   * @return {bool} valid
   */
   function validatePurchase() internal returns (bool) {
     uint256 current = now;
     bool duringFirstRound = (current >= firstRoundStartDate && current <= firstRoundEndDate);
     bool duringSecondRound = (current >= secondRoundStartDate && current <= secondRoundEndDate);
     bool duringThirdRound = (current >= thirdRoundStartDate && current <= thirdRoundEndDate);
     bool nonZeroPurchase = msg.value != 0;

     require(duringFirstRound || duringSecondRound || duringThirdRound);
     require(nonZeroPurchase);
   }

   /**
   * Returns the total WIRA token supply
   * @return totalSupply {uint256} WIRA Token Total Supply
   */
   function totalSupply() public view returns (uint256) {
     return token.totalSupply();
   }

   /**
   * Returns token holder WIRA Token balance
   * @param _owner {address} Token holder address
   * @return balance {uint256} Corresponding token holder balance
   */
   function balanceOf(address _owner) public view returns (uint256) {
     return token.balanceOf(_owner);
   }

   /**
   * Change the WIRA Token controller
   * @param _newController {address} New WIRA Token controller
   */
   function changeController(address _newController) public onlyOwner {
     require(isContract(_newController));
     token.transferControl(_newController);
   }

   function finalize() public onlyOwner {
     require(paused);
     emit Finalized();

    uint256 remainingTokens = TOKENSALE_CAP - tokensMinted;
    token.mint(tokenWalletAddress, remainingTokens);

     finalized = true;
   }

   function enableTransfers() public onlyOwner {
     token.enableTransfers();
   }


   function isContract(address _addr) view internal returns(bool) {
     uint size;
     if (_addr == address(0))
       return false;
     assembly {
         size := extcodesize(_addr)
     }
     return size>0;
   }

   modifier whenNotFinalized() {
     require(!finalized);
     _;
   }

 }