pragma solidity ^0.4.13;
contract IERC20Token {
  function totalSupply() constant returns (uint256 totalSupply);
  function balanceOf(address _owner) constant returns (uint256 balance) {}
  function transfer(address _to, uint256 _value) returns (bool success) {}
  function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {}
  function approve(address _spender, uint256 _value) returns (bool success) {}
  function allowance(address _owner, address _spender) constant returns (uint256 remaining) {}
  event Transfer(address indexed _from, address indexed _to, uint256 _value);
  event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
contract IToken {
  function totalSupply() constant returns (uint256 totalSupply);
  function mintTokens(address _to, uint256 _amount) {}
}
contract Owned {
    address public owner;
    address public newOwner;
    function Owned() {
        owner = msg.sender;
    }
    modifier onlyOwner {
        assert(msg.sender == owner);
        _;
    }
    function transferOwnership(address _newOwner) public onlyOwner {
        require(_newOwner != owner);
        newOwner = _newOwner;
    }
    function acceptOwnership() public {
        require(msg.sender == newOwner);
        OwnerUpdate(owner, newOwner);
        owner = newOwner;
        newOwner = 0x0;
    }
    event OwnerUpdate(address _prevOwner, address _newOwner);
}
contract ReentrancyHandling {
    bool locked;
    modifier noReentrancy() {
        require(!locked);
        locked = true;
        _;
        locked = false;
    }
}
/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {
  function mul(uint256 a, uint256 b) internal constant returns (uint256) {
    uint256 c = a * b;
    assert(a == 0 || c / a == b);
    return c;
  }
  function div(uint256 a, uint256 b) internal constant 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 constant returns (uint256) {
    assert(b <= a);
    return a - b;
  }
  function add(uint256 a, uint256 b) internal constant returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}
contract Crowdsale is ReentrancyHandling, Owned {
  using SafeMath for uint256;
  
  struct ContributorData {
    bool isWhiteListed;
    bool isCommunityRoundApproved;
    uint256 contributionAmount;
    uint256 tokensIssued;
  }
  mapping(address => ContributorData) public contributorList;
  enum state { pendingStart, communityRound, crowdsaleStarted, crowdsaleEnded }
  state crowdsaleState;
  uint public communityRoundStartDate;
  uint public crowdsaleStartDate;
  uint public crowdsaleEndDate;
  event CommunityRoundStarted(uint timestamp);
  event CrowdsaleStarted(uint timestamp);
  event CrowdsaleEnded(uint timestamp);
  IToken token = IToken(0x0);
  uint ethToTokenConversion;
  uint256 maxCrowdsaleCap;
  uint256 maxCommunityCap;
  uint256 maxCommunityWithoutBonusCap;
  uint256 maxContribution;
  uint256 tokenSold = 0;
  uint256 communityTokenSold = 0;
  uint256 communityTokenWithoutBonusSold = 0;
  uint256 crowdsaleTokenSold = 0;
  uint256 public ethRaisedWithoutCompany = 0;
  address companyAddress;   // company wallet address in cold/hardware storage 
  uint maxTokenSupply;
  uint companyTokens;
  bool treasuryLocked = false;
  bool ownerHasClaimedTokens = false;
  bool ownerHasClaimedCompanyTokens = false;
  // validates sender is whitelisted
  modifier onlyWhiteListUser {
    require(contributorList[msg.sender].isWhiteListed == true);
    _;
  }
  // limit gas price to 50 Gwei (about 5-10x the normal amount)
  modifier onlyLowGasPrice {
	  require(tx.gasprice <= 50*10**9 wei);
	  _;
  }
  //
  // Unnamed function that runs when eth is sent to the contract
  //
  function() public noReentrancy onlyWhiteListUser onlyLowGasPrice payable {
    require(msg.value != 0);                                         // Throw if value is 0
    require(companyAddress != 0x0);
    require(token != IToken(0x0));
    checkCrowdsaleState();                                           // Calibrate crowdsale state
    assert((crowdsaleState == state.communityRound && contributorList[msg.sender].isCommunityRoundApproved) ||
            crowdsaleState == state.crowdsaleStarted);
    
    processTransaction(msg.sender, msg.value);                       // Process transaction and issue tokens
    checkCrowdsaleState();                                           // Calibrate crowdsale state
  }
  // 
  // return state of smart contract
  //
  function getState() public constant returns (uint256, uint256, uint) {
    uint currentState = 0;
    if (crowdsaleState == state.pendingStart) {
      currentState = 1;
    }
    else if (crowdsaleState == state.communityRound) {
      currentState = 2;
    }
    else if (crowdsaleState == state.crowdsaleStarted) {
      currentState = 3;
    }
    else if (crowdsaleState == state.crowdsaleEnded) {
      currentState = 4;
    }
    return (tokenSold, communityTokenSold, currentState);
  }
  //
  // Check crowdsale state and calibrate it
  //
  function checkCrowdsaleState() internal {
    if (now > crowdsaleEndDate || tokenSold >= maxTokenSupply) {  // end crowdsale once all tokens are sold or run out of time
      if (crowdsaleState != state.crowdsaleEnded) {
        crowdsaleState = state.crowdsaleEnded;
        CrowdsaleEnded(now);
      }
    }
    else if (now > crowdsaleStartDate) { // move into crowdsale round
      if (crowdsaleState != state.crowdsaleStarted) {
        uint256 communityTokenRemaining = maxCommunityCap.sub(communityTokenSold);  // apply any remaining tokens from community round to crowdsale round
        maxCrowdsaleCap = maxCrowdsaleCap.add(communityTokenRemaining);
        crowdsaleState = state.crowdsaleStarted;  // change state
        CrowdsaleStarted(now);
      }
    }
    else if (now > communityRoundStartDate) {
      if (communityTokenSold < maxCommunityCap) {
        if (crowdsaleState != state.communityRound) {
          crowdsaleState = state.communityRound;
          CommunityRoundStarted(now);
        }
      }
      else {  // automatically start crowdsale when all community round tokens are sold out 
        if (crowdsaleState != state.crowdsaleStarted) {
          crowdsaleState = state.crowdsaleStarted;
          CrowdsaleStarted(now);
        }
      }
    }
  }
  //
  // Issue tokens and return if there is overflow
  //
  function calculateCommunity(address _contributor, uint256 _newContribution) internal returns (uint256, uint256) {
    uint256 communityEthAmount = 0;
    uint256 communityTokenAmount = 0;
    uint previousContribution = contributorList[_contributor].contributionAmount;  // retrieve previous contributions
    // community round ONLY
    if (crowdsaleState == state.communityRound && 
        contributorList[_contributor].isCommunityRoundApproved && 
        previousContribution < maxContribution) {
        communityEthAmount = _newContribution;
        uint256 availableEthAmount = maxContribution.sub(previousContribution);                 
        // limit the contribution ETH amount to the maximum allowed for the community round
        if (communityEthAmount > availableEthAmount) {
          communityEthAmount = availableEthAmount;
        }
        // compute community tokens without bonus
        communityTokenAmount = communityEthAmount.mul(ethToTokenConversion);
        uint256 availableTokenAmount = maxCommunityWithoutBonusCap.sub(communityTokenWithoutBonusSold);
        // verify community tokens do not go over the max cap for community round
        if (communityTokenAmount > availableTokenAmount) {
          // cap the tokens to the max allowed for the community round
          communityTokenAmount = availableTokenAmount;
          // recalculate the corresponding ETH amount
          communityEthAmount = communityTokenAmount.div(ethToTokenConversion);
        }
        // track tokens sold during community round
        communityTokenWithoutBonusSold = communityTokenWithoutBonusSold.add(communityTokenAmount);
        // compute bonus tokens
        uint256 bonusTokenAmount = communityTokenAmount.mul(15);
        bonusTokenAmount = bonusTokenAmount.div(100);
        // add bonus to community tokens
        communityTokenAmount = communityTokenAmount.add(bonusTokenAmount);
        // track tokens sold during community round
        communityTokenSold = communityTokenSold.add(communityTokenAmount);
    }
    return (communityTokenAmount, communityEthAmount);
  }
  //
  // Issue tokens and return if there is overflow
  //
  function calculateCrowdsale(uint256 _remainingContribution) internal returns (uint256, uint256) {
    uint256 crowdsaleEthAmount = _remainingContribution;
    // compute crowdsale tokens
    uint256 crowdsaleTokenAmount = crowdsaleEthAmount.mul(ethToTokenConversion);
    // determine crowdsale tokens remaining
    uint256 availableTokenAmount = maxCrowdsaleCap.sub(crowdsaleTokenSold);
    // verify crowdsale tokens do not go over the max cap for crowdsale round
    if (crowdsaleTokenAmount > availableTokenAmount) {
      // cap the tokens to the max allowed for the crowdsale round
      crowdsaleTokenAmount = availableTokenAmount;
      // recalculate the corresponding ETH amount
      crowdsaleEthAmount = crowdsaleTokenAmount.div(ethToTokenConversion);
    }
    // track tokens sold during crowdsale round
    crowdsaleTokenSold = crowdsaleTokenSold.add(crowdsaleTokenAmount);
    return (crowdsaleTokenAmount, crowdsaleEthAmount);
  }
  //
  // Issue tokens and return if there is overflow
  //
  function processTransaction(address _contributor, uint256 _amount) internal {
    uint256 newContribution = _amount;
    var (communityTokenAmount, communityEthAmount) = calculateCommunity(_contributor, newContribution);
    // compute remaining ETH amount available for purchasing crowdsale tokens
    var (crowdsaleTokenAmount, crowdsaleEthAmount) = calculateCrowdsale(newContribution.sub(communityEthAmount));
    // add up crowdsale + community tokens
    uint256 tokenAmount = crowdsaleTokenAmount.add(communityTokenAmount);
    assert(tokenAmount > 0);
    // Issue new tokens
    token.mintTokens(_contributor, tokenAmount);                              
    // log token issuance
    contributorList[_contributor].tokensIssued = contributorList[_contributor].tokensIssued.add(tokenAmount);                
    // Add contribution amount to existing contributor
    newContribution = crowdsaleEthAmount.add(communityEthAmount);
    contributorList[_contributor].contributionAmount = contributorList[_contributor].contributionAmount.add(newContribution);
    ethRaisedWithoutCompany = ethRaisedWithoutCompany.add(newContribution);                              // Add contribution amount to ETH raised
    tokenSold = tokenSold.add(tokenAmount);                                  // track how many tokens are sold
    // compute any refund if applicable
    uint256 refundAmount = _amount.sub(newContribution);
    if (refundAmount > 0) {
      _contributor.transfer(refundAmount);                                   // refund contributor amount behind the maximum ETH cap
    }
    companyAddress.transfer(newContribution);                                // send ETH to company
  }
  //
  // whitelist validated participants.
  //
  function WhiteListContributors(address[] _contributorAddresses, bool[] _contributorCommunityRoundApproved) public onlyOwner {
    require(_contributorAddresses.length == _contributorCommunityRoundApproved.length); // Check if input data is correct
    for (uint cnt = 0; cnt < _contributorAddresses.length; cnt++) {
      contributorList[_contributorAddresses[cnt]].isWhiteListed = true;
      contributorList[_contributorAddresses[cnt]].isCommunityRoundApproved = _contributorCommunityRoundApproved[cnt];
    }
  }
  //
  // Method is needed for recovering tokens accidentally sent to token address
  //
  function salvageTokensFromContract(address _tokenAddress, address _to, uint _amount) public onlyOwner {
    IERC20Token(_tokenAddress).transfer(_to, _amount);
  }
  //
  // Owner can set multisig address for crowdsale
  //
  function setCompanyAddress(address _newAddress) public onlyOwner {
    require(!treasuryLocked);                              // Check if owner has already claimed tokens
    companyAddress = _newAddress;
    treasuryLocked = true;
  }
  //
  // Owner can set token address where mints will happen
  //
  function setToken(address _newAddress) public onlyOwner {
    token = IToken(_newAddress);
  }
  function getToken() public constant returns (address) {
    return address(token);
  }
  //
  // Claims company tokens
  //
  function claimCompanyTokens() public onlyOwner {
    require(!ownerHasClaimedCompanyTokens);                     // Check if owner has already claimed tokens
    require(companyAddress != 0x0);
    
    tokenSold = tokenSold.add(companyTokens); 
    token.mintTokens(companyAddress, companyTokens);            // Issue company tokens 
    ownerHasClaimedCompanyTokens = true;                        // Block further mints from this method
  }
  //
  // Claim remaining tokens when crowdsale ends
  //
  function claimRemainingTokens() public onlyOwner {
    checkCrowdsaleState();                                        // Calibrate crowdsale state
    require(crowdsaleState == state.crowdsaleEnded);              // Check crowdsale has ended
    require(!ownerHasClaimedTokens);                              // Check if owner has already claimed tokens
    require(companyAddress != 0x0);
    uint256 remainingTokens = maxTokenSupply.sub(token.totalSupply());
    token.mintTokens(companyAddress, remainingTokens);            // Issue tokens to company
    ownerHasClaimedTokens = true;                                 // Block further mints from this method
  }
}
contract StormCrowdsale is Crowdsale {
    string public officialWebsite;
    string public officialFacebook;
    string public officialTelegram;
    string public officialEmail;
  function StormCrowdsale() public {
    officialWebsite = "https://www.stormtoken.com";
    officialFacebook = "https://www.facebook.com/stormtoken/";
    officialTelegram = "https://t.me/joinchat/GHTZGQwsy9mZk0KFEEjGtg";
    officialEmail = "info@stormtoken.com";
    communityRoundStartDate = 1510063200;                       // Nov 7, 2017 @ 6am PST
    crowdsaleStartDate = communityRoundStartDate + 24 hours;    // 24 hours later
    crowdsaleEndDate = communityRoundStartDate + 30 days + 12 hours; // 30 days + 12 hours later: Dec 7th, 2017 @ 6pm PST [1512698400]
    crowdsaleState = state.pendingStart;
    ethToTokenConversion = 26950;                 // 1 ETH == 26,950 STORM tokens
    maxTokenSupply = 10000000000 ether;           // 10,000,000,000
    companyTokens = 8124766171 ether;             // allocation for company pool, private presale, user pool 
                                                  // 2,325,649,071 tokens from the company pool are voluntarily locked for 2 years
    maxCommunityWithoutBonusCap = 945000000 ether;
    maxCommunityCap = 1086750000 ether;           // 945,000,000 with 15% bonus of 141,750,000
    maxCrowdsaleCap = 788483829 ether;            // tokens allocated to crowdsale 
    maxContribution = 100 ether;                  // maximum contribution during community round
  }
}