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;

    //clans
    mapping (address => address) public joinedClans;
    mapping (address => address) public userClans;
    mapping (address => bool) public clanRegistry;
    mapping (address => uint256) public inviteeCount;

    address systemAddress;

    EmcoTokenInterface private oldContract;

    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) private userRefCodes;
    mapping(bytes32 => address) private refCodeOwners;
    mapping(address => address) private refs;

    event Mine(address indexed beneficiary, uint value);

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

    event Migrate(address indexed user, uint256 amount);

    event TransferComment(address indexed to, uint256 amount, bytes comment);

    event SetReferral(address whoSet, address indexed referrer);

    constructor(address emcoAddress) public {
        systemAddress = msg.sender;
        oldContract = EmcoTokenInterface(emcoAddress);
    }

    function migrate(uint _amount) public {
        require(oldContract.transferFrom(msg.sender, this, _amount));
        totalSupply_ = totalSupply_.add(_amount);
        balances[msg.sender] = balances[msg.sender].add(_amount);
        emit Migrate(msg.sender, _amount);
        emit Transfer(address(0), msg.sender, _amount);
    }

    function setReferralCode(bytes32 _code) public returns (bytes32) {
        require(_code != "");
        require(refCodeOwners[_code] == address(0));
        require(oldContract.referralCodeOwners(_code) == address(0));
        require(userReferralCodes(msg.sender) == "");
        userRefCodes[msg.sender] = _code;
        refCodeOwners[_code] = msg.sender;
        return _code;
    }

    function referralCodeOwners(bytes32 _code) public view returns (address owner) {
        address refCodeOwner = refCodeOwners[_code];
        if(refCodeOwner == address(0)) {
            return oldContract.referralCodeOwners(_code);
        } else {
            return refCodeOwner;
        }
    }

    function userReferralCodes(address _address) public view returns (bytes32) {
        bytes32 code = oldContract.userReferralCodes(_address);
        if(code != "") {
            return code;
        } else {
            return userRefCodes[_address];
        }
    }

    function referrals(address _address) public view returns (address) {
        address refInOldContract = oldContract.referrals(_address);
        if(refInOldContract != address(0)) {
            return refInOldContract;
        } else {
            return refs[_address];
        }
    }

    function setReferral(bytes32 _code) public {
        require(refCodeOwners[_code] != address(0));
        require(referrals(msg.sender) == address(0));
        require(oldContract.referrals(msg.sender) == address(0));
        address referrer = refCodeOwners[_code];
        require(referrer != msg.sender, "Can not invite yourself");
        refs[msg.sender] = referrer;
        inviteeCount[referrer] = inviteeCount[referrer].add(1);
        emit SetReferral(msg.sender, referrer);
    }

    function transferWithComment(address _to, uint256 _value, bytes _comment) public returns (bool) {
        emit TransferComment(_to, _value, _comment);
        return transfer(_to, _value);
    }

	/**
	* Create a clan
	*/
    function createClan(uint256 epochLength) public returns (address clanAddress) {
        require(epochLength >= 175200); //min epoch length about a month
		//check if there is a clan already. If so, throw
        require(userClans[msg.sender] == address(0x0));
		//check if user has at least 10 invitees
        require(inviteeCount[msg.sender] >= 10);

		//instantiate new instance of contract
        Clan clan = new Clan(msg.sender, this, epochLength);

		//register clan to mapping
        userClans[msg.sender] = clan;
        clanRegistry[clan] = true;
        return clan;
    }

	function joinClan(address clanAddress) public {
		//ensure than such clan exists
		require(clanRegistry[clanAddress]);
		require(joinedClans[msg.sender] == address(0x0));

		//join user to clan
		Clan clan = Clan(clanAddress);
		clan.join(msg.sender);

		//set clan to user
		joinedClans[msg.sender] = clanAddress;
	}

	function leaveClan() public {
		address clanAddress = joinedClans[msg.sender];
		require(clanAddress != address(0x0));

		Clan clan = Clan(clanAddress);
		clan.leaveClan(msg.sender);

		//unregister user from clan
		joinedClans[msg.sender] = address(0x0);
	}

	/**
	* Update invitees count
	*/
	function updateInviteesCount(address invitee, uint256 count) public onlyOwner {
		inviteeCount[invitee] = count;
	}

	/**
	* @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 mining tokens");

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

		//updating mining limits
		miningTotalDeposited = 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);

		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;

		//assign to clan address
		mintClanReward(mintedAmount.mul(5).div(1000));
	}

	function mintClanReward(uint reward) private {
		//check if user has a clan
		address clanAddress = joinedClans[msg.sender];
		if(clanAddress != address(0x0)) {
			// check if this clan is registered
			require(clanRegistry[clanAddress], "clan is not registered");

			// send appropriate amount of EMCO to clan address
			balances[clanAddress] = balances[clanAddress].add(reward);
			Clan clan = Clan(clanAddress);
			clan.replenish(reward);
			totalSupply_ = totalSupply_.add(reward);
		}
	}

	/**
	* @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 2nd version
 * @dev Emco token implementation
 */
function updateInviteesCount(address invitee, uint256 count) public onlyOwner {
	inviteeCount[invitee] = count;
}

/**
 	* Update invitees count
 	*/