contract EmcoToken is StandardToken, Ownable {

	string public constant name = "EmcoToken";
	string public constant symbol = "EMCO";
	uint8 public constant decimals = 18;

	uint public constant INITIAL_SUPPLY = 1500000 * (10 ** uint(decimals));
	uint public constant MAX_SUPPLY = 36000000 * (10 ** uint(decimals));

	mapping (address => uint) public miningBalances;
	mapping (address => uint) public lastMiningBalanceUpdateTime;

	address systemAddress;

	uint public constant DAY_MINING_DEPOSIT_LIMIT = 360000 * (10 ** uint(decimals));
	uint public constant TOTAL_MINING_DEPOSIT_LIMIT = 3600000 * (10 ** uint(decimals));
	uint currentDay;
	uint currentDayDeposited;
	uint public miningTotalDeposited;

	mapping(address => bytes32) public userReferralCodes;
	mapping(bytes32 => address) public referralCodeOwners;
	mapping(address => address) public referrals;

	event Mine(address indexed beneficiary, uint value);

	event MiningBalanceUpdated(address indexed owner, uint amount, bool isDeposit);

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

	function setReferralCode(bytes32 _code) public returns (bytes32) {
		require(_code != "", "Ref code should not be empty");
		require(referralCodeOwners[_code] == address(0), "This referral code is already used");
		require(userReferralCodes[msg.sender] == "", "Referal code is already set");
		userReferralCodes[msg.sender] = _code;
		referralCodeOwners[_code] = msg.sender;
		return userReferralCodes[msg.sender];
	}

	function setReferral(bytes32 _code) public {
		require(referralCodeOwners[_code] != address(0), "Invalid referral code");
		require(referrals[msg.sender] == address(0), "You already have a referrer");
		address referrer = referralCodeOwners[_code];
		require(referrer != msg.sender, "Can not invite yourself");
		referrals[msg.sender] = referrer;
	}

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

	/**
	* @dev Gets the mining balance if caller.
	* @param _owner The address to query the balance of.
	* @return An uint256 representing the amount of tokens of caller's mining balance
	*/
	function miningBalanceOf(address _owner) public view returns (uint balance) {
		return miningBalances[_owner];
	}

	/**
	* @dev Moves specified amount of tokens from main balance to mining balance 
	* @param _amount An uint256 representing the amount of tokens to transfer to main balance
	*/
	function depositToMiningBalance(uint _amount) public {
		require(balances[msg.sender] >= _amount, "not enough tokens");
		require(getCurrentDayDeposited().add(_amount) <= DAY_MINING_DEPOSIT_LIMIT,
			"Day mining deposit exceeded");
		require(miningTotalDeposited.add(_amount) <= TOTAL_MINING_DEPOSIT_LIMIT,
			"Total mining deposit exceeded");

		balances[msg.sender] = balances[msg.sender].sub(_amount);
		miningBalances[msg.sender] = miningBalances[msg.sender].add(_amount);
		miningTotalDeposited = miningTotalDeposited.add(_amount);
		updateCurrentDayDeposited(_amount);
		lastMiningBalanceUpdateTime[msg.sender] = now;
		emit MiningBalanceUpdated(msg.sender, _amount, true);
	}

	/**
	* @dev Moves specified amount of tokens from mining balance to main balance
	* @param _amount An uint256 representing the amount of tokens to transfer to mining balance
	*/
	function withdrawFromMiningBalance(uint _amount) public {
		require(miningBalances[msg.sender] >= _amount, "not enough tokens on mining balance");

		miningBalances[msg.sender] = miningBalances[msg.sender].sub(_amount);
		balances[msg.sender] = balances[msg.sender].add(_amount);

		//updating mining limits
		miningTotalDeposited.sub(_amount);
		lastMiningBalanceUpdateTime[msg.sender] = now;
		emit MiningBalanceUpdated(msg.sender, _amount, false);
	}

	/**
	* @dev Mine tokens. For every 24h for each user�s token on mining balance, 
	* 1% is burnt on mining balance and Reward % is minted to the main balance. 15% fee of difference 
	* between minted coins and burnt coins goes to system address.
	*/ 
	function mine() public {
		require(totalSupply_ < MAX_SUPPLY, "mining is over");
		uint reward = getReward(totalSupply_);
		uint daysForReward = getDaysForReward();

		uint mintedAmount = miningBalances[msg.sender].mul(reward.sub(1000000000))
										.mul(daysForReward).div(100000000000);
		require(mintedAmount != 0, "mining will not produce any reward");

		uint amountToBurn = miningBalances[msg.sender].mul(daysForReward).div(100);

		//check exceeding max number of tokens
		if(totalSupply_.add(mintedAmount) > MAX_SUPPLY) {
			uint availableToMint = MAX_SUPPLY.sub(totalSupply_);
			amountToBurn = availableToMint.div(mintedAmount).mul(amountToBurn);
			mintedAmount = availableToMint;
		}

		totalSupply_ = totalSupply_.add(mintedAmount);

		miningBalances[msg.sender] = miningBalances[msg.sender].sub(amountToBurn);
		balances[msg.sender] = balances[msg.sender].add(amountToBurn);

		uint userReward;
		uint referrerReward = 0;
		address referrer = referrals[msg.sender];
		
		if(referrer == address(0)) {
			userReward = mintedAmount.mul(85).div(100);
		} else {
			userReward = mintedAmount.mul(86).div(100);
			referrerReward = mintedAmount.div(100);
			balances[referrer] = balances[referrer].add(referrerReward);
			emit Mine(referrer, referrerReward);
			emit Transfer(address(0), referrer, referrerReward);
		}
		balances[msg.sender] = balances[msg.sender].add(userReward);

		emit Mine(msg.sender, userReward);
		emit Transfer(address(0), msg.sender, userReward);

		//update limits
		miningTotalDeposited = miningTotalDeposited.sub(amountToBurn);
		emit MiningBalanceUpdated(msg.sender, amountToBurn, false);

		//set system fee
		uint systemFee = mintedAmount.sub(userReward).sub(referrerReward);
		balances[systemAddress] = balances[systemAddress].add(systemFee);

		emit Mine(systemAddress, systemFee);
		emit Transfer(address(0), systemAddress, systemFee);

		lastMiningBalanceUpdateTime[msg.sender] = now;
	}

	/**
	* @dev Set system address
	* @param _systemAddress An address to set
	*/
	function setSystemAddress(address _systemAddress) public onlyOwner {
		systemAddress = _systemAddress;
	}

	/**
	* @dev Get sum of deposits to mining accounts for current day
	*/
	function getCurrentDayDeposited() public view returns (uint) {
		if(now / 1 days == currentDay) {
			return currentDayDeposited;
		} else {
			return 0;
		}
	}

	/**
	* @dev Get number of days for reward on mining. Maximum 100 days.
	* @return An uint256 representing number of days user will get reward for.
	*/
	function getDaysForReward() public view returns (uint rewardDaysNum){
		if(lastMiningBalanceUpdateTime[msg.sender] == 0) {
			return 0;
		} else {
			uint value = (now - lastMiningBalanceUpdateTime[msg.sender]) / (1 days);
			if(value > 100) {
				return 100;
			} else {
				return value;
			}
		}
	}

	/**
	* @dev Calculate current mining reward based on total supply of tokens
	* @return An uint256 representing reward in percents multiplied by 1000000000
	*/
	function getReward(uint _totalSupply) public pure returns (uint rewardPercent){
		uint rewardFactor = 1000000 * (10 ** uint256(decimals));
		uint decreaseFactor = 41666666;

		if(_totalSupply < 23 * rewardFactor) {
			return 2000000000 - (decreaseFactor.mul(_totalSupply.div(rewardFactor)));
		}

		if(_totalSupply < MAX_SUPPLY) {
			return 1041666666;
		} else {
			return 1000000000;
		} 
	}

	function updateCurrentDayDeposited(uint _addedTokens) private {
		if(now / 1 days == currentDay) {
			currentDayDeposited = currentDayDeposited.add(_addedTokens);
		} else {
			currentDay = now / 1 days;
			currentDayDeposited = _addedTokens;
		}
	}

}
/**
 * @title Emco token
 * @dev Emco token implementation
 */
function getDaysForReward() public view returns (uint rewardDaysNum){
	if(lastMiningBalanceUpdateTime[msg.sender] == 0) {
		return 0;
	} else {
		uint value = (now - lastMiningBalanceUpdateTime[msg.sender]) / (1 days);
		if(value > 100) {
			return 100;
		} else {
			return value;
		}
	}
}

/**
 	* @dev Get number of days for reward on mining. Maximum 100 days.
 	* @return An uint256 representing number of days user will get reward for.
 	*/