contract TTGOracle is ERC721, usingOraclize, Functional, Owned
{
	using SafeMathLib for uint32;
	using SafeMathLib for uint256;
	uint256 public feeLottery;
	
	enum Status {
		NOTFOUND,		//0 game not created
		PLAYING,		//1 buying tickets
		PROCESSING,		//2 waiting for result
		PAYING,	 		//3 redeeming
		CANCELING		//4 canceling the game
	}
	
	struct Game {
		string  nameLottery;
		uint32  countCombinations;
		uint32  gameID;
		uint32  teamAID;
		uint32  teamBID;		
		uint32  dateStopBuy;
		uint32  minStake;				// per finney = 0.001E
		uint32  winCombination;
		uint256 betsSumIn;				// amount bets
		uint256 feeValue;				// amount fee
		Status status;					// status of game
		bool isFreezing;
	}
	Game[] private game;
	ITTGCoin private ttgCoin;
	IItemToken private itemToken;
	uint32 private userAirDropRate = 1000;
	uint32 private ownerAirDropRate = 100;

	struct Stake {
		uint256 sum;		// amount bets
		uint32 count;		// count bets 
	}
	mapping(uint32 => mapping (uint32 => Stake)) public betsAll; // ID-lottery => combination => Stake
	mapping(bytes32 => uint32) private queryRes;  // ID-query => ID-lottery
	
	uint256 public ORACLIZE_GAS_LIMIT = 2000000;
	uint256 public ORACLIZE_GASPRICE_GWEY = 40; // 40Gwey

	event LogEvent(string _event, string nameLottery, uint256 value);
	event LogToken(string _event, address user, uint32 idLottery, uint32 idToken, uint32 combination, uint256 amount);

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }
	
	modifier onlyAdmin {
        require(msg.sender == owner || admins[msg.sender]);
        _;
    }

	modifier onlyOraclize {
        require (msg.sender == oraclize_cbAddress());
        _;
    }

   function setTTGCoin (address _ttgCoin) onlyOwner() public {
    ttgCoin = ITTGCoin(_ttgCoin);
   }

   function setItemToken (address _itemToken) onlyOwner() public {
    itemToken = IItemToken(_itemToken);
   }


	function getLotteryByID(uint32 _id) public view returns (
		string  nameLottery,
		uint32 countCombinations,
		uint32 dateStopBuy,
		uint32 gameID,
		uint32 teamAID,
		uint32 teamBID,		
		uint32 minStake,
		uint32 winCombination,
		uint32 betsCount,
		uint256 betsSumIn,
		uint256 feeValue,
		Status status,
		bool isFreezing
	){
		Game storage gm = game[_id];
		nameLottery = gm.nameLottery;
		countCombinations = gm.countCombinations;
		dateStopBuy = gm.dateStopBuy;
		gameID = gm.gameID;
		teamAID = gm.teamAID;
		teamBID = gm.teamBID;
		minStake = gm.minStake;
		winCombination = gm.winCombination;
		betsCount = getCountTokensByLottery(_id);
		betsSumIn = gm.betsSumIn;  
		if (betsSumIn==0) betsSumIn = getSumInByLottery(_id);
		feeValue = gm.feeValue;
		status = gm.status;
		if ( status == Status.PLAYING && timenow() > dateStopBuy ) status = Status.PROCESSING;
		isFreezing = gm.isFreezing;
	}
	
	function getCountTokensByLottery(uint32 idLottery) internal view returns (uint32)
	{
		Game storage curGame = game[idLottery];
		uint32 count = 0;
		for(uint32 i=1;i<=curGame.countCombinations;i++) count += betsAll[idLottery][i].count;
		return count;
	}
	
	function getSumInByLottery(uint32 idLottery) internal view returns (uint256)
	{
		Game storage curGame = game[idLottery];
		uint256 sum = 0;
		for(uint32 i=1;i<=curGame.countCombinations;i++) sum += betsAll[idLottery][i].sum;
		return sum;
	}
	
	function getTokenByID(uint256 _id) public view returns ( 
			uint256 price,
			uint256 payment,
			uint32 combination,
			uint32 dateBuy,
			uint32 idLottery,
			address ownerToken,
			bool payout,
			uint256 sameComboAmount,
			uint256 tokenID
	){
		Token storage tkn = tokens[_id];

		price = tkn.price;
		
		uint256 packed = tkn.option;
		payout = uint8((packed >> (12*8)) & 0xFF)==1?true:false;
		idLottery   = uint32((packed >> (8*8)) & 0xFFFFFFFF);
		combination = uint32((packed >> (4*8)) & 0xFFFFFFFF);
		dateBuy     = uint32(packed & 0xFFFFFFFF);
		sameComboAmount = betsAll[idLottery][combination].sum;
		tokenID = _id;

		payment = 0;
		Game storage curGame = game[idLottery];
		
		uint256 betsSumIn = curGame.betsSumIn;  
		if (betsSumIn==0) betsSumIn = getSumInByLottery(idLottery);

		if (curGame.winCombination==combination) payment = betsSumIn * tkn.price / betsAll[idLottery][ curGame.winCombination ].sum;
		if (curGame.status == Status.CANCELING) payment = tkn.price;
		
		ownerToken = tokenIndexToOwner[_id];
	}

	function getUserTokens(address user, uint32 count) public view returns ( string res ) 
	{
		res="";
		require(user!=0x0);
		uint32 findCount=0;
		for (uint256 i = totalSupply-1; i >= 0; i--)
		{
			if(i>totalSupply) break;
			if (user == tokenIndexToOwner[i]) 
			{
				res = strConcat( res, ",", uint2str(i) );
				findCount++;
				if (count!=0 && findCount>=count) break;
			}
		}
	}

	function getUserTokensByMatch(address user, uint32 matchID) public view returns ( string res ) 
	{
		res="";
		require(user!=0x0);
		uint32 findCount=0;
		for (uint256 i = totalSupply-1; i >= 0; i--)
		{
			if(i>totalSupply) break;
			if (user == tokenIndexToOwner[i]) 
			{
				Token storage tkn = tokens[i];
				uint256 packed = tkn.option;
				uint32 idStored   = uint32((packed >> (8*8)) & 0xFFFFFFFF);
				if(idStored == matchID){
					res = strConcat( res, ",", uint2str(i) );
					findCount++;
				}
				
			}
		}
	}	

	function getStatLotteries() public view returns ( 
			uint32 countAll,
			uint32 countPlaying,
			uint32 countProcessing,
			string listPlaying,
			string listProcessing
	){
		countAll = uint32(game.length);
		countPlaying = 0;
		countProcessing = 0;
		listPlaying="";
		listProcessing="";
		uint32 curtime = timenow();
		for (uint32 i = 0; i < countAll; i++)
		{
			if (game[i].status!=Status.PLAYING) continue;
			if (curtime <  game[i].dateStopBuy) { countPlaying++; listPlaying = strConcat( listPlaying, ",", uint2str(i) ); }
			if (curtime >= game[i].dateStopBuy) { countProcessing++; listProcessing = strConcat( listProcessing, ",", uint2str(i) ); }
		}
		
	}

	function TTGOracle() public 
	{
	}

	function setOraclizeGasPrice(uint256 priceGwey, uint256 limit) onlyAdmin public
	{
		ORACLIZE_GASPRICE_GWEY = priceGwey;
		ORACLIZE_GAS_LIMIT = limit;
		oraclize_setCustomGasPrice( uint256(ORACLIZE_GASPRICE_GWEY) * 10**9 );
	}

	function freezeLottery(uint32 idLottery, bool freeze) public onlyAdmin 
	{ 
		Game storage curGame = game[idLottery];
		require( curGame.isFreezing != freeze );
		curGame.isFreezing = freeze; 
	}

	function addLottery( string _nameLottery, uint32 _dateStopBuy, uint32 _countCombinations, uint32 gameID, uint32 teamAID, uint32 teamBID, uint32 _minStakeFinney ) onlyAdmin public 
	{

		require( bytes(_nameLottery).length > 2 );
		require( _countCombinations > 1 );
		require( _minStakeFinney > 0 );
		require( _dateStopBuy > timenow() );

		Game memory _game;
		_game.nameLottery = _nameLottery;
		_game.countCombinations = _countCombinations;
		_game.dateStopBuy = _dateStopBuy;
		_game.gameID = gameID;
		_game.minStake 	= _minStakeFinney;
		_game.status = Status.PLAYING;
		_game.teamAID = teamAID;
		_game.teamBID = teamBID;
		

		uint256 newGameId = game.push(_game) - 1;
		
		
		LogEvent( "AddGame", _nameLottery, newGameId );
	}

	function () payable public { require (msg.value == 0x0); }

	function setUserAirDropRate(uint32 rate) onlyAdmin public{
		userAirDropRate = rate;
	}

	function setOwnerAirDropRate(uint32 rate) onlyAdmin public{
		ownerAirDropRate = rate;
	}	
	
	function buyToken(uint32 idLottery, uint32 teamID, uint32 combination, address captainAddress) payable public
	{
		Game storage curGame = game[idLottery];
		require( curGame.status == Status.PLAYING );
		require( timenow() < curGame.dateStopBuy );
		require( combination > 0 && combination <= curGame.countCombinations );
		require( captainAddress != msg.sender );
		require( curGame.isFreezing == false );
		
		// check money for stake
		require( msg.value >= curGame.minStake * 1 finney );
		
		uint256 userStake = msg.value;
		uint256 airDropAmountUser = userStake.mul(userAirDropRate); 
		if(airDropAmountUser > 1000*10**18) airDropAmountUser = 1000*10**18;
		ttgCoin.airDrop(this, msg.sender, airDropAmountUser);
		address teamOwner = itemToken.ownerOf(teamID);
		if(teamOwner!=0x0){
			uint256 airDropAmountOwner = userStake.mul(ownerAirDropRate); 	
			if(airDropAmountOwner > 1000*10**18) airDropAmountOwner = 1000*10**18;			
			ttgCoin.airDrop(this, teamOwner, airDropAmountOwner);   //for team owner 
		}
		uint256 feeValue = userStake.mul(4).div(100);		// 4% fee for contract
		userStake = userStake.minus(feeValue);
		
		if (captainAddress!=0x0) 
		{
			//uint256 captainValue = feeValue.mul(20).div(100);		// bonus for captain = 1%
			// feeValue = feeValue - captainValue;
			// require(feeValue + captainValue + userStake == msg.value);
			// captainAddress.transfer(captainValue);
			ttgCoin.airDrop(this, captainAddress, airDropAmountOwner);   //team owner 
		}

		curGame.feeValue  = curGame.feeValue.add(feeValue);
		betsAll[idLottery][combination].sum += userStake;
		betsAll[idLottery][combination].count++;

		uint128 packed;
		packed = ( uint128(idLottery) << 64 ) + ( uint128(combination) << 32 ) + uint128(block.timestamp);

		Token memory _token = Token({
			price: userStake,
			option : packed
		});

		uint256 newTokenId = totalSupply++;
		tokens[newTokenId] = _token;
		_transfer(0, msg.sender, newTokenId);
		
		LogToken( "Buy", msg.sender, idLottery, uint32(newTokenId), combination, userStake);
	}
	
	// take win money or money for canceling lottery
	function redeemToken(uint256 _tokenId, uint32 teamID) public 
	{
		Token storage tkn = tokens[_tokenId];

		uint256 packed = tkn.option;
		bool payout = uint8((packed >> (96)) & 0xFF)==1?true:false;
		uint32 idLottery = uint32((packed >> (64)) & 0xFFFFFFFF);
		uint32 combination = uint32((packed >> (32)) & 0xFFFFFFFF);

		Game storage curGame = game[idLottery];
		
		require( curGame.status == Status.PAYING || curGame.status == Status.CANCELING);

		require( msg.sender == tokenIndexToOwner[_tokenId] );	// only onwer`s token
		require( payout == false ); // has not paid
		require( combination == curGame.winCombination || curGame.status == Status.CANCELING );

		uint256 sumPayment = 0;
		if ( curGame.status == Status.CANCELING ) sumPayment = tkn.price;
		if ( curGame.status == Status.PAYING ){			
			sumPayment = curGame.betsSumIn * tkn.price / betsAll[idLottery][curGame.winCombination].sum;
			address teamOwner = itemToken.ownerOf(teamID);
				if(teamOwner!=0x0){					
					teamOwner.transfer(sumPayment.div(100));
					sumPayment = sumPayment.mul(99).div(100);
				}
		}

		payout = true;
		packed += uint128(payout?1:0) << 96;
		tkn.option = packed;
	
		msg.sender.transfer(sumPayment);
		
		LogToken( "Redeem", msg.sender, idLottery, uint32(_tokenId), combination, sumPayment);
	}
	
	function cancelLottery(uint32 idLottery) public 
	{
		Game storage curGame = game[idLottery];
		
		require( curGame.status == Status.PLAYING );
		// only owner/admin or anybody after 7 days
		require( msg.sender == owner || admins[msg.sender] || timenow() > curGame.dateStopBuy.add(7 * 24*60*60) );

		curGame.status = Status.CANCELING;

		LogEvent( "CancelLottery", curGame.nameLottery, idLottery );
		
		takeFee(idLottery);
	}

	function __callback(bytes32 queryId, string _result) onlyOraclize public
	{
		uint32 idLottery = queryRes[queryId];
		require( idLottery != 0 );

		Game storage curGame = game[idLottery];
		
		require( curGame.status == Status.PLAYING );
		require( timenow() > curGame.dateStopBuy );
		
		uint32 tmpCombination = uint32(parseInt(_result,0));
		
		string memory error = "callback";
		if ( tmpCombination==0 ) error = "callback_result_not_found";
		if ( tmpCombination > curGame.countCombinations ) { tmpCombination = 0; error = "callback_result_limit"; }

		LogEvent( error, curGame.nameLottery, tmpCombination );

		if (tmpCombination!=0) 
		{
			curGame.winCombination = tmpCombination;
			checkWinNobody(idLottery);
		}
	}

	function resolveLotteryByOraclize(uint32 idLottery, uint32 delaySec) onlyAdmin public payable
	{
		Game storage curGame = game[idLottery];
		
		uint oraclizeFee = oraclize_getPrice( "URL", ORACLIZE_GAS_LIMIT );
		require(msg.value + curGame.feeValue > oraclizeFee); // if contract has not enought money to do query
		
		curGame.feeValue = curGame.feeValue + msg.value - oraclizeFee;

		LogEvent( "ResolveLotteryByOraclize", curGame.nameLottery, delaySec );
		
		string memory tmpQuery;
		tmpQuery = strConcat( "json(https://totalgame.io/api/v2/game/", uint2str(idLottery), "/result.json).result" );
	
		uint32 delay;
		if ( timenow() < curGame.dateStopBuy ) delay = curGame.dateStopBuy - timenow() + delaySec; //TODO:need to convert to safe math
										  else delay = delaySec;
	
		bytes32 queryId = oraclize_query(delay, "URL", tmpQuery, ORACLIZE_GAS_LIMIT);
		queryRes[queryId] = idLottery;
	}

	function resolveLotteryByHand(uint32 idLottery, uint32 combination) onlyAdmin public 
	{
		Game storage curGame = game[idLottery];
		
		require( curGame.status == Status.PLAYING );
		require( combination <= curGame.countCombinations );
		require( combination != 0 );

		//require( timenow() > curGame.dateStopBuy + 2*60*60 ); //TODO: remove comment

		curGame.winCombination = combination;
		
		LogEvent( "ResolveLotteryByHand", curGame.nameLottery, curGame.winCombination );
		
		checkWinNobody(idLottery);
	}
	
	function checkWinNobody(uint32 idLottery) internal
	{
		Game storage curGame = game[idLottery];
		
		curGame.status = Status.PAYING;
		curGame.betsSumIn = getSumInByLottery(idLottery);
		
		// nobody win = send all to feeLottery
		if ( betsAll[idLottery][curGame.winCombination].count == 0 )
		{
			if (curGame.betsSumIn+curGame.feeValue!=0) feeLottery = feeLottery.add(curGame.betsSumIn).add(curGame.feeValue);
			LogEvent( "NOBODYWIN", curGame.nameLottery, curGame.betsSumIn+curGame.feeValue );
		}
		else 
			takeFee(idLottery);
	}
	
	function takeFee(uint32 idLottery) internal
	{
		Game storage curGame = game[idLottery];
		
		// take fee
		if ( curGame.feeValue > 0 )
		{
			feeLottery = feeLottery + curGame.feeValue;
			LogEvent( "TakeFee", curGame.nameLottery, curGame.feeValue );
		}
	}
	
	function withdraw() onlyOwner public
	{
		require( feeLottery > 0 );

		uint256 tmpFeeLottery = feeLottery;
		feeLottery = 0;
		
		owner.transfer(tmpFeeLottery);
		LogEvent( "WITHDRAW", "", tmpFeeLottery);
	}

}
function redeemToken(uint256 _tokenId, uint32 teamID) public 
{
	Token storage tkn = tokens[_tokenId];

	uint256 packed = tkn.option;
	bool payout = uint8((packed >> (96)) & 0xFF)==1?true:false;
	uint32 idLottery = uint32((packed >> (64)) & 0xFFFFFFFF);
	uint32 combination = uint32((packed >> (32)) & 0xFFFFFFFF);

	Game storage curGame = game[idLottery];
		
	require( curGame.status == Status.PAYING || curGame.status == Status.CANCELING);

	require( msg.sender == tokenIndexToOwner[_tokenId] );	// only onwer`s token
	require( payout == false ); // has not paid
	require( combination == curGame.winCombination || curGame.status == Status.CANCELING );

	uint256 sumPayment = 0;
	if ( curGame.status == Status.CANCELING ) sumPayment = tkn.price;
	if ( curGame.status == Status.PAYING ){			
		sumPayment = curGame.betsSumIn * tkn.price / betsAll[idLottery][curGame.winCombination].sum;
		address teamOwner = itemToken.ownerOf(teamID);
			if(teamOwner!=0x0){					
				teamOwner.transfer(sumPayment.div(100));
				sumPayment = sumPayment.mul(99).div(100);
			}
	}

	payout = true;
	packed += uint128(payout?1:0) << 96;
	tkn.option = packed;
	
	msg.sender.transfer(sumPayment);
		
	LogToken( "Redeem", msg.sender, idLottery, uint32(_tokenId), combination, sumPayment);
}

// take win money or money for canceling lottery