///////////////////////////////////////////////////////////////////////////////
//	dk_inv.c
//
//	inventory management routines
///////////////////////////////////////////////////////////////////////////////

#include "server.h"
#include "qcommon.h"
#include "dk_inv.h"

// inventory subsystem

///////////////////////////////////////////////////////////////////////////////
//	globals
///////////////////////////////////////////////////////////////////////////////

static	int					serverInventoryPrecacheCount;
static	int					serverInventoryCount;
static	serverInventory_t	serverInventory[MAX_EDICTS];

static	int					inventoryUpdate = 0;
static	int					clientItem = -1;

clientInventory_t clientInventory [MAX_INVENTORY];

///////////////////////////////////////////////////////////////////////////////
//	precache
///////////////////////////////////////////////////////////////////////////////

static	serverInventoryPrecache_t serverInventoryPrecache[MAX_INVENTORY];

///////////////////////////////////////////////////////////////////////////////
//	dll precache interface code
///////////////////////////////////////////////////////////////////////////////

static	serverInventoryPrecache_t	*InventoryFindPrecache(char *name)
{
    int i;

	for (i = 0; i < serverInventoryPrecacheCount; i++)
	{
		if (!strcmp(serverInventoryPrecache[i].className, name))
		{
			return(&serverInventoryPrecache[i]);
		}
	}

	return(NULL);
}

///////////////////////////////////////////////////////////////////////////////
//	
///////////////////////////////////////////////////////////////////////////////

#if 0       // this will die
static	int	inv_ValidIcon(userIcon_t *icon)
{
	if (!icon)       return(1);
	if (!icon->data) return(0);

	// make sure icon size is in check
	if ((icon->width <= 0) || (icon->width > 50)) return(0);
	if ((icon->height <= 0) || (icon->width > 50)) return(0);

	return(1);
}

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

static	unsigned short	inv_IconCRC(userIcon_t *icon)
{
	unsigned short crc;
	int i;

	if (!icon)
		return	0;

	CRC_Init(&crc);

	// process data
	for (i = 0; i < (icon->width * icon->height * 3); i++)
	{
		CRC_ProcessByte(&crc, icon->data[i]);
	}

	return(crc);
}

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

static	void	inv_AddIcon(userIcon_t *icon)
{
	int i;

	if (!icon)
		return;

	for (i = 0; i < (icon->width * icon->height * 3); i++)
	{
		if (icon->data[i] == 0)
		{
			icon->data[i]++;
		}
	}
}
#endif

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

void User_InventoryPrecache (char *name, char *iconName, char *modelName)
{
	// make sure we are initing
	if (sv.state != ss_loading && sv.state != ss_dead)
	{
		Com_Printf("Host already inited, %s not precached", name);
		return;
	}

	if (serverInventoryPrecacheCount >= MAX_INVENTORY)
	{
		Com_Printf("No more inventory items left, increase MAX_INVENTORY\n");
		return;
	}

	serverInventoryPrecache [serverInventoryPrecacheCount].className = strdup(name);
	serverInventoryPrecache [serverInventoryPrecacheCount].iconName  = iconName;
//	serverInventoryPrecache [serverInventoryPrecacheCount].sum       = inv_IconCRC(icon);

	// weapons have to have a model to draw on the screen
	if (modelName)
	{
		// User_PrecacheModel(modelName);
		serverInventoryPrecache [serverInventoryPrecacheCount].modelName = strdup(modelName);
	}
	else
	{
		serverInventoryPrecache [serverInventoryPrecacheCount].modelName = NULL;
	}

	serverInventoryPrecacheCount++;
}

/////////////////////////////////////////////////////////////////////////////////////////////
/// dll interface code
/////////////////////////////////////////////////////////////////////////////////////////////


void *User_InventoryNew(void)
{
	int i;

	// see if there is a free one avail
	for (i = 0; i < serverInventoryCount; i++)
	{
		if (!serverInventory[i].in_use) break;
	}

	if (i == serverInventoryCount)
	{
	    if (serverInventoryCount >= MAX_EDICTS) 
		{
			Com_Printf("ERROR -- serverInventoryCount maxed out\n");
			return(NULL);
		}
	
		serverInventoryCount++;
	}

    // init this server inventory list
	serverInventory[i].in_use     = 1;
	serverInventory[i].invHead    = NULL;
	serverInventory[i].invTail    = NULL;

	return(&serverInventory[i]);
}



static void User_InventoryFree_r(serverInventoryList_t *p)
{
	if (p == NULL) return;

	User_InventoryFree_r(p->n);
	free(p);
}



void User_InventoryFree(void *ptr)
{
	serverInventory_t *inv = (serverInventory_t *) ptr;

	if (!ptr)
	{
		Com_Printf("User_InventoryFree: NULL pointer\n");
		return;
	}

	// clear out nodes
	User_InventoryFree_r(inv->invHead);

	inv->in_use  = 0;
	inv->invHead = NULL;
	inv->invTail = NULL;
}



userInventory_t *User_InventoryFindItem(void *ptr, char *name)
{
	serverInventory_t *inv = (serverInventory_t *) ptr;
	serverInventoryList_t *p;
	userInventory_t    *userInv;

	if (!ptr)
	{
		Com_Printf("User_InventoryFindItem: NULL pointer\n");
		return(NULL);
	}

	// go down clients inventory list looking for item
	for (p = inv->invHead; p != NULL; p = p->n)
	{
		// double check that name got set
		if (p->data)
		{
			userInv = (userInventory_t *) p->data;

			if (userInv->name)
			{
			    if (!Q_strcasecmp(userInv->name, name))
			    {
				    return(p->data);
				}
			}
			else
			{
				Com_Printf("WARNING -- Invalid inventory item.\n");
			}
		}
		else
		{
			Com_Printf("WARNING -- Invalid inventory item.\n");
		}
	}

	return(NULL);
}




userInventory_t *User_InventoryFirstItem(void *ptr)
{
	serverInventory_t *inv = (serverInventory_t *) ptr;

	if (!ptr)
	{
		Com_Printf("User_InventoryFirstItem: NULL pointer\n");
		return(NULL);
	}

	if (inv->invHead == NULL)
	{
		return(NULL);
	}

	// reset list walker pointer
	inv->invPtr = inv->invHead;

	return(inv->invPtr->data);
}



userInventory_t *User_InventoryNextItem(void *ptr)
{
	serverInventory_t *inv = (serverInventory_t *) ptr;

	if (!ptr)
	{
		Com_Printf("User_InventoryNextItem: NULL pointer\n");
		return(NULL);
	}

	// double check
	if (inv->invPtr == NULL) return(NULL);

	// advance
	inv->invPtr = inv->invPtr->n;

	if (inv->invPtr == NULL) return(NULL);
	return(inv->invPtr->data);
}



int User_InventoryAddItem(void *ptr, userInventory_t *item)
{
	serverInventory_t    *inv = (serverInventory_t *) ptr;
	serverInventoryList_t      *invNode;
	serverInventoryPrecache_t  *precache;


	if (!ptr)
	{
		Com_Printf("User_InventoryAddItem: NULL pointer\n");
		return(0);
	}

	if (!item->name)
	{
		Com_Printf("User_InventoryAddItem: Item must have a className\n");
		return(0);
	}

   precache = InventoryFindPrecache(item->name);

   if (!precache)
	{
		Com_Printf("User_InventoryAddItem: %s not precached\n", item->name);
		return(0);
	}

	if ((invNode = malloc(sizeof(serverInventoryList_t))) == NULL)
	{
		Com_Printf("User_InventoryAddItem: malloc failed, Unable to add item.\n");
		return(0);
	}

	memset(invNode, 0x00, sizeof(serverInventoryList_t));
	invNode->data     = item;
	invNode->precache = precache;

	item->precache = precache;

	// create a new inventory node
	// see if we need to make this item the head
	if ((inv->invHead == NULL) && (inv->invTail == NULL))
	{
		inv->invHead = inv->invTail = invNode;
		return(1);
	}

	// ok add item to tail of list
	inv->invTail->n = invNode;
	invNode->p      = inv->invTail;
	inv->invTail    = invNode;

	return(1);
}


// void User_InventoryDeleteItem(void *ptr, void *item)
void User_InventoryDeleteItem(void *ptr, userInventory_t *item)
{
	serverInventory_t *inv         = (serverInventory_t *) ptr;
	// serverInventoryList_t *invNode = (serverInventoryList_t *) item;
	serverInventoryList_t *invNode;

	if (!ptr)
	{
		Com_Printf("User_InventoryDeleteItem: NULL pointer\n");
		return;
	}

	// find item to delete
	for (invNode = inv->invHead; invNode; invNode = invNode->n)
	{
		if (invNode->data == item) break;
	}

	if (!invNode)
	{
		Com_Printf("User_InventoryDeleteItem: Unable to find item in inventory\n");
		return;
	}

	// see if this the only node left in the list
	if ((invNode == inv->invHead) && (invNode == inv->invTail))
	{
		free(invNode);

		inv->invHead = NULL;
		inv->invTail = NULL;

		return;
	}

	// see if we are deleteing from fron head
	if (invNode == inv->invHead)
	{
		inv->invHead    = invNode->n;
		inv->invHead->p = NULL;

		free(invNode);
		return;
	}

	// see if we are deleting from end
	if (invNode == inv->invTail)
	{
		inv->invTail    = invNode->p;
		inv->invTail->n = NULL;

		free(invNode);
		return;
	}

	// ok we are deleteing from middle
	invNode->p->n = invNode->n;
	invNode->n->p = invNode->p;

	free(invNode);
}



////////////////////////////////////////////////////////////////////////////////////////////////////
/// Server
////////////////////////////////////////////////////////////////////////////////////////////////////

#if 0       // this will die

// send down initial server inventory precache
void inv_SendInventoryStart(client_t *client)
{
	// tell client to request server inventory list
    MSG_WriteByte(&client->message, svc_invstart);
}


// client has requested inventory precache list
void inv_SendInventoryList(void)
{
	int i;

	MSG_WriteByte(&host_client->message, svc_invlist);

	for (i = 0; i < serverInventoryPrecacheCount; i++)
	{
		// make sure item has a className
		if (serverInventoryPrecache[i].className)
		{
		    MSG_WriteShort(&host_client->message, i);
		    MSG_WriteShort(&host_client->message, serverInventoryPrecache[i].sum);
            MSG_WriteString(&host_client->message, serverInventoryPrecache[i].className);
		}
		else
		{
			Com_Printf("Inventory precache with no className\n");
		}
	}
		

	// finish it up
	MSG_WriteShort(&host_client->message, (short) -1);
}

////////////////////////////////////////////////////////////////////////////////////////////////////
//	Server
////////////////////////////////////////////////////////////////////////////////////////////////////

void inv_SendIcon(void)
{
	int i;
	int x, y;
	userIcon_t *icon;
	int r,g,b;

	// get icon number to send
	i = MSG_ReadShort();

	if ((i < 0) || (i >= serverInventoryPrecacheCount))
	{
		Com_Printf("Client requested non existant icon\n");
		return;
	}

	icon = serverInventoryPrecache[i].icon;

	MSG_WriteByte(&host_client->message, svc_invicon);

	// send inventory key
	MSG_WriteShort(&host_client->message, i);

	// send with and height of icon
	MSG_WriteShort(&host_client->message, icon->width);
	MSG_WriteShort(&host_client->message, icon->height);

	// ok let's max out the bandwidth
	for (y = 0; y < icon->height; y++)
	{
	    for (x = 0; x < icon->width; x++)
		{
			// send rgb tuble
			// we are going to use 0 as the termination charter
			// so don't send it
			r = icon->data[(y * icon->width * 3) + (x * 3) + 0];
			g = icon->data[(y * icon->width * 3) + (x * 3) + 1];
			b = icon->data[(y * icon->width * 3) + (x * 3) + 2];

			// this should never happen now that we fix this in the precache
			if (r == 0) r++;
			if (g == 0) g++;
			if (b == 0) b++;

			// send data
			MSG_WriteByte(&host_client->message, r);
			MSG_WriteByte(&host_client->message, g);
			MSG_WriteByte(&host_client->message, b);
		}
	}

	// finish message
	MSG_WriteByte(&host_client->message, 0);
}


void inv_SelectItem(void)
{
	userEntity_t    *userent;
	userInventory_t *inv;
	char            *invName;
	int i;

	userent    = &host_client->sent->v;

	// get inventory item number
	i = MSG_ReadShort();

	if ((i < 0) || (i >= serverInventoryPrecacheCount))
	{
		Com_Printf("Client requested non existant item\n");
		return;
	}

	invName = serverInventoryPrecache[i].className;

	// find this item in user's inventory
	inv = User_InventoryFindItem(userent->inventory, invName);

	if (inv == NULL)
	{
		User_sprint(userent, "You don't have %s\n", invName);
		return;
	}

	// set user's current item to this item
	userent->curWeapon = inv;
	userent->curItem   = inv;
	
	User_sprint(userent, "You selected %s\n", invName);
}



////////////////////////////////////////////////////////////////////////////////////////////////////
//// Client side code
////////////////////////////////////////////////////////////////////////////////////////////////////

static void inv_SaveIcon(clientInventory_t *item)
{
	int i;
	char basedir[100];
	char path[200];
	FILE *f;

	// make sure we have a className
	if (!item->className)   return;
	if (!item->icon_data24) return;

	i = COM_CheckParm ("-basedir");
    if (i && i < com_argc-1)
        strcpy (basedir, com_argv[i+1]);
    else
        strcpy (basedir, host_parms.basedir);

	sprintf(path, "%s/data/cache", basedir);
	_mkdir(path);

	sprintf(path, "%s/data/cache/%s.lmp", basedir, item->className);

	if ((f = fopen(path, "wb")) == NULL)
	{
		Com_Printf("inv_SaveIcon: Unable to open %s\n", path);
		return;
	}

	// write width and height of file
	fwrite((unsigned char *) &item->icon_width, 1, sizeof(int), f);
	fwrite((unsigned char *) &item->icon_height, 1, sizeof(int), f);
	fwrite((unsigned char *) &item->icon_crc, 1, sizeof(unsigned short), f);

	// write data
	fwrite(item->icon_data24, 1, item->icon_width * item->icon_height * 3, f);
	fclose(f);
}



static int inv_FileLength(FILE *f)
{
    int        pos;
    int        end;

    pos = ftell (f);
    fseek (f, 0, SEEK_END);
    end = ftell (f);
    fseek (f, pos, SEEK_SET);

    return end;
}


static void inv_LoadIcons(void)
{
	int i, j, len;
	int width, height;
    char basedir[100];
	char path[200];
	FILE *f;
	unsigned char temp[8000];
	clientInventory_t *item;
	unsigned short crc;
	unsigned short tcrc;

    i = COM_CheckParm ("-basedir");
    if (i && i < com_argc-1)
        strcpy (basedir, com_argv[i+1]);
    else
        strcpy (basedir, host_parms.basedir);

	for (i = 0; i < MAX_INVENTORY; i++)
	{
		item = &clientInventory[i];

		if (!item->in_use)     continue;
        if (!item->className)  continue;
		if (item->icon_data24) continue;

		// ok see if we have a local copy of this icon
		sprintf(path, "%s/data/cache/%s.lmp", basedir, item->className);

		if ((f = fopen(path, "rb")) == NULL) continue;

		// get width and height
		fread((unsigned char *) &width, 1, sizeof(int), f);
		fread((unsigned char *) &height, 1, sizeof(int), f);
		fread((unsigned char *) &tcrc, 1, sizeof(unsigned short), f);

		len = width * height * 3;

		// ok we found the file, read it in
		fread(temp, 1, len, f);
		fclose(f);

		// ok get crc of file
		CRC_Init(&crc);

		for (j = 0; j < len; j++)
		{
		    CRC_ProcessByte(&crc, temp[j]);
		}

		// if crc is not same don't save
		if (crc != item->icon_crc)
		{
			Com_Printf("Unable to load %s, bad crc\n", item->className);
			continue;
		}

		// ok we got a good icon
		item->icon_width  = width;
        item->icon_height = height;

	    item->icon_data24 = malloc(width * height * 3);
	    item->icon_data8  = malloc(width * height);

	    memcpy(item->icon_data24, temp, width * height * 3);

		Com_DPrintf("Cached %s from disk\n", item->className);
	}
}



void inv_CheckIcons(void)
{
	int i;

	// ok now go through inventory list and find first item that
	// needs it's icon downloaded
	for (i = 0; i < MAX_INVENTORY; i++)
	{
		if (clientInventory[i].in_use && clientInventory[i].icon_data24)
		{
			clientItem = i;
		}

		if (clientInventory[i].in_use && !clientInventory[i].icon_data24)
		{
			// we need to update icon, so send command to server to
			// send it to us
			MSG_WriteByte(&cls.message, clc_inviconreq);
			MSG_WriteShort(&cls.message, i);

			Com_DPrintf("Updating icon %s\n", clientInventory[i].className);

			// we will update rest of icons as soon as we get this on
			return;;
		}
	}

	inventoryUpdate = 1;
}



// server is sending down inventory list
void inv_ReceiveInventoryList(void)
{
	unsigned short sum;
	int i;
	char *name;

	// reset list
	memset(&clientInventory, 0x00, sizeof(clientInventory));

	for (;;)
	{
		// get inventory item's id
		if ((i = MSG_ReadShort()) < 0) break;

		sum  = MSG_ReadShort();
		name = MSG_ReadString();

		// make sure id is in range
		if (i >= MAX_INVENTORY)
		{
			Com_Printf("WARNING -- Inventory item out of range.\n");
			continue;
		}

		clientInventory[i].in_use    = 1;
		clientInventory[i].className = strdup(name);

		// icon
		clientInventory[i].icon_crc  = sum;
	}

	// load in as many icons from cached directory as possible
	inv_LoadIcons();
	// inv_CheckIcons();
}



// server is sending us an icon
void inv_ReceiveIcon(void)
{
	int count = 0;
	int i, width, height;
	char  data[8000];
	unsigned short crc;
	byte c;
	
	// get inventory key
    i = MSG_ReadShort();

	// get width and weight of icon
	width  = MSG_ReadShort();
	height = MSG_ReadShort();

	// see if icon is too big
	if ((width * height * 3) > sizeof(data))
	{
		Com_Printf("inv_ReceiveIcon: icon too big\n");

		// read in from buffer
		while (MSG_ReadByte() != 0);
		return;
	}

	CRC_Init(&crc);

	// get icon
	for (;;)
	{
		if ((c = MSG_ReadByte()) == 0) break;

		data[count++] = c;
		CRC_ProcessByte(&crc, c);
	}

	// make sure we got a valid index
	if ((i < 0 ) || (i >= MAX_INVENTORY))
	{
		Com_Printf("Invalid icon index\n");
		return;
	}

	// Com_Printf("got: %x, crc: %x\n", crc, clientInventory[i].icon_crc);

	if (count != (width * height * 3))
	{
		Com_Printf("HMM? cont != width * height * 3\n");
		count = width * height * 3;
	}

	// update icon
    clientInventory[i].icon_width  = width;
    clientInventory[i].icon_height = height;

	clientInventory[i].icon_data24 = malloc(width * height * 3);
	clientInventory[i].icon_data8  = malloc(width * height);

	memcpy(clientInventory[i].icon_data24, data, width * height * 3);

	// now that we have this icon, we need to save it for next time
	// we need it
	inv_SaveIcon(&clientInventory[i]);

	// see there are more icons to download
	inv_CheckIcons();
}


void inv_InventoryStart(void)
{
	// tell server to send us inventory list
	MSG_WriteByte (&cls.message, clc_invlistreq);

	Com_Printf("Sent inventory request\n");
}


////////////////////////////////////////////////////////////////////////////////////////
/// RGB to Palette Routines
///////////////////////////////////////////////////////////////////////////////////////


static int inv_FindRGB(float r, float g, float b)
{
	int i, ret = 1;
	float  dist = 9999999.0;
	float  potDist;
	float  pr, pg, pb;

	for (i = 1; i < 255; i++)
	{
		pr = host_basepal[(i * 3) + 0];
		pg = host_basepal[(i * 3) + 1];
		pb = host_basepal[(i * 3) + 2];

		potDist = ( ((pr-r)*(pr-r)) + ((pg-g)*(pg-g)) + ((pb-b)*(pb-b)) );

		if (potDist < dist)
		{
			dist = potDist;
			ret = i;
		}
	}

	return(ret);
}


static void inv_RGBPalette(clientInventory_t *item)
{
	int r, g, b;
	int k;
	int x, y;

	// see if we need to even convert at all
	if (item->lastPalUpdate == lastPaletteUpdate)
	{
		return;
	}

    if (!item->icon_data24 || !item->icon_data8) 
	{
		return;
	}

	for (y = 0; y < item->icon_height; y++)
	{
		for (x = 0; x < item->icon_width; x++)
		{
			r = item->icon_data24[(y * item->icon_width * 3) + (x * 3) + 0];
			g = item->icon_data24[(y * item->icon_width * 3) + (x * 3) + 1];
			b = item->icon_data24[(y * item->icon_width * 3) + (x * 3) + 2];

			if ((r == 159) && (g == 91) && (b == 83))
			{
				k = 255;
			}
			else
			{
			    // find closest value in current palette to this value
			    k = inv_FindRGB((float) r, (float) g, (float) b);
			}

			item->icon_data8[(y * item->icon_width) + x] = (unsigned char) k;
		}
	}

	item->lastPalUpdate = lastPaletteUpdate;
}



//////////////////////////////////////////////////////////////////////////////////////////
/// Client inventory drawing routines
//////////////////////////////////////////////////////////////////////////////////////////

static int inv_entersound;


static void InventoryQuery_f(void)
{
	// make sure user is connected to server
    if (cls.state != ca_connected)
    {
        Com_Printf("You must first connect to a server.\n");
        return;
	}

	if (!inventoryUpdate)
	{
		Com_Printf("Wait inventory is updating\n");
		return;
	}

	key_dest       = key_inv;
	inv_entersound = true;

}



void Inventory_Init(void)
{
	Cmd_AddCommand("inventory", InventoryQuery_f);
}




void inv_Keydown(int key)
{
	int oldItem;

	switch (key)
	{
	case K_ESCAPE:
		Com_Printf("exiting inventory system\n");
		key_dest = key_game;
		break;

	case K_LEFTARROW:
		// S_LocalSound ("misc/menu1.wav");
		oldItem = clientItem;
		clientItem--;

		while (clientItem != oldItem)
		{
			// see we we need to loop around
		    if (clientItem < 0)
		    {
			    clientItem = MAX_INVENTORY;
		    }

			if (clientInventory[clientItem].in_use) break;
			else clientItem--;
		}

		break;

	case K_RIGHTARROW:
		// S_LocalSound ("misc/menu1.wav");

		oldItem = clientItem;
		clientItem++;

		while (clientItem != oldItem)
		{
			// see if we need to loop around
			if (clientItem >= MAX_INVENTORY)
			{
				clientItem = 0;
			}

			if (clientInventory[clientItem].in_use) break;
			else clientItem++;
		}

		break;

	case K_ENTER:
		// S_LoaclSound("misc/menu1.wav");

		// send server a message selecting this item
		MSG_WriteByte(&cls.message, clc_inviconsel);
		MSG_WriteShort(&cls.message, clientItem);

		key_dest = key_game;
		break;

	}
}



void inv_Draw(void)
{
	qpic2_t temp;
	int i;
	clientInventory_t *item;

	if (key_dest != key_inv) return;
	if (clientItem < 0)      return;

	item = &clientInventory[clientItem];

	// ok convert item's 24 bit image to 8 bit image
	inv_RGBPalette(item);

	temp.width  = item->icon_width;
	temp.height = item->icon_height;
	temp.data   = item->icon_data8;

	Draw_TransPic2(10, 10, &temp);
	// Draw_Pic2(10, 10, &temp);
}

#endif