/*

   MAP.C

   Oliver Kraus
   kraus@lrs.e-technik.uni-erlangen.de

*/

#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "map.h"
#include "names.h"

#define MAP_NONE              0
#define MAP_ASSAULT_TROOPER   1
#define MAP_ASSAULT_CAPTAIN   2
#define MAP_PIC_COP           3
#define MAP_RECON_PATROL      4
#define MAP_OCTABRAIN         5
#define MAP_ENFORCER          6
#define MAP_SENTRY_DRONE      7
#define MAP_ASSAULT_COMMANDER 8
#define MAP_PROTOZOID_SLIMER  9
#define MAP_BATTLELORD       10
#define MAP_OVERLORD         11
#define MAP_CYCLOID_EMPEROR  12
#define MAP_TURRET           13
#define MAP_EGG              14
#define MAP_ROTATE_GUN       15        /* same as MAP_TURRET? */

#define MAP_BLUE_CARD       100
#define MAP_RED_CARD        101
#define MAP_YELLOW_CARD     102

#define MAP_EXIT            200
#define MAP_BONUS_EXIT      201

struct _map_sprite_to_item
{
   short picnum;
   short palette;
   short item;
};
typedef struct _map_sprite_to_item map_sprite_to_item;

struct _map_item_info_struct
{
   short typ;
   short item;
   long cnt;
   char *id;
   char *name;
   char *altname;
};
typedef struct _map_item_info_struct map_item_info_struct;

map_item_info_struct map_item_info[] =
{
   { 0              , MAP_NONE              , 0L, "??", "unknown"              , NULL          },
   { MAP_TYP_MONSTER, MAP_ASSAULT_CAPTAIN   , 0L, "AC", "Assault Captain"      , "Lizman"      },
   { MAP_TYP_MONSTER, MAP_ASSAULT_COMMANDER , 0L, "AM", "Assault Commander"    , NULL          },
   { MAP_TYP_MONSTER, MAP_ASSAULT_TROOPER   , 0L, "AT", "Assault Trooper"      , "Liztroop"    },
   { MAP_TYP_MONSTER, MAP_BATTLELORD        , 0L, "BL", "Battlelord"           , "Boss 1"      },
   { MAP_TYP_MONSTER, MAP_CYCLOID_EMPEROR   , 0L, "CE", "Cycloid Emperor"      , "Boss 3"      },
   { MAP_TYP_MONSTER, MAP_EGG               , 0L, "EG", "Egg"                  , NULL          },
   { MAP_TYP_MONSTER, MAP_ENFORCER          , 0L, "EN", "Enforcer"             , "Mini Boss 1" },
   { MAP_TYP_MONSTER, MAP_OCTABRAIN         , 0L, "OB", "Octabrain"            , NULL          },
   { MAP_TYP_MONSTER, MAP_OVERLORD          , 0L, "OL", "Overlord"             , "Boss 2"      },
   { MAP_TYP_MONSTER, MAP_PIC_COP           , 0L, "PC", "Pic Cop"              , NULL          },
   { MAP_TYP_MONSTER, MAP_PROTOZOID_SLIMER  , 0L, "PS", "Protozoid Slimer"     , NULL          },
   { MAP_TYP_MONSTER, MAP_ROTATE_GUN        , 0L, "RG", "Rotate Gun"           , NULL          },
   { MAP_TYP_MONSTER, MAP_RECON_PATROL      , 0L, "RP", "Recon Patrol Car"     , NULL          },
   { MAP_TYP_MONSTER, MAP_SENTRY_DRONE      , 0L, "SD", "Sentry Drone"         , NULL          },
   { MAP_TYP_MONSTER, MAP_TURRET            , 0L, "TU", "Turret"               , NULL          },

   { MAP_TYP_CARD,    MAP_BLUE_CARD         , 0L, "BC", "Blue Card"            , NULL          },
   { MAP_TYP_CARD,    MAP_RED_CARD          , 0L, "RC", "Red Card"             , NULL          },
   { MAP_TYP_CARD,    MAP_YELLOW_CARD       , 0L, "YC", "Yellow Card"          , NULL          },

   { -1             , MAP_NONE              , 0L, NULL, NULL                  }
};

char *map_get_item_id(int idx)
{
   if ( idx < 0 )
      return "";
   return map_item_info[idx].id;
}


char *map_get_item_string(int idx)
{
   if ( idx < 0 )
      return "";
   return map_item_info[idx].name;
}

int map_get_item_typ(int idx)
{
   if ( idx < 0 )
      return 0;
   return (int)map_item_info[idx].typ;
}

int map_get_item_cnt(int idx)
{
   if ( idx < 0 )
      return 0;
   return (int)map_item_info[idx].cnt;
}


map_sprite_to_item map_to_item[] =
{
   { LIZTROOP          , -1,  MAP_ASSAULT_TROOPER    },
   { LIZTROOPRUNNING   , -1,  MAP_ASSAULT_TROOPER    },
   { LIZTROOPSTAYPUT   , -1,  MAP_ASSAULT_TROOPER    },
   { LIZTOP            , -1,  MAP_ASSAULT_TROOPER    },
   { LIZTROOPSHOOT     , -1,  MAP_ASSAULT_TROOPER    },
   { LIZTROOPJETPACK   , -1,  MAP_ASSAULT_TROOPER    },
   { LIZTROOPDSPRITE   , -1,  MAP_ASSAULT_TROOPER    },
   { LIZTROOPONTOILET  , -1,  MAP_ASSAULT_TROOPER    },
   { LIZTROOPDUCKING   , -1,  MAP_ASSAULT_TROOPER    },

   { OCTABRAIN         , -1,  MAP_OCTABRAIN          },
   { OCTABRAINSTAYPUT  , -1,  MAP_OCTABRAIN          },
   { OCTATOP           , -1,  MAP_OCTABRAIN          },

   { DRONE             , -1,  MAP_SENTRY_DRONE       },

   { COMMANDER         , -1,  MAP_ASSAULT_COMMANDER  },
   { COMMANDERSTAYPUT  , -1,  MAP_ASSAULT_COMMANDER  },

   { RECON             , -1,  MAP_RECON_PATROL       },

   { PIGCOP            , -1,  MAP_PIC_COP            },
   { PIGCOPSTAYPUT     , -1,  MAP_PIC_COP            },
   { PIGCOPDIVE        , -1,  MAP_PIC_COP            },
   { PIGTOP            , -1,  MAP_PIC_COP            },

   { LIZMAN            , -1,  MAP_ASSAULT_CAPTAIN    },
   { LIZMANSTAYPUT     , -1,  MAP_ASSAULT_CAPTAIN    },
   { LIZMANSPITTING    , -1,  MAP_ASSAULT_CAPTAIN    },
   { LIZMANFEEDING     , -1,  MAP_ASSAULT_CAPTAIN    },
   { LIZMANJUMP        , -1,  MAP_ASSAULT_CAPTAIN    },

   { BOSS1             , -1,  MAP_BATTLELORD         },
   { BOSS1STAYPUT      , -1,  MAP_BATTLELORD         },
   { BOSS1SHOOT        , -1,  MAP_BATTLELORD         },
   { BOSS1LOB          , -1,  MAP_BATTLELORD         },
   { BOSSTOP           , -1,  MAP_BATTLELORD         },

   { BOSS3             , -1,  MAP_OVERLORD           },   /* strange BOSS2/BOSS3 */

   { BOSS2             , -1,  MAP_CYCLOID_EMPEROR    },

   { EGG               , -1,  MAP_EGG                },

   { ROTATEGUN         , -1,  MAP_ROTATE_GUN         },

   { ACCESSCARD        ,  0,  MAP_BLUE_CARD          },
   { ACCESSCARD        , 21,  MAP_RED_CARD           },
   { ACCESSCARD        , 23,  MAP_YELLOW_CARD        },

   { -1, -1, -1}
};

int map_get_info_idx(short picnum, short palette)
{
   int i, j;
   i = 0;
   while ( map_to_item[i].item >= 0 )
   {
      if ( map_to_item[i].picnum == picnum )
      {
         if ( map_to_item[i].palette < 0 )
            break;
         if ( map_to_item[i].palette == palette )
            break;
      }
      i++;
   }
   if ( map_to_item[i].item < 0 )
      return -1;
   j = 0;
   while ( map_item_info[j].typ >= 0  )
   {
      if ( map_item_info[j].item == map_to_item[i].item )
         break;
      j++;
   }
   if ( map_item_info[j].typ < 0  )
      return -1;
   return j;
}

int is_map_file(char *name)
{
   long lval, pos;
   short sval;
   map_player_struct pl;
   FILE *fp;

   pos = 0L;
   fp = fopen(name, "rb");
   if ( fp == NULL )
      return 0;

   if ( fread(&lval, sizeof(long), 1, fp) != 1 )
   {
      fclose(fp);
      return 0;
   }
   pos += sizeof(long);

   /* check version number */
   if ( lval < 7 )
   {
      fclose(fp);
      return 0;
   }
   if ( lval > 100 )
   {
      fclose(fp);
      return 0;
   }


   if ( fread(&pl, sizeof(map_player_struct), 1, fp) != 1 )
   {
      fclose(fp);
      return 0;
   }
   if ( pl.ang >= 2048 )
   {
      fclose(fp);
      return 0;
   }
   pos += sizeof(map_player_struct);

   if ( fread(&sval, sizeof(short), 1, fp) != 1 )
   {
      fclose(fp);
      return 0;
   }
   if ( sval < 0 )
   {
      fclose(fp);
      return 0;
   }
   pos += (long)sizeof(short);
   pos += (long)sizeof(map_sector_struct)*(long)sval;
   if ( fseek(fp, pos, SEEK_SET) != 0 )
   {
      fclose(fp);
      return 0;
   }
   if ( ftell(fp) != pos )
   {
      fclose(fp);
      return 0;
   }

   if ( fread(&sval, sizeof(short), 1, fp) != 1 )
   {
      fclose(fp);
      return 0;
   }
   if ( sval < 0 )
   {
      fclose(fp);
      return 0;
   }
   pos += (long)sizeof(short);
   pos += (long)sizeof(map_wall_struct)*(long)sval;
   if ( fseek(fp, pos, SEEK_SET) != 0 )
   {
      fclose(fp);
      return 0;
   }
   if ( ftell(fp) != pos )
   {
      fclose(fp);
      return 0;
   }

   if ( fread(&sval, sizeof(short), 1, fp) != 1 )
   {
      fclose(fp);
      return 0;
   }
   if ( sval < 0 )
   {
      fclose(fp);
      return 0;
   }
   pos += (long)sizeof(short);
   pos += (long)sizeof(map_sprite_struct)*(long)sval;
   if ( fseek(fp, pos, SEEK_SET) != 0 )
   {
      fclose(fp);
      return 0;
   }
   if ( ftell(fp) != pos )
   {
      fclose(fp);
      return 0;
   }

   if ( fgetc(fp) != EOF )
   {
      fclose(fp);
      return 0;
   }

   fclose(fp);
   return 1;
}

void map_name_cut(char *s)
{
   size_t i = 0;
   while( s[i] != '\0' )
   {
      if ( s[i] == '.' )
      {
         s[i] = '\0';
         break;
      }
      i++;
   }

}

int map_name_split(char *dest, long *val, char *src)
{
   size_t len, i;
   len = strlen(src);
   i = len;
   while( i > 0 )
   {
      i--;
      if ( !isdigit(src[i]) )
      {
         i++;
         break;
      }
   }
   strncpy(dest, src, i);
   dest[i] = '\0';
   if ( i == len )
      return 0;
   *val = atol(src+i);
   return 1;
}

int map_name_compare(char *n1, char *n2)
{
   int ret, r1, r2;
   long v1, v2;
   static char s1[14];
   static char s2[14];
   static char t1[14];
   static char t2[14];

   strncpy(t1, n1, 12);
   strncpy(t2, n2, 12);
   t1[12] = '\0';
   t2[12] = '\0';
   map_name_cut(t1);
   map_name_cut(t2);

   r1 = map_name_split(s1, &v1, t1);
   r2 = map_name_split(s2, &v2, t2);

   if ( r1 != 0 && r2 != 0 )
   {
      ret = strcmp(s1, s2);
      if ( ret == 0 )
      {
         if ( v1 < v2 )
            ret = -1;
         else if ( v1 > v2 )
            ret = 1;
      }
   }
   else
   {
      ret = strcmp(t1, t2);
   }
   return ret;
}

/*
   if 'fp' is NULL, a filepointer is generated with
   fopen and fname
*/
map_type map_Open(char *fname, FILE *fp)
{
   map_type map;
   int is_close_fp = 0;

   if ( fname == NULL )
      return NULL;

   if ( fp == NULL )
   {
      fp = fopen(fname, "rb");
      if ( fp == NULL )
         return NULL;
      is_close_fp = 1;
   }

   map = (map_type)malloc(sizeof(map_struct));
   if ( map != NULL )
   {
      map->fname = (char *)malloc(strlen(fname)+1);
      if ( map->fname != NULL )
      {
         strcpy(map->fname, fname);
         map->is_close_fp = is_close_fp;
         map->fp = fp;
         map->sec_list = NULL;
         map->wall_list = NULL;
         map->sprite_list = NULL;
         map_SetMonster(map);
         map_SetCard(map);
         return map;
      }
      free(map);
   }

   if ( is_close_fp != 0 )
      fclose(fp);
   return NULL;
}

void map_Close(map_type map)
{
   if ( map != NULL )
   {
      if ( map->sec_list != NULL )
         free(map->sec_list);
      if ( map->wall_list != NULL )
         free(map->wall_list);
      if ( map->sprite_list != NULL )
         free(map->sprite_list);
      if ( map->is_close_fp != 0 )
         fclose(map->fp);
      free(map->fname);
      free(map);
   }
}

short map_GetShort(map_type map)
{
   short x;
   if ( fread(&x, sizeof(short), 1, map->fp) != 1 )
   {
      perror("read 16 Bit");
      return -1;
   }
   return x;
}

int map_read_version(map_type map)
{
   char *task = "read version";
   if ( fread(&(map->version), sizeof(long), 1, map->fp) != 1 )
   {
      perror(task);
      return 0;
   }
   return 1;
}


int map_read_player_pos(map_type map)
{
   char *task = "read player pos";
   if ( fread(&(map->player), sizeof(map_player_struct), 1, map->fp) != 1 )
   {
      perror(task);
      return 0;
   }
   return 1;
}

int map_read_sector_list(map_type map)
{
   char *task = "read sector list";
   map->sec_cnt = map_GetShort(map);
   if ( map->sec_cnt < 0 )
      return 0;
   if ( map->sec_list != NULL )
      free(map->sec_list);
   map->sec_list = (map_sector_struct *)
      malloc(sizeof(map_sector_struct)*(size_t)map->sec_cnt);
   if ( map->sec_list == NULL )
   {
      fprintf( map->fp, "%s: out of memory\n", task);
      return 0;
   }
   if ( fread((void *)(map->sec_list),
        sizeof(map_sector_struct),
        (size_t)map->sec_cnt, map->fp) != map->sec_cnt )
   {
      perror(task);
      return 0;
   }
   return 1;
}

int map_read_wall_list(map_type map)
{
   char *task = "read wall list";
   map->wall_cnt = map_GetShort(map);
   if ( map->wall_cnt < 0 )
      return 0;
   if ( map->wall_list != NULL )
      free(map->wall_list);
   map->wall_list = (map_wall_struct *)
      malloc(sizeof(map_wall_struct)*(size_t)map->wall_cnt);
   if ( map->wall_list == NULL )
   {
      fprintf( map->fp, "%s: out of memory\n", task);
      return 0;
   }
   if ( fread((void *)(map->wall_list),
        sizeof(map_wall_struct),
        (size_t)map->wall_cnt, map->fp) != map->wall_cnt )
   {
      perror(task);
      return 0;
   }
   return 1;
}

int map_read_sprite_list(map_type map)
{
   char *task = "read sprite list";
   map->sprite_cnt = map_GetShort(map);
   if ( map->sprite_cnt < 0 )
      return 0;
   if ( map->sprite_list != NULL )
      free(map->sprite_list);
   map->sprite_list = (map_sprite_struct *)
      malloc(sizeof(map_sprite_struct)*(size_t)map->sprite_cnt);
   if ( map->sprite_list == NULL )
   {
      fprintf( map->fp, "%s: out of memory\n", task);
      return 0;
   }
   if ( fread((void *)(map->sprite_list),
        sizeof(map_sprite_struct),
        (size_t)map->sprite_cnt, map->fp) != map->sprite_cnt )
   {
      perror(task);
      return 0;
   }
   return 1;
}

int map_Read(map_type map, long pos)
{
   if ( fseek(map->fp, pos, SEEK_SET) != 0 )
   {
      perror("file seek");
      return 0;
   }
   if ( map_read_version(map) == 0 )
      return 0;
   if ( map_read_player_pos(map) == 0 )
      return 0;
   if ( map_read_sector_list(map) == 0 )
      return 0;
   if ( map_read_wall_list(map) == 0 )
      return 0;
   if ( map_read_sprite_list(map) == 0 )
      return 0;
   return 1;
}

/* calculate the size of the map, ignoring texture 0 walls */
int map_CalculateSize(map_type map)
{
   short i, j;
   short wallnum, wallidx;
   long x, y;

   map->min_x = 0x07fffffffL;
   map->min_y = 0x07fffffffL;
   map->max_x = -0x07fffffffL;
   map->max_y = -0x07fffffffL;

   for( i = 0; i < map->sec_cnt; i++ )
   {
      wallnum = map->sec_list[i].wallnum;
      wallidx = map->sec_list[i].wallptr;
      if (
            map->sec_list[i].ceilingstat != 0 ||
            map->sec_list[i].floorstat != 0 ||
            map->sec_list[i].ceilingshade != 0 ||
            map->sec_list[i].ceilingpal != 0 ||
            map->sec_list[i].ceilingxpanning != 0 ||
            map->sec_list[i].ceilingypanning != 0 ||
            map->sec_list[i].floorshade != 0 ||
            map->sec_list[i].floorpal != 0 ||
            map->sec_list[i].floorxpanning != 0 ||
            map->sec_list[i].floorypanning != 0 ||
            map->sec_list[i].visibility != 0 ||
            map->sec_list[i].filler != 0 ||
            map->sec_list[i].lotag != 0 ||
            map->sec_list[i].hitag != 0
          )
      {
         for( j = 0; j < wallnum; j++ )
         {
            x = map->wall_list[wallidx].x;
            y = map->wall_list[wallidx].y;

            /* NOTE: To ignore logos on the map, art with idx 0 is ignored */
            /* hopeing, that nobody assigns textures to his logo */
            /* well... wrong see e3l2.map of duke3d: checking more stuff */
            if ( map->wall_list[wallidx].picnum != 0 )
            {
               if ( map->min_x > x )
                  map->min_x = x;
               if ( map->min_y > y )
                  map->min_y = y;
               if ( map->max_x < x )
                  map->max_x = x;
               if ( map->max_y < y )
                  map->max_y = y;
            }
            wallidx = map->wall_list[wallidx].point2;
         }
      }
   }

   /*
   for( i = 0; i < map->wall_cnt; i++ )
   {
      if ( map->min_x > map->wall_list[i].x )
         map->min_x = map->wall_list[i].x;
      if ( map->min_y > map->wall_list[i].y )
         map->min_y = map->wall_list[i].y;
      if ( map->max_x < map->wall_list[i].x )
         map->max_x = map->wall_list[i].x;
      if ( map->max_y < map->wall_list[i].y )
         map->max_y = map->wall_list[i].y;
   }
   */
   return 1;
}

int map_cnt_sprites(map_type map)
{
   int j;
   int idx;
   map_sprite_struct *sprite;
   short i;

   j = 0;
   while ( map_item_info[j].typ >= 0  )
   {
      map_item_info[j].cnt = 0L;
      j++;
   }

   for( i = 0; i < map->sprite_cnt; i++ )
   {
      sprite = map->sprite_list+i;
      idx = map_get_info_idx(sprite->picnum, sprite->pal);
      if ( idx >= 0 )
      {
              

         if ( map_get_item_typ(idx) == MAP_TYP_MONSTER &&
              map->is_monster == 0 )
            continue;

         if ( map_get_item_typ(idx) == MAP_TYP_CARD &&
              map->is_card == 0 )
            continue;

         map_item_info[idx].cnt++;
      }
   }

   map->legend_item_cnt = 0;

   j = 0;
   while ( map_item_info[j].typ >= 0  )
   {
      if ( map_item_info[j].cnt != 0L )
         map->legend_item_cnt++;
      j++;
   }

   return map->legend_item_cnt;
}
