pragma solidity ^0.4.19;

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {

  /**
  * @dev Multiplies two numbers, throws on overflow.
  */
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  /**
  * @dev Integer division of two numbers, truncating the quotient.
  */
  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;
  }

  /**
  * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
  */
  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  /**
  * @dev Adds two numbers, throws on overflow.
  */
  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}

/**
 * @title ERC20Basic
 * @dev Simpler version of ERC20 interface
 * @dev see https://github.com/ethereum/EIPs/issues/179
 */
contract ERC20Basic {
  function totalSupply() public view returns (uint256);
  function balanceOf(address who) public view returns (uint256);
  function transfer(address to, uint256 value) public returns (bool);
  event Transfer(address indexed from, address indexed to, uint256 value);
}

/**
 * @title Basic token
 * @dev Basic version of StandardToken, with no allowances.
 */
contract BasicToken is ERC20Basic {
  using SafeMath for uint256;

  mapping(address => uint256) balances;

  uint256 totalSupply_;

  /**
  * @dev total number of tokens in existence
  */
  function totalSupply() public view returns (uint256) {
    return totalSupply_;
  }

  /**
  * @dev transfer token for a specified address
  * @param _to The address to transfer to.
  * @param _value The amount to be transferred.
  */
  function transfer(address _to, uint256 _value) public returns (bool) {
    require(_to != address(0));
    require(_value <= balances[msg.sender]);

    // SafeMath.sub will throw if there is not enough balance.
    balances[msg.sender] = balances[msg.sender].sub(_value);
    balances[_to] = balances[_to].add(_value);
    Transfer(msg.sender, _to, _value);
    return true;
  }

  /**
  * @dev Gets the balance of the specified address.
  * @param _owner The address to query the the balance of.
  * @return An uint256 representing the amount owned by the passed address.
  */
  function balanceOf(address _owner) public view returns (uint256 balance) {
    return balances[_owner];
  }

}

/**
 * @title ERC20 interface
 * @dev see https://github.com/ethereum/EIPs/issues/20
 */
contract ERC20 is ERC20Basic {
  function allowance(address owner, address spender) public view returns (uint256);
  function transferFrom(address from, address to, uint256 value) public returns (bool);
  function approve(address spender, uint256 value) public returns (bool);
  event Approval(address indexed owner, address indexed spender, uint256 value);
}


/**
 * @title Standard ERC20 token
 *
 * @dev Implementation of the basic standard token.
 * @dev https://github.com/ethereum/EIPs/issues/20
 * @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
 */
contract StandardToken is ERC20, BasicToken {

  mapping (address => mapping (address => uint256)) internal allowed;


  /**
   * @dev Transfer tokens from one address to another
   * @param _from address The address which you want to send tokens from
   * @param _to address The address which you want to transfer to
   * @param _value uint256 the amount of tokens to be transferred
   */
  function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
    require(_to != address(0));
    require(_value <= balances[_from]);
    require(_value <= allowed[_from][msg.sender]);

    balances[_from] = balances[_from].sub(_value);
    balances[_to] = balances[_to].add(_value);
    allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
    Transfer(_from, _to, _value);
    return true;
  }

  /**
   * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
   *
   * Beware that changing an allowance with this method brings the risk that someone may use both the old
   * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
   * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
   * @param _spender The address which will spend the funds.
   * @param _value The amount of tokens to be spent.
   */
  function approve(address _spender, uint256 _value) public returns (bool) {
    allowed[msg.sender][_spender] = _value;
    Approval(msg.sender, _spender, _value);
    return true;
  }

  /**
   * @dev Function to check the amount of tokens that an owner allowed to a spender.
   * @param _owner address The address which owns the funds.
   * @param _spender address The address which will spend the funds.
   * @return A uint256 specifying the amount of tokens still available for the spender.
   */
  function allowance(address _owner, address _spender) public view returns (uint256) {
    return allowed[_owner][_spender];
  }

  /**
   * @dev Increase the amount of tokens that an owner allowed to a spender.
   *
   * approve should be called when allowed[_spender] == 0. To increment
   * allowed value is better to use this function to avoid 2 calls (and wait until
   * the first transaction is mined)
   * From MonolithDAO Token.sol
   * @param _spender The address which will spend the funds.
   * @param _addedValue The amount of tokens to increase the allowance by.
   */
  function increaseApproval(address _spender, uint _addedValue) public returns (bool) {
    allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue);
    Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
    return true;
  }

  /**
   * @dev Decrease the amount of tokens that an owner allowed to a spender.
   *
   * approve should be called when allowed[_spender] == 0. To decrement
   * allowed value is better to use this function to avoid 2 calls (and wait until
   * the first transaction is mined)
   * From MonolithDAO Token.sol
   * @param _spender The address which will spend the funds.
   * @param _subtractedValue The amount of tokens to decrease the allowance by.
   */
  function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) {
    uint oldValue = allowed[msg.sender][_spender];
    if (_subtractedValue > oldValue) {
      allowed[msg.sender][_spender] = 0;
    } else {
      allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
    }
    Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
    return true;
  }

}

/**
 * @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 {
    //Variables
    address public owner;

    address public newOwner;

    //    Modifiers
    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    /**
     * @dev The Ownable constructor sets the original `owner` of the contract to the sender
     * account.
     */
    function Ownable() public {
        owner = msg.sender;
    }

    /**
     * @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 {
        require(_newOwner != address(0));
        newOwner = _newOwner;

    }

    function acceptOwnership() public {
        if (msg.sender == newOwner) {
            owner = newOwner;
        }
    }
}

contract ElyERC20 is StandardToken, Ownable {
    /* Public variables of the token */
    uint256 public creationBlock;

    uint8 public decimals;

    string public name;

    string public symbol;

    string public standard;

    bool public locked;

    /* Initializes contract with initial supply tokens to the creator of the contract */
    function ElyERC20(
        uint256 _totalSupply,
        string _tokenName,
        uint8 _decimalUnits,
        string _tokenSymbol,
        bool _transferAllSupplyToOwner,
        bool _locked
    ) public {
        standard = "ERC20 0.1";
        locked = _locked;
        totalSupply_ = _totalSupply;

        if (_transferAllSupplyToOwner) {
            balances[msg.sender] = totalSupply_;
        } else {
            balances[this] = totalSupply_;
        }
        name = _tokenName;
        // Set the name for display purposes
        symbol = _tokenSymbol;
        // Set the symbol for display purposes
        decimals = _decimalUnits;
        // Amount of decimals for display purposes
        creationBlock = block.number;
    }

    /* public methods */
    function transfer(address _to, uint256 _value) public returns (bool) {
        require(locked == false);
        return super.transfer(_to, _value);
    }

    function approve(address _spender, uint256 _value) public returns (bool success) {
        if (locked) {
            return false;
        }
        return super.approve(_spender, _value);
    }

    function increaseApproval(address _spender, uint _addedValue) public returns (bool success) {
        if (locked) {
            return false;
        }
        return super.increaseApproval(_spender, _addedValue);
    }

    function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool success) {
        if (locked) {
            return false;
        }
        return super.decreaseApproval(_spender, _subtractedValue);
    }

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        if (locked) {
            return false;
        }

        return super.transferFrom(_from, _to, _value);
    }

}

contract ElyAllocation is Ownable {

    ElyToken public token;
    ICO public ico;

    using SafeMath for uint256;

    uint256 public constant DECIMALS = 18;

    uint256 public remainingTeamTokens = uint256(100000000).mul(uint(10) ** uint(DECIMALS));

    uint256 public remainingGrowthTokens = uint256(350000000).mul(uint(10) ** uint(DECIMALS));

    address public growthTokensAddress;

    uint256 public lastReleaseTime;
    uint256 public growthTokensLastClaim;

    address[] public team;

    function ElyAllocation(
        address _token,
        address _ico,
        address _growthTokensAddress
    ) public {
        require(_token != address(0) && _ico != address(0) && _growthTokensAddress != address(0));
        token = ElyToken(_token);
        ico = ICO(_ico);
        lastReleaseTime = ico.endTime().add(1 days);
        growthTokensLastClaim = ico.endTime();
        growthTokensAddress = _growthTokensAddress;
    }

    function setTokenContract(address _token) public onlyOwner {
        require(_token != address(0));
        token = ElyToken(_token);
    }

    function setICO(address _ico) public onlyOwner {
        require(_ico != address(0));
        ico = ICO(_ico);
    }

    function setTeamAddresses(address[] _team) public onlyOwner {
        if (_team.length == 0) {
            return;
        }
        for (uint256 i = 0; i < _team.length; i++) {
            if (_team[i] == address(0)) {
                return;
            }
        }
        team = _team;
    }

    function claimGrowthTokens() public {
        if (growthTokensLastClaim > ico.endTime() && growthTokensLastClaim.add(1 years) > block.timestamp) {
            return;
        }

        uint256 amount = uint256(50000000).mul(uint(10) ** uint(DECIMALS));

        require(
            remainingGrowthTokens >= amount &&
            msg.sender == growthTokensAddress &&
            amount == token.mint(msg.sender, amount)
        );
        remainingGrowthTokens = remainingGrowthTokens.sub(amount);
        growthTokensLastClaim = growthTokensLastClaim.add(1 years);
    }

    function claim() public {
        if (team.length == 0 || lastReleaseTime.add(30 days) > block.timestamp) {
            return;
        }

        uint256 totalAmount = remainingTeamTokens.div(100);
        uint256 amount = totalAmount.div(team.length);

        if (totalAmount == 0 || amount == 0) {
            return;
        }

        for (uint256 i = 0; i < team.length; i++) {
            require(amount == token.mint(team[i], amount));
        }

        lastReleaseTime = lastReleaseTime.add(30 days);
        remainingTeamTokens = remainingTeamTokens.sub(totalAmount);
    }

}

contract LockupContract is Ownable {

    ElyToken public token;
    SellableToken public ico;
    Referral public referral;

    using SafeMath for uint256;

    uint256 public lockPeriod = 2 weeks;
    uint256 public contributionLockPeriod = uint256(1 years).div(2);

    mapping (address => uint256) public lockedAmount;
    mapping (address => uint256) public lockedContributions;

    function LockupContract(
        address _token,
        address _ico,
        address _referral
    ) public {
        require(_token != address(0) && _ico != address(0) && _referral != address(0));
        token = ElyToken(_token);
        ico = SellableToken(_ico);
        referral = Referral(_referral);
    }

    function setTokenContract(address _token) public onlyOwner {
        require(_token != address(0));
        token = ElyToken(_token);
    }

    function setICO(address _ico) public onlyOwner {
        require(_ico != address(0));
        ico = SellableToken(_ico);
    }

    function setRefferal(address _referral) public onlyOwner {
        require(_referral != address(0));
        referral = Referral(_referral);
    }

    function setLockPeriod(uint256 _period) public onlyOwner {
        lockPeriod = _period;
    }

    function setContributionLockPeriod(uint256 _period) public onlyOwner {
        contributionLockPeriod = _period;
    }

    function log(address _address, uint256 _amount) public {
        if (msg.sender == address(referral) || msg.sender == address(token)) {
            lockedAmount[_address] = lockedAmount[_address].add(_amount);
        }
    }

    function logLargeContribution(address _address, uint256 _amount) public {
        if (msg.sender == address(ico)) {
            lockedContributions[_address] = lockedContributions[_address].add(_amount);
        }
    }

    function isTransferAllowed(address _address, uint256 _value) public view returns (bool) {
        if (ico.endTime().add(lockPeriod) < block.timestamp) {
            return checkLargeContributionsLock(_address, _value);
        }
        if (token.balanceOf(_address).sub(lockedAmount[_address]) >= _value) {
            return checkLargeContributionsLock(_address, _value);
        }

        return false;
    }

    function checkLargeContributionsLock(address _address, uint256 _value) public view returns (bool) {
        if (ico.endTime().add(contributionLockPeriod) < block.timestamp) {
            return true;
        }
        if (token.balanceOf(_address).sub(lockedContributions[_address]) >= _value) {
            return true;
        }

        return false;
    }

}

contract Multivest is Ownable {

    using SafeMath for uint256;

    /* public variables */
    mapping (address => bool) public allowedMultivests;

    /* events */
    event MultivestSet(address multivest);

    event MultivestUnset(address multivest);

    event Contribution(address holder, uint256 value, uint256 tokens);

    modifier onlyAllowedMultivests(address _addresss) {
        require(allowedMultivests[_addresss] == true);
        _;
    }

    /* constructor */
    function Multivest() public {}

    function setAllowedMultivest(address _address) public onlyOwner {
        allowedMultivests[_address] = true;
        MultivestSet(_address);
    }

    function unsetAllowedMultivest(address _address) public onlyOwner {
        allowedMultivests[_address] = false;
        MultivestUnset(_address);
    }

    function multivestBuy(address _address, uint256 _value) public onlyAllowedMultivests(msg.sender) {
        require(buy(_address, _value) == true);
    }

    function multivestBuy(
        address _address,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    ) public payable onlyAllowedMultivests(verify(keccak256(msg.sender), _v, _r, _s)) {
        require(_address == msg.sender && buy(msg.sender, msg.value) == true);
    }

    function verify(bytes32 _hash, uint8 _v, bytes32 _r, bytes32 _s) internal pure returns (address) {
        bytes memory prefix = "\x19Ethereum Signed Message:\n32";

        return ecrecover(keccak256(prefix, _hash), _v, _r, _s);
    }

    function buy(address _address, uint256 _value) internal returns (bool);

}

contract Referral is Multivest {

    ElyToken public token;
    LockupContract public lockupContract;

    uint256 public constant DECIMALS = 18;

    uint256 public totalSupply = 10000000 * 10 ** DECIMALS;

    address public tokenHolder;

    mapping (address => bool) public claimed;

    /* constructor */
    function Referral(
        address _token,
        address _tokenHolder
    ) public Multivest() {
        require(_token != address(0) && _tokenHolder != address(0));
        token = ElyToken(_token);
        tokenHolder = _tokenHolder;
    }

    function setTokenContract(address _token) public onlyOwner {
        if (_token != address(0)) {
            token = ElyToken(_token);
        }
    }

    function setLockupContract(address _lockupContract) public onlyOwner {
        require(_lockupContract != address(0));
        lockupContract = LockupContract(_lockupContract);
    }

    function setTokenHolder(address _tokenHolder) public onlyOwner {
        if (_tokenHolder != address(0)) {
            tokenHolder = _tokenHolder;
        }
    }

    function multivestMint(
        address _address,
        uint256 _amount,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    ) public onlyAllowedMultivests(verify(keccak256(msg.sender, _amount), _v, _r, _s)) {
        _amount = _amount.mul(10 ** DECIMALS);
        require(
            claimed[_address] == false &&
            _address == msg.sender &&
            _amount > 0 &&
            _amount <= totalSupply &&
            _amount == token.mint(_address, _amount)
        );

        totalSupply = totalSupply.sub(_amount);
        claimed[_address] = true;
        lockupContract.log(_address, _amount);
    }

    function claimUnsoldTokens() public {
        if (msg.sender == tokenHolder && totalSupply > 0) {
            require(totalSupply == token.mint(msg.sender, totalSupply));
            totalSupply = 0;
        }
    }

    function buy(address _address, uint256 value) internal returns (bool) {
        _address = _address;
        value = value;
        return true;
    }
}

contract SellableToken is Multivest {

    ElyToken public token;

    uint256 public constant DECIMALS = 18;

    uint256 public minPurchase = 1000000;//10usd * 10 ^ 5

    uint256 public softCap = 300000000000;//usd * 10 ^ 5
    uint256 public hardCap = 1500000000000;//usd * 10 ^ 5

    uint256 public compensationAmount = 5100000000;//usd * 10 ^ 5
    uint256 public compensatedAmount;

    uint256 public startTime;
    uint256 public endTime;

    uint256 public maxTokenSupply;

    uint256 public soldTokens;

    uint256 public collectedEthers;

    uint256 public priceUpdateAt;

    address public etherHolder;

    address public compensationAddress;

    uint256 public collectedUSD;

    uint256 public etherPriceInUSD; //$753.25  75325000

    mapping (address => uint256) public etherBalances;

    mapping (address => bool) public whitelist;

    Tier[] public tiers;

    struct Tier {
        uint256 maxAmount;
        uint256 price;
        uint256 startTime;
        uint256 endTime;
    }

    event WhitelistSet(address indexed contributorAddress, bool isWhitelisted);

    event Refund(address _holder, uint256 _ethers, uint256 _tokens);

    function SellableToken(
        address _token,
        address _etherHolder,
        address _compensationAddress,
        uint256 _etherPriceInUSD,
        uint256 _maxTokenSupply
    )
        public Multivest()
    {
        require(_token != address(0));
        token = ElyToken(_token);

        require(_etherHolder != address(0) && _compensationAddress != address(0));
        etherHolder = _etherHolder;
        compensationAddress = _compensationAddress;
        require((_maxTokenSupply == uint256(0)) || (_maxTokenSupply <= token.maxSupply()));

        etherPriceInUSD = _etherPriceInUSD;
        maxTokenSupply = _maxTokenSupply;

        priceUpdateAt = block.timestamp;
    }

    function() public payable {
        require(true == whitelist[msg.sender] && buy(msg.sender, msg.value) == true);
    }

    function setTokenContract(address _token) public onlyOwner {
        require(_token != address(0));
        token = ElyToken(_token);
    }

    function isActive() public view returns (bool) {
        if (maxTokenSupply > uint256(0) && soldTokens == maxTokenSupply) {
            return false;
        }

        return withinPeriod();
    }

    function withinPeriod() public view returns (bool) {
        return block.timestamp >= startTime && block.timestamp <= endTime;
    }

    function setEtherHolder(address _etherHolder) public onlyOwner {
        if (_etherHolder != address(0)) {
            etherHolder = _etherHolder;
        }
    }

    function setCompensationAddress(address _compensationAddress) public onlyOwner {
        if (_compensationAddress != address(0)) {
            compensationAddress = _compensationAddress;
        }
    }

    function updateWhitelist(address _address, bool isWhitelisted) public onlyOwner {
        whitelist[_address] = isWhitelisted;
        WhitelistSet(_address, isWhitelisted);
    }

    function mint(address _address, uint256 _tokenAmount) public onlyOwner returns (uint256) {
        return mintInternal(_address, _tokenAmount);
    }

    function setEtherPriceInUSD(string _price) public onlyOwner {
        setEtherInUSDInternal(_price);
    }

    function setEtherInUSD(string _price) public onlyAllowedMultivests(msg.sender) {
        setEtherInUSDInternal(_price);
    }

    // set ether price in USD with 5 digits after the decimal point
    //ex. 308.75000
    //for updating the price through  multivest
    function setEtherInUSDInternal(string _price) internal {
        bytes memory bytePrice = bytes(_price);
        uint256 dot = bytePrice.length.sub(uint256(6));

        // check if dot is in 6 position  from  the last
        require(0x2e == uint(bytePrice[dot]));

        uint256 newPrice = uint256(10 ** 23).div(parseInt(_price, 5));

        require(newPrice > 0);

        etherPriceInUSD = parseInt(_price, 5);

        priceUpdateAt = block.timestamp;
    }

    function mintInternal(address _address, uint256 _tokenAmount) internal returns (uint256) {
        uint256 mintedAmount = token.mint(_address, _tokenAmount);

        require(mintedAmount == _tokenAmount);

        mintedAmount = mintedAmount.add(token.mint(compensationAddress, _tokenAmount.mul(5).div(1000)));

        soldTokens = soldTokens.add(_tokenAmount);
        if (maxTokenSupply > 0) {
            require(maxTokenSupply >= soldTokens);
        }

        return _tokenAmount;
    }

    function transferEthersInternal() internal {
        if (collectedUSD >= softCap) {
            if (compensatedAmount < compensationAmount) {
                uint256 amount = uint256(1 ether).mul(compensationAmount.sub(compensatedAmount)).div(etherPriceInUSD);
                compensationAddress.transfer(amount);
                compensatedAmount = compensationAmount;
            }

            etherHolder.transfer(this.balance);
        }
    }

    function parseInt(string _a, uint _b) internal pure returns (uint) {
        bytes memory bresult = bytes(_a);
        uint mintt = 0;
        bool decimals = false;
        for (uint i=0; i<bresult.length; i++){
            if ((bresult[i] >= 48)&&(bresult[i] <= 57)){
                if (decimals){
                    if (_b == 0) break;
                    else _b--;
                }
                mintt *= 10;
                mintt += uint(bresult[i]) - 48;
            } else if (bresult[i] == 46) decimals = true;
        }
        if (_b > 0) mintt *= 10**_b;
        return mintt;
    }

}

contract ICO is SellableToken {

    SellableToken public privateSale;
    LockupContract public lockupContract;

    uint8 public constant PRE_ICO_TIER = 0;
    uint8 public constant ICO_TIER_FIRST = 1;
    uint8 public constant ICO_TIER_TWO = 2;
    uint8 public constant ICO_TIER_LAST = 3;

    Stats public preICOStats;

    uint256 public lockupThreshold = 10000000000;

    struct Stats {
        uint256 soldTokens;
        uint256 collectedUSD;
        uint256 collectedEthers;
        bool burned;
    }

    function ICO(
        address _token,
        address _etherHolder,
        address _compensationAddress,
        uint256 _etherPriceInUSD, // if price 709.38000 the  value has to be 70938000
        uint256 _maxTokenSupply
    ) public SellableToken(
        _token,
        _etherHolder,
        _compensationAddress,
        _etherPriceInUSD,
        _maxTokenSupply
    ) {
        tiers.push(
            Tier(
                uint256(40000000).mul(uint256(10) ** DECIMALS),
                uint256(6000),
                1525046400,
                1525564800
            )
        );//@ 0,06 USD PreICO
        tiers.push(
            Tier(
                uint256(150000000).mul(uint256(10) ** DECIMALS),
                uint256(8000),
                1526256000,
                1526774400
            )
        );//@ 0,08 USD
        tiers.push(
            Tier(
                uint256(150000000).mul(uint256(10) ** DECIMALS),
                uint256(10000),
                1526860800,
                1527379200
            )
        );//@ 0,10 USD
        tiers.push(
            Tier(
                uint256(150000000).mul(uint256(10) ** DECIMALS),
                uint256(12000),
                1527465600,
                1527984000
            )
        );//@ 0,12 USD

        startTime = 1526256000;
        endTime = 1527984000;
    }

    function setPrivateSale(address _privateSale) public onlyOwner {
        if (_privateSale != address(0)) {
            privateSale = SellableToken(_privateSale);
        }
    }

    function setLockupContract(address _lockupContract) public onlyOwner {
        require(_lockupContract != address(0));
        lockupContract = LockupContract(_lockupContract);
    }

    function changePreICODates(uint256 _start, uint256 _end) public onlyOwner {
        if (_start != 0 && _start < _end) {
            Tier storage preICOTier = tiers[PRE_ICO_TIER];
            preICOTier.startTime = _start;
            preICOTier.endTime = _end;
        }
    }

    function changeICODates(uint8 _tierId, uint256 _start, uint256 _end) public onlyOwner {
        if (_start != 0 && _start < _end && _tierId < tiers.length) {
            Tier storage icoTier = tiers[_tierId];
            icoTier.startTime = _start;
            icoTier.endTime = _end;
            if (_tierId == ICO_TIER_FIRST) {
                startTime = _start;
            } else if (_tierId == ICO_TIER_LAST) {
                endTime = _end;
            }
        }
    }

    function burnUnsoldTokens() public onlyOwner {
        if (block.timestamp >= tiers[PRE_ICO_TIER].endTime && preICOStats.burned == false) {
            token.burnTokens(tiers[PRE_ICO_TIER].maxAmount.sub(preICOStats.soldTokens));
            preICOStats.burned = true;
        }
        if (block.timestamp >= endTime && maxTokenSupply > soldTokens) {
            token.burnTokens(maxTokenSupply.sub(soldTokens));
            maxTokenSupply = soldTokens;
        }
    }

    function transferEthers() public onlyOwner {
        super.transferEthersInternal();
    }

    function transferCompensationEthers() public {
        if (msg.sender == compensationAddress) {
            super.transferEthersInternal();
        }
    }

    function getActiveTier() public view returns (uint8) {
        for (uint8 i = 0; i < tiers.length; i++) {
            if (block.timestamp >= tiers[i].startTime && block.timestamp <= tiers[i].endTime) {
                return i;
            }
        }

        return uint8(tiers.length);
    }

    function calculateTokensAmount(uint256 _value, bool _isEther) public view returns (
        uint256 tokenAmount,
        uint256 currencyAmount
    ) {
        if (_value == 0) {
            return (0, 0);
        }
        uint8 activeTier = getActiveTier();

        if (activeTier == tiers.length) {
            if (endTime < block.timestamp) {
                return (0, 0);
            }
            if (startTime > block.timestamp) {
                activeTier = PRE_ICO_TIER;
            }
        }

        if (_isEther) {
            currencyAmount = _value.mul(etherPriceInUSD);
            tokenAmount = currencyAmount.div(tiers[activeTier].price);
            if (currencyAmount < minPurchase.mul(1 ether)) {
                return (0, 0);
            }
            currencyAmount = currencyAmount.div(1 ether);
        } else {
            if (_value < minPurchase) {
                return (0, 0);
            }
            currencyAmount = uint256(1 ether).mul(_value).div(etherPriceInUSD);
            tokenAmount = _value.mul(uint256(10) ** DECIMALS).div(tiers[activeTier].price);
        }
    }

    function calculateEthersAmount(uint256 _amount) public view returns (uint256 ethersAmount) {
        uint8 activeTier = getActiveTier();

        if (activeTier == tiers.length) {
            if (endTime < block.timestamp) {
                return 0;
            }
            if (startTime > block.timestamp) {
                activeTier = PRE_ICO_TIER;
            }
        }

        if (_amount == 0 || _amount.mul(tiers[activeTier].price) < minPurchase) {
            return 0;
        }

        ethersAmount = _amount.mul(tiers[activeTier].price).div(etherPriceInUSD);
    }

    function getMinEthersInvestment() public view returns (uint256) {
        return uint256(1 ether).mul(minPurchase).div(etherPriceInUSD);
    }

    function getStats() public view returns (
        uint256 start,
        uint256 end,
        uint256 sold,
        uint256 totalSoldTokens,
        uint256 maxSupply,
        uint256 min,
        uint256 soft,
        uint256 hard,
        uint256 tokensPerEth,
        uint256[16] tiersData
    ) {
        start = startTime;
        end = endTime;
        sold = soldTokens;
        totalSoldTokens = soldTokens.add(preICOStats.soldTokens);
        if (address(privateSale) != address(0)) {
            totalSoldTokens = totalSoldTokens.add(privateSale.soldTokens());
        }
        maxSupply = maxTokenSupply;
        min = minPurchase;
        soft = softCap;
        hard = hardCap;
        uint256 usd;
        (tokensPerEth, usd) = calculateTokensAmount(1 ether, true);
        uint256 j = 0;
        for (uint256 i = 0; i < tiers.length; i++) {
            tiersData[j++] = uint256(tiers[i].maxAmount);
            tiersData[j++] = uint256(tiers[i].price);
            tiersData[j++] = uint256(tiers[i].startTime);
            tiersData[j++] = uint256(tiers[i].endTime);
        }
    }

    function isRefundPossible() public view returns (bool) {
        if (getActiveTier() != tiers.length || block.timestamp < startTime || collectedUSD >= softCap) {
            return false;
        }
        return true;
    }

    function refund() public returns (bool) {
        if (!isRefundPossible() || etherBalances[msg.sender] == 0) {
            return false;
        }

        msg.sender.transfer(etherBalances[msg.sender]);
        uint256 burnedAmount = token.burnInvestorTokens(msg.sender);
        if (burnedAmount == 0) {
            return false;
        }

        Refund(msg.sender, etherBalances[msg.sender], burnedAmount);
        etherBalances[msg.sender] = 0;

        return true;
    }

    function mintPreICO(address _address, uint256 _tokenAmount, uint256 _ethAmount, uint256 _usdAmount) internal returns (uint256) {
        uint256 mintedAmount = token.mint(_address, _tokenAmount);

        require(mintedAmount == _tokenAmount);

        preICOStats.soldTokens = preICOStats.soldTokens.add(_tokenAmount);
        preICOStats.collectedEthers = preICOStats.collectedEthers.add(_ethAmount);
        preICOStats.collectedUSD = preICOStats.collectedUSD.add(_usdAmount);

        require(tiers[PRE_ICO_TIER].maxAmount >= preICOStats.soldTokens);

        if (preICOStats.collectedUSD <= compensationAmount) {
            compensatedAmount = compensatedAmount.add(_usdAmount);
            compensationAddress.transfer(this.balance);
        }

        return _tokenAmount;
    }

    function buy(address _address, uint256 _value) internal returns (bool) {
        if (_value == 0 || _address == address(0)) {
            return false;
        }
        uint8 activeTier = getActiveTier();
        if (activeTier == tiers.length) {
            return false;
        }

        uint256 tokenAmount;
        uint256 usdAmount;
        uint256 mintedAmount;

        (tokenAmount, usdAmount) = calculateTokensAmount(_value, true);

        if (usdAmount >= lockupThreshold) {
            lockupContract.logLargeContribution(_address, tokenAmount);
        }

        if (activeTier == PRE_ICO_TIER) {
            mintedAmount = mintPreICO(_address, tokenAmount, _value, usdAmount);
            require(usdAmount > 0 && mintedAmount > 0);
        } else {
            mintedAmount = mintInternal(_address, tokenAmount);

            collectedEthers = collectedEthers.add(_value);
            collectedUSD = collectedUSD.add(usdAmount);

            require(hardCap >= collectedUSD && usdAmount > 0 && mintedAmount > 0);

            etherBalances[_address] = etherBalances[_address].add(_value);
        }

        Contribution(_address, _value, tokenAmount);

        return true;
    }

}

contract PrivateSale is SellableToken {

    uint256 public price = 4000;//0.04 cents * 10 ^ 5

    function PrivateSale(
        address _token,
        address _etherHolder,
        address _compensationAddress,
        uint256 _startTime,
        uint256 _endTime,
        uint256 _etherPriceInUSD, // if price 709.38000 the  value has to be 70938000
        uint256 _maxTokenSupply
    ) public SellableToken(
        _token,
        _etherHolder,
        _compensationAddress,
        _etherPriceInUSD,
        _maxTokenSupply
    ) {
        require(_startTime > 0 && _endTime > _startTime);
        startTime = _startTime;
        endTime = _endTime;
    }

    function burnUnsoldTokens() public onlyOwner {
        if (block.timestamp >= endTime && maxTokenSupply > soldTokens) {
            token.burnTokens(maxTokenSupply.sub(soldTokens));
            maxTokenSupply = soldTokens;
        }
    }

    function calculateTokensAmount(uint256 _value) public view returns (uint256 tokenAmount, uint256 usdAmount) {
        if (_value == 0) {
            return (0, 0);
        }

        usdAmount = _value.mul(etherPriceInUSD);
        tokenAmount = usdAmount.div(price);

        if (usdAmount < minPurchase.mul(1 ether)) {
            return (0, 0);
        }
        usdAmount = usdAmount.div(1 ether);
    }

    function calculateEthersAmount(uint256 _amount) public view returns (uint256 ethersAmount) {
        if (_amount == 0 || _amount.mul(price) < minPurchase) {
            return 0;
        }

        ethersAmount = _amount.mul(price).div(etherPriceInUSD);
    }

    function getMinEthersInvestment() public view returns (uint256) {
        return uint256(1 ether).mul(minPurchase).div(etherPriceInUSD);
    }

    function getStats() public view returns (
        uint256 start,
        uint256 end,
        uint256 sold,
        uint256 maxSupply,
        uint256 min,
        uint256 soft,
        uint256 hard,
        uint256 priceAmount,
        uint256 tokensPerEth
    ) {
        start = startTime;
        end = endTime;
        sold = soldTokens;
        maxSupply = maxTokenSupply;
        min = minPurchase;
        soft = softCap;
        hard = hardCap;
        priceAmount = price;
        uint256 usd;
        (tokensPerEth, usd) = calculateTokensAmount(1 ether);
    }

    function buy(address _address, uint256 _value) internal returns (bool) {
        if (_value == 0) {
            return false;
        }
        require(_address != address(0) && withinPeriod());

        uint256 tokenAmount;
        uint256 usdAmount;

        (tokenAmount, usdAmount) = calculateTokensAmount(_value);

        uint256 mintedAmount = token.mint(_address, tokenAmount);
        soldTokens = soldTokens.add(tokenAmount);
        require(mintedAmount == tokenAmount && maxTokenSupply >= soldTokens && usdAmount > 0 && mintedAmount > 0);

        collectedEthers = collectedEthers.add(_value);
        collectedUSD = collectedUSD.add(usdAmount);

        Contribution(_address, _value, tokenAmount);

        etherHolder.transfer(this.balance);
        return true;
    }

}

/*
This contract manages the minters and the modifier to allow mint to happen only if called by minters
This contract contains basic minting functionality though
*/
contract MintingERC20 is ElyERC20 {

    using SafeMath for uint256;

    //Variables
    mapping (address => bool) public minters;

    uint256 public maxSupply;

    //Modifiers
    modifier onlyMinters () {
        require(true == minters[msg.sender]);
        _;
    }

    function MintingERC20(
        uint256 _initialSupply,
        uint256 _maxSupply,
        string _tokenName,
        uint8 _decimals,
        string _symbol,
        bool _transferAllSupplyToOwner,
        bool _locked
    )
        public ElyERC20(_initialSupply, _tokenName, _decimals, _symbol, _transferAllSupplyToOwner, _locked)
    {
        standard = "MintingERC20 0.1";
        minters[msg.sender] = true;
        maxSupply = _maxSupply;
    }

    function addMinter(address _newMinter) public onlyOwner {
        minters[_newMinter] = true;
    }

    function removeMinter(address _minter) public onlyOwner {
        minters[_minter] = false;
    }

    function mint(address _addr, uint256 _amount) public onlyMinters returns (uint256) {
        if (true == locked) {
            return uint256(0);
        }

        if (_amount == uint256(0)) {
            return uint256(0);
        }

        if (totalSupply_.add(_amount) > maxSupply) {
            return uint256(0);
        }

        totalSupply_ = totalSupply_.add(_amount);
        balances[_addr] = balances[_addr].add(_amount);
        Transfer(address(0), _addr, _amount);

        return _amount;
    }

}

contract ElyToken is MintingERC20 {

    SellableToken public ico;
    SellableToken public privateSale;
    LockupContract public lockupContract;

    address public bountyAddress;

    bool public transferFrozen = true;

    modifier onlySellable() {
        require(msg.sender == address(ico) || msg.sender == address(privateSale));
        _;
    }

    function ElyToken(
        address _bountyAddress,
        bool _locked
    )
        public MintingERC20(0, maxSupply, "Elycoin", 18, "ELY", false, _locked)
    {
        require(_bountyAddress != address(0));
        bountyAddress = _bountyAddress;
        standard = "ELY 0.1";
        maxSupply = uint(1000000000).mul(uint(10) ** decimals);
        require(uint(10000000).mul(uint(10) ** decimals) == super.mint(bountyAddress, uint(10000000).mul(uint(10) ** decimals)));
    }

    function setICO(address _ico) public onlyOwner {
        require(_ico != address(0));
        ico = SellableToken(_ico);
    }

    function setPrivateSale(address _privateSale) public onlyOwner {
        require(_privateSale != address(0));
        privateSale = SellableToken(_privateSale);
    }

    function setLockupContract(address _lockupContract) public onlyOwner {
        require(_lockupContract != address(0));
        lockupContract = LockupContract(_lockupContract);
    }

    function setLocked(bool _locked) public onlyOwner {
        locked = _locked;
    }

    function freezing(bool _transferFrozen) public onlyOwner {
        if (address(ico) != address(0) && !ico.isActive() && block.timestamp >= ico.startTime()) {
            transferFrozen = _transferFrozen;
        }
    }

    function mint(address _addr, uint256 _amount) public onlyMinters returns (uint256) {
        if (msg.sender == owner) {
            require(address(ico) != address(0));
            if (!ico.isActive()) {
                return super.mint(_addr, _amount);
            }
            return uint256(0);
        }
        return super.mint(_addr, _amount);
    }

    function transferAllowed(address _address, uint256 _amount) public view returns (bool) {
        return !transferFrozen && lockupContract.isTransferAllowed(_address, _amount);
    }

    function transfer(address _to, uint _value) public returns (bool) {
        require(msg.sender == bountyAddress || transferAllowed(msg.sender, _value));
        if (msg.sender == bountyAddress) {
            lockupContract.log(_to, _value);
        }
        return super.transfer(_to, _value);
    }

    function transferFrom(address _from, address _to, uint _value) public returns (bool success) {
        require(_from == bountyAddress || transferAllowed(_from, _value));
        if (_from == bountyAddress) {
            lockupContract.log(_to, _value);
        }
        return super.transferFrom(_from, _to, _value);
    }

    function burnTokens(uint256 _amount) public onlySellable {
        if (totalSupply_.add(_amount) > maxSupply) {
            Transfer(address(this), address(0), maxSupply.sub(totalSupply_));
            totalSupply_ = maxSupply;
        } else {
            totalSupply_ = totalSupply_.add(_amount);
            Transfer(address(this), address(0), _amount);
        }
    }

    function burnInvestorTokens(address _address) public constant onlySellable returns (uint256 amount) {
        amount = balances[_address];
        balances[_address] = 0;
        Transfer(_address, address(0), amount);
    }

}