#include "redis_manage.h"
#include <string.h>
#include <strings.h>
#include <Poco/Stopwatch.h>

#define RedisNeedAuth 1
RedisManage* RedisManage::_redisIns = NULL;

RedisManage::RedisManage()
: _logger(Logger::get("StrategyServer"))
{
    _pConf = ServerConfig::getInstance();
    if(NULL == _pConf)
    {
        _logger.error("getInstance error!");
    }
    else
    {
        _redisIp = _pConf->getLocalSettings()._redisServerIp;
        _redisAuth = _pConf->getLocalSettings()._redisAuth;
        _redisNeedAuth = _pConf->getLocalSettings()._redisNeedAuth;
        _redisPort = _pConf->getLocalSettings()._redisServerPort;
        _redisTimeOut = _pConf->getLocalSettings()._redisConTimeOut;
        _connRetryTimes = _pConf->getLocalSettings()._connRetryTimes;
    }

    _logger.information(format("redisIp is:%s,redisPort is:%?d,redisTimeOut is:%?d,redisNeedAuth is:%?d,redisAuth is:%s",_redisIp,_redisPort,_redisTimeOut,_redisNeedAuth,_redisAuth));
}
RedisManage::~RedisManage()
{
}
void RedisManage::freeRedisContext(redisContext **pContext)
{
	redisFree(*pContext);
}
void RedisManage::getRedisContext(redisContext **pContext)
{
	int iRetry = 0;
	while(iRetry++ < _connRetryTimes)
	{
	    *pContext = connect(_redisIp, _redisPort, _redisTimeOut);
	    if(NULL != *pContext)
	    	break;
	    _logger.error(format("redis connection has retried %?d times",iRetry));
	}
	if(NULL == *pContext)
	{
		_logger.error(format("redis context return NULL after %?d retry times,redis connecting Fail!",_connRetryTimes));
	}
	else
	{
		if(RedisNeedAuth == _redisNeedAuth)
		{
			std::string stCmd = string("auth") + string(" ") + _redisAuth;
			if(false == set(stCmd,*pContext))
			{
				_logger.error(format("redis set auth error!,stCmd is:%s",stCmd));
				freeRedisContext(pContext);
				*pContext = NULL;
			}
		}
	}
}
redisContext* RedisManage::connect(const std::string &stIp, const unsigned int &port, const int &timeout)
{
	redisContext *redisCon = NULL;
	if( timeout < 0 )
		redisCon = redisConnect(stIp.c_str(), port);
	else
	{
		struct timeval tv;
		tv.tv_sec = 0;
		tv.tv_usec = 1000*timeout;
		redisCon = redisConnectWithTimeout(stIp.c_str(), port, tv);
	}
	if(redisCon == NULL)
	{
		_logger.error(format("redisConnect error,ip is:%s,port is:%?d",stIp,port));
		return NULL;
	}
	if(redisCon->err != REDIS_OK)
	{
		_logger.error(format("redisConnect error,ip is:%s,port is:%?d,error_no:%?d,error_msg:%s",stIp,port,redisCon->err,string(redisCon->errstr)));
		redisFree(redisCon);
		return NULL;
	}
	return redisCon;
}

RedisManage* RedisManage::getInstance()
{
	if(NULL == _redisIns)
	{
		_redisIns = new RedisManage();
	}
	return _redisIns;
}

bool RedisManage::get(const std::string &stCommand, std::string &stValue,redisContext *pContext, int *iduration)
{
	Stopwatch stopwatch;
	stopwatch.start();

	if(stCommand.empty())
	{
		_logger.error("redis key is empty,error!");
		return false;
	}
	redisReply *r = (redisReply*)redisCommand(pContext, stCommand.c_str());
	if(NULL == r)
	{
		_logger.error(format("redisCommand:%s,need check whether the redis_connect(%s:%?d) is normal",stCommand,_redisIp,_redisPort));
		return false;
	}
	if(r->type != REDIS_REPLY_STRING)
	{
		freeReplyObject(r);
		_logger.error(format("redis type is:%?d,Fail to execute command:%s",r->type,stCommand));
		return false;
	}
	stValue = r->str;
	freeReplyObject(r);

	stopwatch.stop();
	if(NULL != iduration)
		*iduration = (int)(stopwatch.elapsed()/1000);
	return true;
}

redisReply *RedisManage::mget(const std::string &stCommand, redisContext *pContext, int *iduration)
{
	Stopwatch stopwatch;
	stopwatch.start();

	if(stCommand.empty())
	{
		_logger.error("redis key is empty,error!");
		return NULL;
	}
	redisReply *r = (redisReply*)redisCommand(pContext, stCommand.c_str());
	if(NULL == r)
	{
		_logger.error(format("redisCommand:%s,need check whether the redis_connect(%s:%?d) is normal",stCommand,_redisIp,_redisPort));
		return NULL;
	}
	if(r->type != REDIS_REPLY_ARRAY)
	{
		freeReplyObject(r);
		_logger.error(format("Fail to execute command:[%s]",stCommand));
		return NULL;
	}

	stopwatch.stop();
	if(NULL != iduration)
		*iduration = (int)(stopwatch.elapsed()/1000);
	return r;
}
bool RedisManage::scan(const std::string &stCommand, const int iCount, redisContext *pContext, std::map<string,string> &stMapResult, const char* pMatch, int *iduration)
{
	Stopwatch stopwatch;
	stopwatch.start();

	std::string stFullCmd;
	std::stringstream stFullStreamCmd;

	stFullStreamCmd << " count " << iCount;
	if (NULL != pMatch)
		stFullStreamCmd << " match " << pMatch;

	std::string stCmdLimit = stFullStreamCmd.str();
	std::string stCursor("0");

	do
	{
		stFullCmd = stCommand + std::string(" ") + stCursor + std::string(" ") + stCmdLimit;
		redisReply *r = (redisReply*)redisCommand(pContext, stFullCmd.c_str());
		if(NULL == r)
		{
			_logger.error(format("redisCommand:%s error,need check whether the redis_connect(%s:%?d) is normal",stFullCmd,_redisIp,_redisPort));
			return false;
		}
		if(r->type != REDIS_REPLY_ARRAY or r->elements != 2 or r->element[1]->type != REDIS_REPLY_ARRAY)
		{
			freeReplyObject(r);
			_logger.error(format("redisContext type:%?d or element:%?d error,redisCommand is:%s",r->type,r->element,stFullCmd));
			return false;
		}
		stCursor = string(r->element[0]->str);
		for(int i=0; i< r->element[1]->elements; i+=2)
		{
			stMapResult[string(r->element[1]->element[i]->str)] = string(r->element[1]->element[i+1]->str);
		}
		freeReplyObject(r);
	}while(atoi(stCursor.c_str()) != 0);

	stopwatch.stop();
	if(NULL != iduration)
		*iduration = (int)(stopwatch.elapsed()/1000);
	return true;
}
bool RedisManage::set(const std::string &stCommand,redisContext *pContext, int *iduration)
{
	Stopwatch stopwatch;
	stopwatch.start();

	if(stCommand.empty())
	{
		_logger.error("redis key is empty,error!");
		return false;
	}
	redisReply *r = (redisReply*)redisCommand(pContext, stCommand.c_str());
	if(NULL == r)
	{
		_logger.error(format("redisCommand:%s error,need check whether the redis_connect(%s:%?d) is normal",stCommand,_redisIp,_redisPort));
		return false;
	}
	if(!(r->type == REDIS_REPLY_STATUS && strcasecmp(r->str,"ok")==0) && !(r->type == REDIS_REPLY_INTEGER))
	{
		freeReplyObject(r);
		_logger.error(format("Fail to execute command:%s,redis type is:%?d,get str is:%s",stCommand,r->type,string(r->str)));
		return false;
	}
	freeReplyObject(r);

	stopwatch.stop();
	if(NULL != iduration)
		*iduration = (int)(stopwatch.elapsed()/1000);

	return true;
}
