#include "allegro.h"

#ifdef _WIN32
#include "winalleg.h"

#include <winsock.h>
#else
#define SOCKET_ERROR -1
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#endif
#include <png.h>

#ifndef L_SET
#define L_SET SEEK_SET
#endif

#include "defs.h"
#include "console.h"
#include "engine.h"
#include "gfx.h"
#include "init.h"
#include "items.h"
#include "level.h"
#include "sound.h"
#include "weapons.h"
#include "ai.h"
#include "maths.h"
#include "math.h"

void renderLights()
{
   int x, y;

   unsigned int light[2*10];
   unsigned int lDist[10];
   unsigned int lBright[10];

   char numLights = get_config_int("Map", "NumLights", 0);

   if (numLights > 10)
   {
      Console.Write("Error: trying to create more than 10 lights...");
      numLights = 10;
   }

   for (int i = 0; i < numLights; i++)
   {
      sprintf(buf, "Light.%d", i);
      light[2*i] = get_config_int(buf, "PosX", 0);
      light[2*i+1] = get_config_int(buf, "PosY", 0);
      lDist[i] = get_config_int(buf, "Dist", 0);
      lBright[i] = get_config_int(buf, "Brightness", 255);
   }

   if (numLights == 0) return;

   Console.Write("Rendering level lights...");

   lights = create_bitmap(level->w, level->h);
    
   for (y = 0; y < level->h; y++)
   for (x = 0; x < level->w; x++)
   {
      int color = 0;
      for (int i = 0; i < numLights; i++)
      {
         traceLight(light[i*2], light[i*2+1], x, y);
         if (trace || ((traceX == x) && (traceY == y)))
         {
	    int dist = 1;
	    int ratio = 255;
	    if (lDist[i])
	    {
	       dist = INT(hypot(abs(light[i*2] - x), abs(light[i*2+1] - y)));
	       ratio = (255 * (lDist[i] - dist)) / (lDist[i] ? lDist[i] : 1);
	    }
	    int brightness = (lBright[i] * (dist > lDist[i] ? 0 : ratio)) / 255;
            color = min(255, color + brightness);
         }
      }
      putpixel(lights, x, y, makecol32(color, color, color));
   }
}

int loadWurmzLevel(char *name)
{
   int i;
   int numItems;
   interact *tmpItem;

   sprintf(buf, "lev/%s/level.cfg", name);
   override_config_file(buf);

   pScroll = get_config_int("Map", "PScroll", 0);

   sprintf(buf, "lev/%s/%s", name, get_config_string("Map", "Back", "back.bmp"));

   background = load_bitmap(buf, backpal);
   if (!background)
   {
      return 1;
   }

   if (pScroll)
   {
      sprintf(buf, "lev/%s/%s", name, get_config_string("Map", "Parallax", ""));

      parallax = load_bitmap(buf, backpal);
   }

   sprintf(buf, "lev/%s/%s", name, get_config_string("Map", "Level", "level.bmp"));

   level = load_bitmap(buf, backpal);
   if (!level)
   {
      destroy_bitmap(background);
      return 1;
   }

   sprintf(buf, "lev/%s/%s", name, get_config_string("Map", "Material", "material.bmp"));

   material = load_bitmap(buf, backpal);
   if (!material)
   {
      destroy_bitmap(level);
      destroy_bitmap(background);
      return 1;
   }

   sprintf(buf, "lev/%s/%s", name, get_config_string("Map", "Music", "music.mp3"));
   Sound.startMusic(buf);

   GP_JUMPER_FORCE = get_config_int("Physics", "JumperForce", 1000);
   GP_KICKER_FORCE = get_config_int("Physics", "KickerForce", 1000);

   respawnNumPoints = get_config_int("Map", "NumStartPoints", 1);

   Weather.numParticles = get_config_int("Weather", "Particles", 0);
   Weather.effectStart(get_config_int("Weather", "WeatherFX", 0));

   Engine.hurtMaterialAmount = get_config_int("Physics", "HurtAmount", 1);
   Engine.hurtMaterialFrequency = get_config_int("Physics", "HurtFrequency", 1);

   for (i=0; i<respawnNumPoints; i++)
   {
      sprintf(buf, "StartX%d", i);
      respawnPoints[i*2] = get_config_int("Map", buf, 10);
      sprintf(buf, "StartY%d", i);
      respawnPoints[i*2+1] = get_config_int("Map", buf, 10);
   }

   numItems = get_config_int("Map", "NumItems", 0);

   for (i=0; i<numItems; i++)
   {
      sprintf(buf, "Item.%d", i);
      if ((get_config_int(buf, "Type", 0) != ITEM_MEDKIT) || (!Engine.crateDrops))
      {
         tmpItem = new interact;
         tmpItem->localTime = 0;
         tmpItem->next = itemList;
         if (itemList)
            itemList->prev = tmpItem;
         tmpItem->prev = NULL;
         itemList = tmpItem;
         itemList->spawnX = get_config_int(buf, "PosX", 10);
         itemList->spawnY = get_config_int(buf, "PosY", 10);
         itemList->type = get_config_int(buf, "Type", 0);
         itemList->amount = get_config_int(buf, "Amount", 1);
         itemList->respawn = get_config_int(buf, "Respawn", 600);
         itemList->bounce = get_config_int(buf, "Bounce", 500);
         itemList->gravity = get_config_int(buf, "Gravity", GP_ITEM_GRAVITY);
         itemList->detectDist = get_config_int(buf, "DetectDist", 0);
         sprintf(itemList->name, get_config_string(buf, "Name", ""));
         itemList->start();
      }
   }
   Console.WriteF("Loaded %d map items...", numItems);
   mapSizeX = level->w;
   mapSizeY = level->h;
   renderLights();
   return 0;
}

int loadLieroLevel(char *name)
{
   unsigned char materialData[256];
   int paletteData[256];
   bool hasPalette;
   
   unsigned char palEntry[3];

   unsigned char lineBuffer[504];

   unsigned char backColors[256];
   int numBackColors = 0;

   pScroll = 0;
   
   Engine.hurtMaterialFrequency = 1;

   Console.WriteF("Loading materials...");

   FILE *materialFile = fopen("data/material.dat", "rb");
   fread(materialData, 256, 1, materialFile);
   fclose(materialFile);

   for (int i = 0; i < 256; i++)
   {
      if (materialData[i] == '4')
      {
         backColors[numBackColors] = i;
         numBackColors++;
      }
   }

   sprintf(buf, "lev/%s", name);

   if (!exists(buf))
   {
      Console.WriteF("File not found...");
      return 1;
   }

   hasPalette = (file_size(buf) >= 177178);

   Console.WriteF("Opening level file...");
   FILE *levelFile = fopen(buf, "rb");

   if (hasPalette)
   {
      fseek(levelFile, 176410, L_SET);
      for (int i = 0; i < 256; i++)
      {
         fread(palEntry, 3, 1, levelFile);
         paletteData[i] = makecol(palEntry[0] << 2, palEntry[1] << 2, palEntry[2] << 2);
         Console.WriteF("Kolor %d: %d, %d, %d", i, 4*palEntry[0], 4*palEntry[1], 4*palEntry[2]);
      }
      fseek(levelFile, 0, L_SET);
   }
   else
   {
      Console.WriteF("Loading palette...");
      FILE *paletteFile = fopen("data/palette.lpl", "rb");
      for (int i = 0; i < 256; i++)
      {
         fread(palEntry, 3, 1, paletteFile);
         paletteData[i] = makecol(palEntry[0], palEntry[1], palEntry[2]);
      }
      fclose(paletteFile);
   }

   background = create_bitmap(504, 350);
   material = create_bitmap(504, 350);
   level = create_bitmap(504, 350);

   Console.WriteF("Parsing level data...");

   for (int i = 0; i < 350; i++)
   {
      fread(lineBuffer, 504, 1, levelFile);
      for (int j = 0; j < 504; j++)
      {
         unsigned char c = materialData[lineBuffer[j]];
         if (i >= 348) c='1';
         int col = paletteData[lineBuffer[j]];
         int randBck = backColors[rand()%numBackColors];
         int bck = paletteData[randBck];
         switch(c)
         {
            case '0':
               putpixel(level, j, i, col);
               putpixel(material, j, i, MAT_BLOCK_WEAP | MAT_BLOCK_ROPE | MAT_BLOCK_WORM | MAT_DESTROYABLE);
               putpixel(background, j, i, bck);
               break;
            case '1':
               putpixel(level, j, i, col);
               putpixel(material, j, i, MAT_BLOCK_WEAP | MAT_BLOCK_ROPE | MAT_BLOCK_WORM);
               putpixel(background, j, i, bck);
               break;
            case '2':
               putpixel(level, j, i, col);
               putpixel(material, j, i, MAT_BLOCK_WORM);
               putpixel(background, j, i, bck);
               break;
            case '3':
               putpixel(level, j, i, col);
               putpixel(material, j, i, MAT_BLOCK_WEAP | MAT_BLOCK_ROPE | MAT_DESTROYABLE);
               putpixel(background, j, i, bck);
               break;
            case '4':
            case '5':
               putpixel(level, j, i, GC_PINK);
               putpixel(material, j, i, 0);
               putpixel(background, j, i, col);
               break;
            default:
               Console.WriteF("Unknown material type: %c", c);
         }
      }
   }

   fclose(levelFile);

   Engine.wormRandomSpawn = 1;

   return 0;
}

int initLevel(char *name)
{
   int i;
   interact *tmpItem;
   
   freeItems();

   if (((strstr(name, ".lev") == NULL) && (strstr(name, ".LEV") == NULL)) || (loadLieroLevel(name)))
   if (loadWurmzLevel(name))
   {
      return 1;
   }
  
   if (Engine.crateDrops)
   {
   for (i=0; i<Engine.crateHealthCount; i++)
   {
      tmpItem = new interact;
      tmpItem->localTime = 0;
      tmpItem->next = itemList;
      if (itemList)
         itemList->prev = tmpItem;
      tmpItem->prev = NULL;
      itemList = tmpItem;
      itemList->spawnX = 10+abs(RANDOM())%(mapSizeX-20);
      itemList->spawnY = 5+abs(RANDOM())%(mapSizeY-20);
      itemList->amount = 0;
      itemList->type = ITEM_MEDKIT;
      itemList->respawn = 600;
      itemList->bounce = 500;
      itemList->gravity = GP_ITEM_GRAVITY;
      itemList->detectDist = 0;
      itemList->start();
      itemList->owner=&worms[0];
      itemList->localTime=abs(RANDOM());
   }
   for (i=0; i<Engine.crateWeapCount; i++)
   {
      tmpItem = new interact;
      tmpItem->localTime = 0;
      tmpItem->next = itemList;
      if (itemList)
         itemList->prev = tmpItem;
      tmpItem->prev = NULL;
      itemList = tmpItem;
      itemList->spawnX = 10+abs(RANDOM())%(mapSizeX-20);
      itemList->spawnY = 5+abs(RANDOM())%(mapSizeY-20);
      itemList->amount = 0;
      itemList->type = ITEM_WEAPPACK;
      itemList->respawn = 600;
      itemList->bounce = 500;
      itemList->gravity = GP_ITEM_GRAVITY;
      itemList->detectDist = 0;
      itemList->start();
      itemList->owner=&worms[0];
      itemList->localTime=abs(RANDOM());
   }
   }
   return 0;
}

void freeLevel() {
   if (background) destroy_bitmap(background);
   if (level) destroy_bitmap(level);
   if (material) destroy_bitmap(material);
   if (lights) destroy_bitmap(lights);
   if (parallax) destroy_bitmap(parallax);
   Weather.effectStop();
}


