/**
* Copyright (C) 2008 Happy Fish / YuQing
*
* FastDFS may be copied only under the terms of the GNU General
* Public License V3, which may be found in the FastDFS source kit.
* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail.
**/

#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <netdb.h>
#include "fastcommon/logger.h"
#include "fastcommon/sockopt.h"
#include "fastcommon/shared_func.h"
#include "fastcommon/local_ip_func.h"
#include "tracker_proto.h"
#include "fdfs_global.h"
#include "fdfs_shared_func.h"


bool fdfs_server_contain(TrackerServerInfo *pServerInfo,
        const char *target_ip, const int target_port)
{
	ConnectionInfo *conn;
	ConnectionInfo *end;

    if (pServerInfo->count == 1)
    {
		return FC_CONNECTION_SERVER_EQUAL(pServerInfo->connections[0],
                target_ip, target_port);
    }
    else if (pServerInfo->count == 2)
    {
		return FC_CONNECTION_SERVER_EQUAL(pServerInfo->connections[0],
                target_ip, target_port) ||
            FC_CONNECTION_SERVER_EQUAL(pServerInfo->connections[1],
                    target_ip, target_port);
    }

	end = pServerInfo->connections + pServerInfo->count;
	for (conn=pServerInfo->connections; conn<end; conn++)
    {
		if (FC_CONNECTION_SERVER_EQUAL(*conn, target_ip, target_port))
        {
            return true;
        }
    }

    return false;
}

bool fdfs_server_contain_ex(TrackerServerInfo *pServer1,
        TrackerServerInfo *pServer2)
{
	ConnectionInfo *conn;
	ConnectionInfo *end;

    if (pServer1->count == 1)
    {
        return fdfs_server_contain1(pServer2, pServer1->connections + 0);
    }
    else if (pServer1->count == 2)
    {
        if (fdfs_server_contain1(pServer2, pServer1->connections + 0))
        {
            return true;
        }
        return fdfs_server_contain1(pServer2, pServer1->connections + 1);
    }

	end = pServer1->connections + pServer1->count;
	for (conn=pServer1->connections; conn<end; conn++)
    {
		if (fdfs_server_contain1(pServer2, conn))
        {
            return true;
        }
    }

    return false;
}

bool fdfs_server_equal(TrackerServerInfo *pServer1,
        TrackerServerInfo *pServer2)
{
	ConnectionInfo *conn;
	ConnectionInfo *end;

    if (pServer1->count != pServer2->count)
    {
        return false;
    }

    if (pServer1->count == 1)
    {
        return (pServer1->connections->port == pServer2->connections->port &&
            strcmp(pServer1->connections->ip_addr, pServer2->connections->ip_addr) == 0);
    }

	end = pServer1->connections + pServer1->count;
	for (conn=pServer1->connections; conn<end; conn++)
    {
		if (!fdfs_server_contain1(pServer2, conn))
        {
            return false;
        }
    }

    return true;
}

bool fdfs_server_contain_local_service(TrackerServerInfo *pServerInfo,
        const int target_port)
{
    const char *current_ip;
    
    current_ip = get_first_local_ip();
    while (current_ip != NULL)
    {
        if (fdfs_server_contain(pServerInfo, current_ip, target_port))
        {
            return true;
        }
        current_ip = get_next_local_ip(current_ip);
    }

    return false;
}

TrackerServerInfo *fdfs_tracker_group_get_server(TrackerServerGroup *pGroup,
        const char *target_ip, const int target_port)
{
    TrackerServerInfo *pServer;
    TrackerServerInfo *pEnd;

    pEnd = pGroup->servers + pGroup->server_count;
    for (pServer=pGroup->servers; pServer<pEnd; pServer++)
    {
        if (fdfs_server_contain(pServer, target_ip, target_port))
        {
            return pServer;
        }
    }

    return NULL;
}

void fdfs_server_sock_reset(TrackerServerInfo *pServerInfo)
{
	ConnectionInfo *conn;
	ConnectionInfo *end;

    if (pServerInfo->count == 1)
    {
		pServerInfo->connections[0].sock = -1;
    }
    else if (pServerInfo->count == 2)
    {
		pServerInfo->connections[0].sock = -1;
		pServerInfo->connections[1].sock = -1;
    }
    else
    {
        end = pServerInfo->connections + pServerInfo->count;
        for (conn=pServerInfo->connections; conn<end; conn++)
        {
            conn->sock = -1;
        }
    }
}

int fdfs_get_tracker_leader_index_ex(TrackerServerGroup *pServerGroup,
		const char *leaderIp, const int leaderPort)
{
	TrackerServerInfo *pServer;
	TrackerServerInfo *pEnd;

	if (pServerGroup->server_count == 0)
	{
		return -1;
	}

	pEnd = pServerGroup->servers + pServerGroup->server_count;
	for (pServer=pServerGroup->servers; pServer<pEnd; pServer++)
	{
        if (fdfs_server_contain(pServer, leaderIp, leaderPort))
		{
			return pServer - pServerGroup->servers;
		}
	}

	return -1;
}

int fdfs_parse_storage_reserved_space(IniContext *pIniContext,
		FDFSStorageReservedSpace *pStorageReservedSpace)
{
	int result;
	int len;
	char *pReservedSpaceStr;
	int64_t storage_reserved;

	pReservedSpaceStr = iniGetStrValue(NULL, \
			"reserved_storage_space", pIniContext);
	if (pReservedSpaceStr == NULL)
	{
		pStorageReservedSpace->flag = \
				TRACKER_STORAGE_RESERVED_SPACE_FLAG_MB;
		pStorageReservedSpace->rs.mb = FDFS_DEF_STORAGE_RESERVED_MB;
		return 0;
	}
	if (*pReservedSpaceStr == '\0')
	{
		logError("file: "__FILE__", line: %d, " \
			"item \"reserved_storage_space\" is empty!", \
			__LINE__);
		return EINVAL;
	}

	len = strlen(pReservedSpaceStr);
	if (*(pReservedSpaceStr + len - 1) == '%')
	{
		char *endptr;
		pStorageReservedSpace->flag = TRACKER_STORAGE_RESERVED_SPACE_FLAG_RATIO;
		endptr = NULL;
		*(pReservedSpaceStr + len - 1) = '\0';
		pStorageReservedSpace->rs.ratio = \
					strtod(pReservedSpaceStr, &endptr);
		if (endptr != NULL && *endptr != '\0')
		{
			logError("file: "__FILE__", line: %d, " \
				"item \"reserved_storage_space\": %s%%"\
				" is invalid!", __LINE__, \
				pReservedSpaceStr);
			return EINVAL;
		}

		if (pStorageReservedSpace->rs.ratio <= 0.00 || \
			pStorageReservedSpace->rs.ratio >= 100.00)
		{
			logError("file: "__FILE__", line: %d, " \
				"item \"reserved_storage_space\": %s%%"\
				" is invalid!", __LINE__, \
				pReservedSpaceStr);
			return EINVAL;
		}

		pStorageReservedSpace->rs.ratio /= 100.00;
		return 0;
	}

	if ((result=parse_bytes(pReservedSpaceStr, 1, &storage_reserved)) != 0)
	{
		return result;
	}

	pStorageReservedSpace->flag = TRACKER_STORAGE_RESERVED_SPACE_FLAG_MB;
	pStorageReservedSpace->rs.mb = storage_reserved / FDFS_ONE_MB;
	return 0;
}

const char *fdfs_storage_reserved_space_to_string(FDFSStorageReservedSpace
			*pStorageReservedSpace, char *buff)
{
	if (pStorageReservedSpace->flag ==
			TRACKER_STORAGE_RESERVED_SPACE_FLAG_MB)
	{
		sprintf(buff, "%"PRId64"MB", pStorageReservedSpace->rs.mb);
	}
	else
	{
		sprintf(buff, "%.2f%%", 100.00 * pStorageReservedSpace->rs.ratio);
	}

	return buff;
}

const char *fdfs_storage_reserved_space_to_string_ex(const bool flag,
	const int64_t space_mb, const int64_t total_mb,
    const double space_ratio, char *buff)
{
	if (flag == TRACKER_STORAGE_RESERVED_SPACE_FLAG_MB)
	{
		sprintf(buff, "%"PRId64" MB", space_mb);
	}
	else
	{
		sprintf(buff, "%"PRId64" MB(%.2f%%)", (int64_t)(total_mb * space_ratio),
			 100.00 * space_ratio);
	}

	return buff;
}

int64_t fdfs_get_storage_reserved_space_mb(const int64_t total_mb,
		FDFSStorageReservedSpace *pStorageReservedSpace)
{
	if (pStorageReservedSpace->flag == \
			TRACKER_STORAGE_RESERVED_SPACE_FLAG_MB)
	{
		return pStorageReservedSpace->rs.mb;
	}
	else
	{
		return (int64_t)(total_mb * pStorageReservedSpace->rs.ratio);
	}
}

bool fdfs_check_reserved_space(FDFSGroupInfo *pGroup, \
	FDFSStorageReservedSpace *pStorageReservedSpace)
{
	if (pStorageReservedSpace->flag == \
			TRACKER_STORAGE_RESERVED_SPACE_FLAG_MB)
	{
		return pGroup->free_mb > pStorageReservedSpace->rs.mb;
	}
	else
	{
		if (pGroup->total_mb == 0)
		{
			return false;
		}

		/*
		logInfo("storage=%.4f, rs.ratio=%.4f", 
			((double)pGroup->free_mb / (double)pGroup->total_mb),
			pStorageReservedSpace->rs.ratio);
		*/

		return ((double)pGroup->free_mb / (double)pGroup->total_mb) > \
			pStorageReservedSpace->rs.ratio;
	}
}

bool fdfs_check_reserved_space_trunk(FDFSGroupInfo *pGroup, \
	FDFSStorageReservedSpace *pStorageReservedSpace)
{
	if (pStorageReservedSpace->flag == \
			TRACKER_STORAGE_RESERVED_SPACE_FLAG_MB)
	{
		return (pGroup->free_mb + pGroup->trunk_free_mb > 
			pStorageReservedSpace->rs.mb);
	}
	else
	{
		if (pGroup->total_mb == 0)
		{
			return false;
		}

		/*
		logInfo("storage trunk=%.4f, rs.ratio=%.4f", 
		((double)(pGroup->free_mb + pGroup->trunk_free_mb) / \
		(double)pGroup->total_mb), pStorageReservedSpace->rs.ratio);
		*/

		return ((double)(pGroup->free_mb + pGroup->trunk_free_mb) / \
		(double)pGroup->total_mb) > pStorageReservedSpace->rs.ratio;
	}
}

bool fdfs_check_reserved_space_path(const int64_t total_mb, \
	const int64_t free_mb, const int64_t avg_mb, \
	FDFSStorageReservedSpace *pStorageReservedSpace)
{
	if (pStorageReservedSpace->flag == \
			TRACKER_STORAGE_RESERVED_SPACE_FLAG_MB)
	{
		return free_mb > avg_mb;
	}
	else
	{
		if (total_mb == 0)
		{
			return false;
		}

		/*
		logInfo("storage path, free_mb=%"PRId64 \
			", total_mb=%"PRId64", " \
			"real ratio=%.4f, rs.ratio=%.4f", \
			free_mb, total_mb, ((double)free_mb / total_mb), \
			pStorageReservedSpace->rs.ratio);
		*/

		return ((double)free_mb / (double)total_mb) > \
			pStorageReservedSpace->rs.ratio;
	}
}

int fdfs_connection_pool_init(const char *config_filename, \
		IniContext *pItemContext)
{
	g_use_connection_pool = iniGetBoolValue(NULL, "use_connection_pool", \
				pItemContext, false);
	if (!g_use_connection_pool)
	{
		return 0;
	}

	g_connection_pool_max_idle_time = iniGetIntValue(NULL, \
			"connection_pool_max_idle_time", \
			pItemContext, 3600);
	if (g_connection_pool_max_idle_time <= 0)
	{
		logError("file: "__FILE__", line: %d, " \
			"connection_pool_max_idle_time: %d of conf " \
			"filename: \"%s\" is invalid!", __LINE__, \
			g_connection_pool_max_idle_time, config_filename);
		return EINVAL;
	}

	return conn_pool_init(&g_connection_pool, SF_G_CONNECT_TIMEOUT, \
        		0, g_connection_pool_max_idle_time);
}

void fdfs_connection_pool_destroy()
{
	conn_pool_destroy(&g_connection_pool);
}

void fdfs_set_log_rotate_size(LogContext *pContext, const int64_t log_rotate_size)
{
	if (log_rotate_size > 0)
	{
		pContext->rotate_size = log_rotate_size;
		log_set_rotate_time_format(pContext, "%Y%m%d_%H%M%S");
	}
	else
	{
		pContext->rotate_size = 0;
		log_set_rotate_time_format(pContext, "%Y%m%d");
	}
}

int fdfs_parse_server_info_ex(char *server_str, const int default_port,
        TrackerServerInfo *pServer, const bool resolve)
{
	char *pColon;
    char *hosts[FDFS_MULTI_IP_MAX_COUNT];
    ConnectionInfo *conn;
    int port;
    int i;

    memset(pServer, 0, sizeof(TrackerServerInfo));
    if ((pColon=strrchr(server_str, ':')) == NULL)
    {
        logInfo("file: "__FILE__", line: %d, "
                "no port part in %s, set port to %d",
                __LINE__, server_str, default_port);
        port = default_port;
    }
    else
    {
        *pColon = '\0';
        port = atoi(pColon + 1);
    }

    conn = pServer->connections;
    pServer->count =  splitEx(server_str, ',',
            hosts, FDFS_MULTI_IP_MAX_COUNT);
    for (i=0; i<pServer->count; i++)
    {
        if (resolve)
        {
            if (getIpaddrByName(hosts[i], conn->ip_addr,
                        sizeof(conn->ip_addr)) == INADDR_NONE)
            {
                logError("file: "__FILE__", line: %d, "
                        "host \"%s\" is invalid, error info: %s",
                        __LINE__, hosts[i], hstrerror(h_errno));
                return EINVAL;
            }
        }
        else
        {
            snprintf(conn->ip_addr, sizeof(conn->ip_addr), "%s", hosts[i]);
        }
        conn->port = port;
        conn->sock = -1;
        conn++;
    }

    return 0;
}

int fdfs_server_info_to_string_ex(const TrackerServerInfo *pServer,
        const int port, char *buff, const int buffSize)
{
	const ConnectionInfo *conn;
	const ConnectionInfo *end;
    int len;

    if (pServer->count <= 0)
    {
        *buff = '\0';
        return 0;
    }
    if (pServer->count == 1)
    {
        return snprintf(buff, buffSize, "%s:%u",
                pServer->connections[0].ip_addr, port);
    }

    len = snprintf(buff, buffSize, "%s", pServer->connections[0].ip_addr);
	end = pServer->connections + pServer->count;
	for (conn=pServer->connections + 1; conn<end; conn++)
    {
        len += snprintf(buff + len, buffSize - len, ",%s", conn->ip_addr);
    }
    len += snprintf(buff + len, buffSize - len, ":%d", port);
    return len;
}

int fdfs_get_ip_type(const char* ip)
{
    if (ip == NULL || (int)strlen(ip) < 8)
    {
        return FDFS_IP_TYPE_UNKNOWN;
    }

    if (memcmp(ip, "10.", 3) == 0)
    {
        return FDFS_IP_TYPE_PRIVATE_10;
    }
    if (memcmp(ip, "192.168.", 8) == 0)
    {
        return FDFS_IP_TYPE_PRIVATE_192;
    }

    if (memcmp(ip, "172.", 4) == 0)
    {
        int b;
        b = atoi(ip + 4);
        if (b >= 16 && b < 32)
        {
            return FDFS_IP_TYPE_PRIVATE_172;
        }
    }

    return FDFS_IP_TYPE_OUTER;
}

int fdfs_check_server_ips(const TrackerServerInfo *pServer,
        char *error_info, const int error_size)
{
    int type0;
    int type1;
    if (pServer->count == 1)
    {
        *error_info = '\0';
        return 0;
    }

    if (pServer->count <= 0)
    {
        logError("file: "__FILE__", line: %d, "
                "empty server", __LINE__);
        return EINVAL;
    }

    if (pServer->count > FDFS_MULTI_IP_MAX_COUNT)
    {
        snprintf(error_info, error_size,
                "too many server ip addresses: %d, exceeds %d",
                pServer->count, FDFS_MULTI_IP_MAX_COUNT);
        return EINVAL;
    }

    type0 = fdfs_get_ip_type(pServer->connections[0].ip_addr);
    type1 = fdfs_get_ip_type(pServer->connections[1].ip_addr);
    if (type0 == type1)
    {
        snprintf(error_info, error_size,
                "invalid ip addresses %s and %s, "
                "one MUST be an inner IP and another is a outer IP, "
                "or two different types of inner IP addresses",
                pServer->connections[0].ip_addr,
                pServer->connections[1].ip_addr);
        return EINVAL;
    }

    *error_info = '\0';
    return 0;
}

int fdfs_parse_multi_ips_ex(char *ip_str, FDFSMultiIP *ip_addrs,
        char *error_info, const int error_size, const bool resolve)
{
    char *hosts[FDFS_MULTI_IP_MAX_COUNT];
    int i;

    ip_addrs->index = 0;
    ip_addrs->count = splitEx(ip_str, ',', hosts, FDFS_MULTI_IP_MAX_COUNT);
    for (i=0; i<ip_addrs->count; i++)
    {
        if (resolve)
        {
            if (getIpaddrByName(hosts[i], ip_addrs->ips[i].address,
                        sizeof(ip_addrs->ips[i].address)) == INADDR_NONE)
            {
                snprintf(error_info, error_size,
                        "host \"%s\" is invalid, error info: %s",
                        hosts[i], hstrerror(h_errno));
                return EINVAL;
            }
        }
        else
        {
            snprintf(ip_addrs->ips[i].address,
                    sizeof(ip_addrs->ips[i].address), "%s", hosts[i]);
        }

        ip_addrs->ips[i].type = fdfs_get_ip_type(ip_addrs->ips[i].address);
        if (ip_addrs->ips[i].type == FDFS_IP_TYPE_UNKNOWN)
        {
            snprintf(error_info, error_size,
                    "ip address \"%s\" is invalid",
                    ip_addrs->ips[i].address);
            return EINVAL;
        }
    }

    *error_info = '\0';
    return 0;
}

int fdfs_multi_ips_to_string_ex(const FDFSMultiIP *ip_addrs,
        const char seperator, char *buff, const int buffSize)
{
    int i;
    int len;

    if (ip_addrs->count <= 0)
    {
        *buff = '\0';
        return 0;
    }
    if (ip_addrs->count == 1)
    {
        return snprintf(buff, buffSize, "%s",
                ip_addrs->ips[0].address);
    }

    len = snprintf(buff, buffSize, "%s", ip_addrs->ips[0].address);
	for (i=1; i<ip_addrs->count; i++)
    {
        len += snprintf(buff + len, buffSize - len, "%c%s",
                seperator, ip_addrs->ips[i].address);
    }
    return len;
}

const char *fdfs_get_ipaddr_by_peer_ip(const FDFSMultiIP *ip_addrs,
        const char *client_ip)
{
    int ip_type;
    int index;
    if (ip_addrs->count == 1)
    {
        return ip_addrs->ips[0].address;
    }

    if (ip_addrs->count <= 0)
    {
        return "";
    }

    ip_type = fdfs_get_ip_type(client_ip);
    index = ip_addrs->ips[FDFS_MULTI_IP_INDEX_OUTER].type == ip_type ?
        FDFS_MULTI_IP_INDEX_OUTER : FDFS_MULTI_IP_INDEX_INNER;
    return ip_addrs->ips[index].address;
}

int fdfs_check_and_format_ips(FDFSMultiIP *ip_addrs,
        char *error_info, const int error_size)
{
    FDFSIPInfo swap_ip;
    if (ip_addrs->count == 1)
    {
        *error_info = '\0';
        return 0;
    }

    if (ip_addrs->count <= 0)
    {
        logError("file: "__FILE__", line: %d, "
                "empty server", __LINE__);
        return EINVAL;
    }

    if (ip_addrs->count > FDFS_MULTI_IP_MAX_COUNT)
    {
        snprintf(error_info, error_size,
                "too many server ip addresses: %d, exceeds %d",
                ip_addrs->count, FDFS_MULTI_IP_MAX_COUNT);
        return EINVAL;
    }

    if (ip_addrs->ips[FDFS_MULTI_IP_INDEX_INNER].type ==
            ip_addrs->ips[FDFS_MULTI_IP_INDEX_OUTER].type)
    {
        snprintf(error_info, error_size,
                "invalid ip addresses %s and %s, "
                "one MUST be an inner IP and another is a outer IP, "
                "or two different types of inner IP addresses",
                ip_addrs->ips[0].address, ip_addrs->ips[1].address);
        return EINVAL;
    }

    if (ip_addrs->ips[FDFS_MULTI_IP_INDEX_INNER].type == FDFS_IP_TYPE_OUTER)
    {
        swap_ip = ip_addrs->ips[FDFS_MULTI_IP_INDEX_INNER];
        ip_addrs->ips[FDFS_MULTI_IP_INDEX_INNER] =
            ip_addrs->ips[FDFS_MULTI_IP_INDEX_OUTER];
        ip_addrs->ips[FDFS_MULTI_IP_INDEX_OUTER] = swap_ip;
    }

    *error_info = '\0';
    return 0;
}

void fdfs_set_multi_ip_index(FDFSMultiIP *multi_ip, const char *target_ip)
{
    int i;
    if (multi_ip->count <= 1)
    {
        return;
    }

    for (i=0; i<multi_ip->count; i++)
    {
        if (strcmp(multi_ip->ips[i].address, target_ip) == 0)
        {
            multi_ip->index = i;
            break;
        }
    }
}

void fdfs_set_server_info_index(TrackerServerInfo *pServer,
        const char *target_ip, const int target_port)
{
    int i;
    if (pServer->count <= 1)
    {
        return;
    }

    for (i=0; i<pServer->count; i++)
    {
        if (FC_CONNECTION_SERVER_EQUAL(pServer->connections[i],
                    target_ip, target_port))
        {
            pServer->index = i;
            break;
        }
    }
}

void fdfs_set_server_info(TrackerServerInfo *pServer,
        const char *ip_addr, const int port)
{
    pServer->count = 1;
    pServer->index = 0;
    conn_pool_set_server_info(pServer->connections + 0, ip_addr, port);
}

void fdfs_set_server_info_ex(TrackerServerInfo *pServer,
        const FDFSMultiIP *ip_addrs, const int port)
{
    int i;

    pServer->count = ip_addrs->count;
    pServer->index = 0;
    for (i=0; i<ip_addrs->count; i++)
    {
        conn_pool_set_server_info(pServer->connections + i,
                ip_addrs->ips[i].address, port);
    }
}

