contract HYPEtoken is tokenERC20
{

    using SafeMath for uint256;
    
    
    	/*************************************/
        /*  User whitelisting functionality  */
        /*************************************/
        bool public whitelistingStatus = false;
        mapping (address => bool) public whitelisted;

        
        /**
         * Change whitelisting status on or off
         *
         * When whitelisting is true, then crowdsale will only accept investors who are whitelisted.
         */
        function changeWhitelistingStatus() onlyOwner public{
            if (whitelistingStatus == false){
			    whitelistingStatus = true;
                whitelisted[owner]= true;
            }
            else{
                whitelistingStatus = false;    
            }
		}
		
		/**
         * Whitelist any user address - only Owner can do this
         *
         * It will add user address in whitelisted mapping
         */
        function whitelistUser(address userAddress) onlyOwner public{
            require(whitelistingStatus == true);
            require(userAddress != address(0));
            whitelisted[userAddress] = true;
		}    
		



		/// @notice `freeze? Prevent | Allow` `target` from sending & receiving tokens
		/// @param target Address to be frozen
		/// @param freeze either to freeze it or not
		function freezeAccount(address target, bool freeze) onlyOwner public {
				frozenAccount[target] = freeze;
			emit  FrozenFunds(target, freeze);
		}
        

        
        //Just in case, owner wants to transfer Tokens from contract to owner address
        function manualWithdrawToken(uint256 _amount) onlyOwner public {
      		uint256 tokenAmount = _amount.mul(100);
            _transfer(address(this), msg.sender, tokenAmount);
        }
          
        //Just in case, owner wants to transfer Ether from contract to owner address
        function manualWithdrawEther()onlyOwner public{
			uint256 amount=address(this).balance;
			owner.transfer(amount);
		}
		

        //To air drop
        function airDrop(address[] memory recipients,uint[] memory tokenAmount) public onlyOwner returns (bool) {
            uint reciversLength  = recipients.length;
            require(reciversLength <= 150);
            for(uint i = 0; i < reciversLength; i++)
            {
                  //This will loop through all the recipients and send them the specified tokens
                  _transfer(owner, recipients[i], tokenAmount[i]);
            }
            return true;
        }
        
        
        

    uint public meltHoldSeconds = 172800;  // 172800 seconds = 48 Hr. user can withdraw only after this period




    //Event for eexternal token deposit and withdraw

    event tokenDepositEv(address token, address user, uint amount, uint balance);
    event tokenWithdrawEv(address token, address user, uint amount, uint balance);

    function setWithdrawWaitingPeriod(uint valueInSeconds) onlyOwner public returns (bool)
    {
        meltHoldSeconds = valueInSeconds;
        return true;
    }

    function newTokenTypeData(address token,bytes32 _tokenName, bytes8 _tokenSymbol, uint _decimalCount, uint _minFreezingValue, uint _rateFactor, uint _perDayFreezeRate) onlyOwner public returns (bool)
    {
        tokenTypeData memory temp;

        temp.tokenName=_tokenName;
        temp.tokenSymbol=_tokenSymbol;
        temp.decimalCount=_decimalCount;
        temp.minFreezingValue=_minFreezingValue;
        temp.rateFactor=_rateFactor;      
        temp.perDayFreezeRate=_perDayFreezeRate;   
        temp.freezingAllowed=true;  
        tokenTypeDatas[token] = temp;
        return true;
    }

    function freezingOnOffForTokenType(address token) onlyOwner public returns (bool)
    {
        if (tokenTypeDatas[token].freezingAllowed == false)
        {
            tokenTypeDatas[token].freezingAllowed = true;
        }
        else
        {
            tokenTypeDatas[token].freezingAllowed = false;    
        } 
        return true;     
    }

    function setMinFreezingValue(address token, uint _minFreezingValue) onlyOwner public returns (bool)
    {
        tokenTypeDatas[token].minFreezingValue = _minFreezingValue;
        return true;
    }

    function setRateFactor(address token, uint _rateFactor) onlyOwner public returns (bool)
    {
        tokenTypeDatas[token].rateFactor = _rateFactor;
        return true;
    }

    function setPerDayFreezeRate(address token, uint _perDayFreezeRate) onlyOwner public returns (bool)
    {
        tokenTypeDatas[token].perDayFreezeRate = _perDayFreezeRate;
        return true;
    }

   
    

    //To deposit token
    function tokenDeposit(address token, uint amount) public 
    {
        //remember to call Token(address).approve(this, amount) or this contract will not be able to do the transfer on your behalf.
        require(token!=address(0),"Address(0) found, can't continue");
        require(ERC20Essential(token).transferFrom(msg.sender, address(this), amount),"ERC20 'transferFrom' call failed");
        tokens[token][msg.sender].totalValue = tokens[token][msg.sender].totalValue.add(amount);
        emit tokenDepositEv(token, msg.sender, amount, tokens[token][msg.sender].totalValue);
    }

    //To withdraw token
    function tokenWithdraw(address token, uint amount) public
    {
        require(!safeGuard,"System Paused By Admin");
        require(token != address(this));
        require(token!=address(0),"Address(0) found, can't continue");
        if(now.sub(meltHoldSeconds) > tokens[token][msg.sender].freezeDate)
        {
           tokens[token][msg.sender].meltValue = 0; 
        }
        require(tokens[token][msg.sender].totalValue.sub(tokens[token][msg.sender].freezeValue.add(tokens[token][msg.sender].meltValue)) >= amount,"Required amount is not free to withdraw");       
        tokens[token][msg.sender].totalValue = tokens[token][msg.sender].totalValue.sub(amount);
        ERC20Essential(token).transfer(msg.sender, amount);
        emit tokenWithdrawEv(token, msg.sender, amount, tokens[token][msg.sender].totalValue);
    }

    event releaseMyHypeEv(address token, uint amount);
    //releasing after minumum waiting period to withdraw HYPE 
    function releaseMyHype(address token) public returns (bool)
    {
        require(!safeGuard,"System Paused By Admin");
        require(token!=address(0),"Address(0) found, can't continue");
        require(token == address(this),"Only pissible for HYPE ");
        require(now.sub(meltHoldSeconds) > tokens[token][msg.sender].freezeDate,"wait period is not over");
        uint amount = tokens[token][msg.sender].meltValue;
        balanceOf[msg.sender] = balanceOf[msg.sender].add(amount);
        tokens[token][msg.sender].totalValue = balanceOf[msg.sender].add(tokens[token][msg.sender].freezeValue );
        tokens[token][msg.sender].meltValue = 0; 
        emit releaseMyHypeEv(token, amount);
        return true;
    }

    event tokenBalanceFreezeEv(address token, uint amount, uint earning);


    function tokenBalanceFreeze(address token, uint amount)   public returns (bool)
    {
        require(!safeGuard,"System Paused By Admin");
        require(tokenTypeDatas[token].freezingAllowed,"token type not allowed to freeze");
        require(token!=address(0),"Address(0) found, can't continue");
        address callingUser = msg.sender;
        require(msg.sender != address(0),"Address(0) found, can't continue");

        require(amount <=  tokens[token][callingUser].totalValue.sub(tokens[token][callingUser].freezeValue.add(tokens[token][callingUser].meltValue)) && amount >= tokenTypeDatas[token].minFreezingValue, "less than required or less balance");
        
        //before adding more freezing amount calculating earning on existing freeze amount and updating same in user ether balance
        uint freezeValue = tokens[token][callingUser].freezeValue;
        uint earnedValue;
        if (freezeValue > 0)
        {
            earnedValue = getEarning(token,callingUser,freezeValue);
            require(mintInternal(earnedValue),"minting failed");
            tokens[address(this)][callingUser].meltValue = tokens[address(this)][callingUser].meltValue.add(earnedValue);
        }

        tokens[token][callingUser].freezeValue = tokens[token][callingUser].freezeValue.add(amount);
        if (token==address(this))
        {
            balanceOf[callingUser] = balanceOf[callingUser].sub(amount);
        }
        tokens[token][callingUser].freezeDate = now;

        emit tokenBalanceFreezeEv(token,amount,earnedValue);
        return true;
    }

    function getEarning(address token,address user,uint amount) internal view returns(uint256)
    {
        uint effectiveAmount = calculatePercentage(amount,tokenTypeDatas[token].rateFactor);
        uint interestAmount = calculatePercentage(effectiveAmount,tokenTypeDatas[token].perDayFreezeRate);
        uint secondsPassed = (now - tokens[token][user].freezeDate);
        uint daysPassed=0;
        if (secondsPassed >= 86400)  // if less than one day earning will be zero
        {
           daysPassed = secondsPassed.div(86400); 
        }
        return daysPassed.mul(interestAmount);
    }


    event tokenBalanceMeltEv(address token, uint amount, uint earning);


    function tokenBalanceMelt(address token, uint amount)   public returns (bool)
    {
        require(!safeGuard,"System Paused By Admin");
        require(token!=address(0),"Address(0) found, can't continue");
        address callingUser = msg.sender;
        require(msg.sender != address(0),"Address(0) found, can't continue");
        require(amount <=  tokens[token][callingUser].freezeValue && amount > 0, "less than required or less balance");
        
        //before melting amount calculating earning on existing freeze amount and updating same in user ether balance
        uint freezeValue = tokens[token][callingUser].freezeValue;
        uint earnedValue = getEarning(token,callingUser,freezeValue);
        require(mintInternal(earnedValue),"minting failed");
        tokens[address(this)][callingUser].meltValue = tokens[address(this)][callingUser].meltValue.add(earnedValue);       
        
        tokens[token][callingUser].freezeValue = tokens[token][callingUser].freezeValue.sub(amount);
        if (token==address(this))
        {
            tokens[token][callingUser].meltValue = tokens[token][callingUser].meltValue.add(amount);
        }

        tokens[token][callingUser].freezeDate = now;
        emit tokenBalanceMeltEv(token,amount,earnedValue);
        return true;
    }

    function viewMyReward(address token) public view returns(uint freezedValue, uint rewardValue)
    {
        address callingUser = msg.sender;
        uint freezeValue = tokens[token][callingUser].freezeValue;
        uint earnedValue = getEarning(token,callingUser,freezeValue);
        return (freezeValue,earnedValue);
    }

}
//Main contract starts here
function manualWithdrawToken(uint256 _amount) onlyOwner public {
uint256 tokenAmount = _amount.mul(100);
    _transfer(address(this), msg.sender, tokenAmount);
}

//Just in case, owner wants to transfer Tokens from contract to owner address