/* Tower Toppler - Nebulus
 * Copyright (C) 2000-2003  Andreas Rver
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include "level.h"

#include "points.h"
#include "archi.h"
#include "decl.h"
#include "configuration.h"
#include "screen.h"

#include <string.h>
#include <stdlib.h>

#define TOWERWID 16

/* tower block flags */
#define TBF_NONE     0x0000
#define TBF_EMPTY    0x0001 /* block is not solid */
#define TBF_PLATFORM 0x0002 /* block is a platform */
#define TBF_STATION  0x0004 /* block is a lift station */
#define TBF_DEADLY   0x0008 /* block is deadly */
#define TBF_ROBOT    0x0010 /* block is a robot */

struct _tblockdata {
    const char *nam; /* name */
    char       ch;   /* representation in saved tower file */
    Uint16     tf;   /* flags; TBF_foo */
} static towerblockdata[NUM_TBLOCKS] = {
    { "space",            ' ', TBF_EMPTY },
    { "lift top stop",    'v', TBF_EMPTY|TBF_STATION },
    { "lift middle stop", '+', TBF_EMPTY|TBF_STATION },
    { "lift bottom stop",  0,  TBF_EMPTY|TBF_STATION },
    { "robot 1",          '1', TBF_EMPTY|TBF_DEADLY|TBF_ROBOT },
    { "robot 2",          '2', TBF_EMPTY|TBF_DEADLY|TBF_ROBOT },
    { "robot 3",          '3', TBF_EMPTY|TBF_DEADLY|TBF_ROBOT },
    { "robot 4",          '4', TBF_EMPTY|TBF_DEADLY|TBF_ROBOT },
    { "robot 5",          '5', TBF_EMPTY|TBF_DEADLY|TBF_ROBOT },
    { "robot 6",          '6', TBF_EMPTY|TBF_DEADLY|TBF_ROBOT },
    { "robot 7",          '7', TBF_EMPTY|TBF_DEADLY|TBF_ROBOT },
    { "stick",            '!', /*TBF_PLATFORM*/ },
    { "step",             '-', TBF_PLATFORM },
    { "vanisher step",    '.', TBF_PLATFORM },
    { "slider > step",    '>', TBF_PLATFORM },
    { "slider < step",    '<', TBF_PLATFORM },
    { "box",              'b', TBF_NONE },
    { "door",             '#', TBF_EMPTY },
    { "target door",      'T', TBF_EMPTY },
    { "stick top",         0,  TBF_STATION/*|TBF_PLATFORM*/ },
    { "stick middle",      0,  TBF_STATION/*|TBF_PLATFORM*/ },
    { "stick bottom",      0,  TBF_STATION/*|TBF_PLATFORM*/ },
    { "lift top",          0,  TBF_STATION|TBF_PLATFORM },
    { "lift middle",       0,  TBF_STATION|TBF_PLATFORM },
    { "lift bottom",      '^', TBF_STATION|TBF_PLATFORM },
};

/* Sections in the data files; do not change the order,
 * and always add new ones to the end, so that we keep
 * compatibility with old towers/missions.
 * (the loader silently ignores unrecognized sections)
 */
enum towersection {
    TSS_END,
    TSS_TOWERNAME,
    TSS_TOWERTIME,
    TSS_TOWERCOLOR,
    TSS_TOWERDATA,
    TSS_DEMO,
    TSS_ROBOT
};

char tss_string_name[] = "name";
char tss_string_time[] = "time";
char tss_string_color[] = "color";
char tss_string_data[] = "data";
char tss_string_demo[] = "demo";
char tss_string_robot[] = "robot";

static Uint8 * mission = NULL;
static Uint8 towerheight;
static Uint8 towerrobot;
static Uint8 tower[256][TOWERWID];
static char towername[TOWERNAMELEN+1];
static Uint8 towernumber;
static Uint8 towercolor_red, towercolor_green, towercolor_blue;
static Uint16 towertime;
static Uint16 *towerdemo = NULL;
static int towerdemo_len = 0;

typedef struct mission_node {
  char name[30];
  char fname[100];
  Uint8 prio;         // the lower prio, the further in front the mission will be in the list
  mission_node *next;
} mission_node;

mission_node * missions;

static int missionfiles (const struct dirent *file)
{
  int len = strlen(file->d_name);

  return ((len > 4) &&
	  (file->d_name[len - 1] == 'm') &&
          (file->d_name[len - 2] == 't') &&
          (file->d_name[len - 3] == 't') &&
          (file->d_name[len - 4] == '.'));
}

Uint8 conv_char2towercode(char ch) {
  if (ch)
    for (int x = 0; x < NUM_TBLOCKS; x++)
      if (ch == towerblockdata[x].ch) return x;
  return TB_EMPTY;
}

char conv_towercode2char(Uint8 code) {
  if ((code < NUM_TBLOCKS) && (towerblockdata[code].ch)) 
      return towerblockdata[code].ch;
  return towerblockdata[TB_EMPTY].ch;
}

static void add_mission(char *fname) {

  char mname[30];
  Uint8 prio;

FILE * f = fopen(fname, "rb");

  if (!f) return;

  unsigned char mnamelength;
  fread(&mnamelength, 1, 1, f);

  if (mnamelength > 29) mnamelength = 29;

  fread(mname, mnamelength, 1, f);
  mname[mnamelength] = 0;
  fread(&prio, 1, 1, f);

  mission_node * m = missions;
  mission_node * l = NULL;

  /* first check if the mission is already there */
  while (m) {

    int erg = strcmp(m->name, mname);
    /* no two missions with the same name */
    if (erg == 0) {
      fclose(f);
      return;
    }
    l = m;
    m = m->next;
  }

  m = missions;
  l = NULL;

  while (m) {

    /* we have passed our target, the current mission must
     * be inserted before this mission
     */
    if (m->prio > prio) {
      mission_node * n = new mission_node;
      strcpy(n->name, mname);
      strcpy(n->fname, fname);
      n->prio = prio;
      n->next = m;

      if (l)
        l->next = n;
      else
        missions = n;

      fclose(f);
      return;
    }
    l = m;
    m = m->next;
  }

  /* insert at the end */
  m = new mission_node;
  strcpy(m->name, mname);
  strcpy(m->fname, fname);
  m->prio = prio;
  m->next = NULL;

  if (l)
    l->next = m;
  else
    missions = m;

  fclose(f);
}


void lev_findmissions() {
#if (SYSTEM == SYS_RISCOS)
  char pathname[256];
#else
  char pathname[100];
#endif

  struct dirent **eps = NULL;

  missions = NULL;

  /* check if already called, if so free the old list */
  while (missions) {
    mission_node *n = missions;
    missions = missions->next;
    delete n;
  }

#if (SYSTEM == SYS_WINDOWS)
  {
    char n[100];
    GetCurrentDirectory(100, n);
    sprintf(pathname, "%s\\", n);
  }
#else
#if (SYSTEM == SYS_RISCOS)
// On RISC OS we use the path variable of the application
// at the current directory doesn't really make sense
  sprintf(pathname, "%s/", TOP_DATADIR);

#else
  sprintf(pathname, "%s", "./");
#endif
#endif
  int n = alpha_scandir(pathname, &eps, missionfiles);
  if (n >= 0) {

    for (int i = 0; i < n; i++) {
    
      char fname[256];
      sprintf(fname, "%s%s", pathname, eps[i]->d_name);

      add_mission(fname);

      free(eps[i]);
    }
  }
  free(eps);
  eps = NULL;

#if (SYSTEM != SYS_WINDOWS)

  snprintf(pathname, 100, "%s/.toppler/", getenv("HOME"));
  n = alpha_scandir(pathname, &eps, missionfiles);
  if (n >= 0) {

    for (int i = 0; i < n; i++) {

      char fname[200];
      snprintf(fname, 200, "%s%s", pathname, eps[i]->d_name);

      add_mission(fname);
    }
  }
  free(eps);
  eps = NULL;

#if (SYSTEM != SYS_RISCOS)
// We've already done this above for RISCOS

  snprintf(pathname, 100, "%s/", TOP_DATADIR);
  n = alpha_scandir(pathname, &eps, missionfiles);

  if (n >= 0) {

    for (int i = 0; i < n; i++) {

      char fname[200];
      snprintf(fname, 200, "%s%s", pathname, eps[i]->d_name);

      add_mission(fname);
    }
  }
  free(eps);
  eps = NULL;
#endif

#endif

}

void lev_done() {
  if (mission) {
    delete [] mission;
    mission = NULL;
  }

  mission_node * m = missions;

  while (m) {
    m = m->next;
    delete missions;
    missions = m;
  }

  if (towerdemo) delete [] towerdemo;
}


Uint16 lev_missionnumber() {
  int num = 0;
  mission_node * m = missions;

  while (m) {
    num++;
    m = m->next;
  }

  return num;
}

const char * lev_missionname(Uint16 num) {
  mission_node * m = missions;

  while (num) {
    m = m->next;
    num--;
  }

  return m->name;
}

bool lev_loadmission(Uint16 num) {

  mission_node *m = missions;
  while (num) {
    num--;
    m = m->next;
  }

  FILE * in = fopen(m->fname, "rb");

  if (mission) delete [] mission;

  /* find out file size */
  fseek(in, 0, SEEK_END);
  int fsize = ftell(in);

  /* get enough memory and load the whole file into memory */
  mission = new unsigned char[fsize];
  fseek(in, 0, SEEK_SET);
  fread(mission, fsize, 1, in);

  fclose(in);

  for (int t = 0; t < lev_towercount(); t++) {
    lev_selecttower(t);
    for (int r = 0; r < towerheight; r++)
      for (int c = 0; c < TOWERWID; c++)
        if (tower[r][c] >= NUM_TBLOCKS)
          return false;
  }

  return true;
}

Uint8 lev_towercount(void) {
  return mission[mission[0] + 2];
}

void lev_selecttower(Uint8 number) {

  Uint32 towerstart;

  towernumber = number;
  towerrobot = number;
  Uint8 section;
  Uint32 section_len;

  lev_set_towerdemo(0, NULL);

  // find start of towerdata in mission
  {
    Uint32 idxpos = 0;

    idxpos += mission[mission[0] + 3];
    idxpos += long(mission[mission[0] + 4]) << 8;
    idxpos += long(mission[mission[0] + 5]) << 16;
    idxpos += long(mission[mission[0] + 6]) << 24;

    towerstart = mission[idxpos + 4 * number];
    towerstart += long(mission[idxpos + 4 * number + 1]) << 8;
    towerstart += long(mission[idxpos + 4 * number + 2]) << 16;
    towerstart += long(mission[idxpos + 4 * number + 3]) << 24;
  }

  do {
    section = mission[towerstart++];
    section_len = mission[towerstart++];
    section_len += Uint32(mission[towerstart++]) << 8;
    section_len += Uint32(mission[towerstart++]) << 16;
    section_len += Uint32(mission[towerstart++]) << 24;
    switch ((towersection)section) {
    case TSS_TOWERNAME:
      memmove(towername, &mission[towerstart], section_len);
      towername[section_len] = 0;
      break;
    case TSS_TOWERTIME:
      towertime = mission[towerstart] + (int(mission[towerstart + 1]) << 8);
      break;
    case TSS_TOWERCOLOR:
      towercolor_red = mission[towerstart];
      towercolor_green = mission[towerstart + 1];
      towercolor_blue = mission[towerstart + 2];
      break;
    case TSS_TOWERDATA:
      {
        towerheight = mission[towerstart];

        Uint32 bitstart = towerstart + 1;
        Uint32 bytestart = bitstart + 2 * towerheight;
        Uint16 wpos = 0;
        Uint16 bpos = 0;

        lev_clear_tower();

        for (Uint8 row = 0; row < towerheight; row++) {
          for (Uint8 col = 0; col < TOWERWID; col++) {
            if ((mission[bitstart + (bpos >> 3)] << (bpos & 7)) & 0x80)
              tower[row][col] = mission[bytestart + wpos++];
            else
              tower[row][col] = 0;
            bpos++;
          }
        }
        break;
      }
    case TSS_DEMO:
      {
        // get tower demo
        Uint16 *tmpbuf = NULL;
        Uint16 tmpbuf_len = mission[towerstart];
        tmpbuf_len += long(mission[towerstart+1]) << 8;
        Uint16 ofs = 2;

        if (tmpbuf_len) {
          tmpbuf = new Uint16[tmpbuf_len];
          Uint16 idx = 0;
          while (idx < tmpbuf_len) {
            Uint8 run = mission[towerstart + ofs++];
            Uint16 data = mission[towerstart + ofs++];
            data += Uint16(mission[towerstart + ofs++]) << 8;

            while (run) {
              tmpbuf[idx++] = data;
              run--;
            }
          }
        }

        lev_set_towerdemo(tmpbuf_len, tmpbuf);
        break;
      }
    case TSS_ROBOT:
      towerrobot = mission[towerstart];
#ifndef CREATOR
      towerrobot %= scr_numrobots();
#endif
      break;
    case TSS_END:
    default:      break;
    }
    towerstart += section_len;
  } while ((towersection)section != TSS_END);
}

char *
gen_passwd(int pwlen, char *allowed, int buflen, char *buf)
{
  static char passwd[PASSWORD_LEN + 1];
  int len = buflen;
  int alen;
  int i;

  if (!allowed) return NULL;

  alen = strlen(allowed);

  if (pwlen > PASSWORD_LEN) pwlen = PASSWORD_LEN;

  if (buflen < (pwlen*5)) len = pwlen*5;

  (void)memset(passwd, 0, PASSWORD_LEN);

  for (i = 0; i < len; i++) {
    passwd[i % pwlen] += buf[i % buflen];
    if (passwd[i % pwlen] > alen) passwd[(i+1) % pwlen]++;
  }

  for (i = 0; i < pwlen; i++)
    passwd[i] = allowed[abs(passwd[i]) % alen];

  passwd[pwlen] = '\0';

  return passwd;
}

char *lev_get_passwd(void) {
    return gen_passwd(PASSWORD_LEN, PASSWORD_CHARS, 256*TOWERWID, (char *)tower);
}

bool lev_show_passwd(int levnum) {
  return ((levnum > 0) &&
          (levnum < lev_towercount()) &&
          ((levnum % 3) == 0));
}

int lev_tower_passwd_entry(const char *passwd) {
  int i;
  if (!passwd) return 0;
  for (i = 0; i < lev_towercount(); i++) {
    lev_selecttower(i);
    if (!strcmp(passwd,lev_get_passwd())) return i;
  }
  return 0;
}

void lev_clear_tower(void) {
    memset(&tower, TB_EMPTY, 256*TOWERWID);
}

void lev_set_towercol(Uint8 r, Uint8 g, Uint8 b) {
  towercolor_red = r;
  towercolor_green = g;
  towercolor_blue = b;
}

Uint8 lev_towercol_red() {
  return towercolor_red;
}

Uint8 lev_towercol_green() {
  return towercolor_green;
}

Uint8 lev_towercol_blue() {
  return towercolor_blue;
}

Uint8 lev_tower(Uint16 row, Uint8 column) {
  return tower[row][column];
}

Uint8 lev_set_tower(Uint16 row, Uint8 column, Uint8 block) {
    Uint8 tmp = tower[row][column];
    tower[row][column] = block;
    return tmp;
}

Uint8 lev_towerrows(void) {
  return towerheight;
}

char * lev_towername(void) {
  return towername;
}

void lev_set_towerdemo(int demolen, Uint16 *demobuf) {
    if (towerdemo) delete [] towerdemo;
    towerdemo = demobuf;
    towerdemo_len = demolen;
}

void lev_get_towerdemo(int &demolen, Uint16 *&demobuf) {
    demobuf = towerdemo;
    demolen = towerdemo_len;
}

void lev_set_towername(char *str) {
    (void) strncpy(towername, str, TOWERNAMELEN);
    towername[TOWERNAMELEN] = '\0';
}

Uint8 lev_towernr(void) {
  return towernumber;
}

Uint8 lev_robotnr(void) {
  return towerrobot;
}

void lev_set_robotnr(Uint8 robot) {
  towerrobot = robot;
}

Uint16 lev_towertime(void) {
  return towertime;
}

void lev_set_towertime(Uint16 time) {
  towertime = time;
}

void lev_removelayer(Uint8 layer) {
  while (layer < towerheight) {
    for (Uint8 c = 0; c < TOWERWID; c++)
      tower[layer][c] = tower[layer + 1][c];
    layer++;
  }

  towerheight--;
}

/* empties a cell in the tower */
void lev_clear(int row, int col) {
  tower[row][col] = TB_EMPTY;
}

/* if the given position contains a vanishing step, remove it */
void lev_removevanishstep(int row, int col) {
  if (tower[row][col] == TB_STEP_VANISHER)
    tower[row][col] = TB_EMPTY;
}

/********** everything for doors ********/

/* returns true, if the given position is the upper end of a door
 (a door is always 3 layers) */
bool lev_is_door_upperend(int row, int col) {
  return lev_is_door(row, col) &&
    lev_is_door(row + 1, col) &&
    lev_is_door(row + 2, col);
}

/* returns true if the given position contains a door */
bool lev_is_door(int row, int col) {
  return (tower[row][col] == TB_DOOR ||
          tower[row][col] == TB_DOOR_TARGET);
}

/* returns true, if the given fiels contains a target door */
bool lev_is_targetdoor(int row, int col) {
  return tower[row][col] == TB_DOOR_TARGET;
}

/**************** everything for elevators ******************/

bool lev_is_station(int row, int col) {
  return ((towerblockdata[tower[row][col]].tf & TBF_STATION));
}
bool lev_is_up_station(int row, int col) {
   return ((tower[row][col] == TB_ELEV_BOTTOM) ||
	   (tower[row][col] == TB_ELEV_MIDDLE));
}
bool lev_is_down_station(int row, int col) {
   return ((tower[row][col] == TB_ELEV_TOP) ||
	   (tower[row][col] == TB_ELEV_MIDDLE));
}
bool lev_is_bottom_station(int row, int col) {
   return (tower[row][col] == TB_ELEV_BOTTOM);
}

bool lev_is_platform(int row, int col) {
  return ((towerblockdata[tower[row][col]].tf & TBF_PLATFORM));
}
bool lev_is_stick(int row, int col) {
  return ((tower[row][col] == TB_STICK) ||
	  (tower[row][col] == TB_STICK_TOP) ||
	  (tower[row][col] == TB_STICK_MIDDLE) ||
	  (tower[row][col] == TB_STICK_BOTTOM));
}

bool lev_is_elevator(int row, int col) {
  return ((tower[row][col] == TB_STICK_BOTTOM) ||
	  (tower[row][col] == TB_STICK_MIDDLE) ||
	  (tower[row][col] == TB_STICK_TOP) ||
	  (tower[row][col] == TB_ELEV_BOTTOM) ||
	  (tower[row][col] == TB_ELEV_MIDDLE) ||
	  (tower[row][col] == TB_ELEV_TOP));
}

void lev_platform2stick(int row, int col) {
  if (tower[row][col] == TB_ELEV_TOP) tower[row][col] = TB_STICK_TOP;
  else if (tower[row][col] == TB_ELEV_MIDDLE) tower[row][col] = TB_STICK_MIDDLE;
  else if (tower[row][col] == TB_ELEV_BOTTOM) tower[row][col] = TB_STICK_BOTTOM;
  else if (tower[row][col] == TB_STEP) tower[row][col] = TB_STICK;
}
void lev_stick2platform(int row, int col) {
  if (tower[row][col] == TB_STICK_TOP) tower[row][col] = TB_ELEV_TOP;
  else if (tower[row][col] == TB_STICK_MIDDLE) tower[row][col] = TB_ELEV_MIDDLE;
  else if (tower[row][col] == TB_STICK_BOTTOM) tower[row][col] = TB_ELEV_BOTTOM;
  else if (tower[row][col] == TB_STICK) tower[row][col] = TB_STEP;
}
void lev_stick2empty(int row, int col) {
  if (tower[row][col] == TB_STICK_TOP) tower[row][col] = TB_STATION_TOP;
  else if (tower[row][col] == TB_STICK_MIDDLE) tower[row][col] = TB_STATION_MIDDLE;
  else if (tower[row][col] == TB_STICK_BOTTOM) tower[row][col] = TB_STATION_BOTTOM;
  else if (tower[row][col] == TB_STICK) tower[row][col] = TB_EMPTY;
}
void lev_empty2stick(int row, int col) {
  if (tower[row][col] == TB_STATION_TOP) tower[row][col] = TB_STICK_TOP;
  else if (tower[row][col] == TB_STATION_MIDDLE) tower[row][col] = TB_STICK_MIDDLE;
  else if (tower[row][col] == TB_STATION_BOTTOM) tower[row][col] = TB_STICK_BOTTOM;
  else if (tower[row][col] == TB_EMPTY) tower[row][col] = TB_STICK;
}
void lev_platform2empty(int row, int col) {
  if (tower[row][col] == TB_ELEV_TOP) tower[row][col] = TB_STATION_TOP;
  else if (tower[row][col] == TB_ELEV_MIDDLE) tower[row][col] = TB_STATION_MIDDLE;
  else if (tower[row][col] == TB_ELEV_BOTTOM) tower[row][col] = TB_STATION_BOTTOM;
  else if (tower[row][col] == TB_STEP) tower[row][col] = TB_EMPTY;
}

/* misc questions */
bool lev_is_empty(int row, int col) {
  return ((towerblockdata[tower[row][col]].tf & TBF_EMPTY));
}

bool lev_is_box(int row, int col) {
  return tower[row][col] == TB_BOX;
}

int lev_is_sliding(int row, int col) {
    return ((tower[row][col] == TB_STEP_LSLIDER) ? 1 :
	    (tower[row][col] == TB_STEP_RSLIDER) ? -1 : 
	    0);
}

bool lev_is_robot(int row, int col) {
  return ((towerblockdata[tower[row][col]].tf & TBF_ROBOT));
}


/* returns true, if the given figure can be at the given position
 without colliding with fixed objects of the tower */
bool lev_testfigure(long angle, long vert, long back,
                    long fore, long typ, long height, long width) {
  long hinten, vorn, y, x = 0, k, t;

  hinten = ((angle + back) >> 3) & 0xf;
  vorn = (((angle + fore) >> 3) + 1) & 0xf;

  y = vert / 4;
  vert &= 3;

  switch (typ) {

    case 0:  /* toppler */
      x = (vert == 3) ? 3 : 2;
      break;
  
    case 1:  /* robot */
      x = (vert == 0) ? 1 : 2;
      break;
  
    case 2:  /* snowball */
      x = (vert == 0) ? 0 : 1;
      break;
  }

  do {
    k = x;
    do {
      if (lev_is_platform(k + y, hinten)) {
        return false;
      } else if (lev_is_stick(k + y, hinten)) {
        t = hinten * 8 + height;
        if ((angle >= t) && (angle < t + width))
          return false;
      } else if (lev_is_box(k + y, hinten)) {
        t = hinten * 8 + height;
        if ((angle >= t) && (angle < t + width)) {
          if (typ == 2) {
            // the snowball removes the box
            lev_clear(k + y, hinten);
            pts_add(50);
          }
          return false;
        }
      }
      k--;
    } while (k != -1);
    hinten = (hinten + 1) & 0xf;
  } while (hinten != vorn);

  return true;
}

unsigned char lev_putplatform(int row, int col) {
  unsigned char erg = tower[row][col];

  tower[row][col] = TB_ELEV_BOTTOM;

  return erg;
}

void lev_restore(int row, int col, unsigned char bg) {
  tower[row][col] = bg;
}


/* load and save a tower */
bool lev_loadtower(const char *fname) {
  FILE *in = open_local_data_file(fname);
  char line[200];

  if (in == NULL) return false;
    
  lev_clear_tower();
  lev_set_towerdemo(0, NULL);
  towertime = 0;
  towerheight = 0;
  towerrobot = 0;

  while (true) {

    if (feof(in)) break;

    /* look for next section start */
    fgets(line, 200, in);
    if (line[0] != '[')
      continue;

    if (strncmp(&line[1], tss_string_name, strlen(tss_string_name)) == 0) {
      fgets(towername, TOWERNAMELEN+1, in);
      /* remove not allowed characters */
      {
        int inp = 0;
        int outp = 0;
        while(towername[inp]) {
          if ((towername[inp] >= 32) && (towername[inp] < 127))
            towername[outp++] = towername[inp];
          inp++;
        }
        towername[outp] = 0;
      }
    } else if (strncmp(&line[1], tss_string_color, strlen(tss_string_color)) == 0) {
      fgets(line, 200, in);
      sscanf(line, "%hhu, %hhu, %hhu\n", &towercolor_red, &towercolor_green, &towercolor_blue);
    } else if (strncmp(&line[1], tss_string_time, strlen(tss_string_time)) == 0) {
      fgets(line, 200, in);
      sscanf(line, "%hu\n", &towertime);
    } else if (strncmp(&line[1], tss_string_data, strlen(tss_string_data)) == 0) {

      fgets(line, 200, in);
      sscanf(line, "%hhu\n", &towerheight);

      for (int row = towerheight - 1; row >= 0; row--) {
    
        fgets(line, 200, in);
    
        for (int col = 0; col < TOWERWID; col++)
          tower[row][col] = conv_char2towercode(line[col]);
      }
    } else if (strncmp(&line[1], tss_string_demo, strlen(tss_string_demo)) == 0) {
      if (fgets(line, 200, in)) {
          sscanf(line, "%i\n", &towerdemo_len);
    
          if (towerdemo_len > 0) {
              towerdemo = new Uint16[towerdemo_len];
    
              for (int idx = 0; idx < towerdemo_len; idx++) {
                  fgets(line, 200, in);
                  sscanf(line, "%hu\n", &towerdemo[idx]);
              }
          } else towerdemo = NULL;
      }
    } else if (strncmp(&line[1], tss_string_robot, strlen(tss_string_robot)) == 0) {
      fgets(line, 200, in);
      {
        int i;
        sscanf(line, "%u\n", &i);
#ifdef CREATOR
        towerrobot = i & 0xFF;
#else
        towerrobot = (i & 0xFF) % scr_numrobots();
#endif
      }
    }
  }

  fclose(in);
  return true;
}

bool lev_savetower(const char *fname) {
  FILE *out = create_local_data_file(fname);

  if (out == NULL) return false;

  fprintf(out, "[%s]\n", tss_string_name);
  fprintf(out, "%s\n", towername);

  fprintf(out, "[%s]\n", tss_string_color);
  fprintf(out, "%hhu, %hhu, %hhu\n", towercolor_red, towercolor_green, towercolor_blue);

  fprintf(out, "[%s]\n", tss_string_time);
  fprintf(out, "%hu\n", towertime);

  fprintf(out, "[%s]\n", tss_string_robot);
  fprintf(out, "%hhu\n", towerrobot);

  fprintf(out, "[%s]\n", tss_string_data);
  fprintf(out, "%hhu\n", towerheight);
  for (int row = towerheight - 1; row >= 0; row--) {
    char line[TOWERWID+2];

    for (int col = 0; col < TOWERWID; col++)
      line[col] = conv_towercode2char(tower[row][col]);

    line[TOWERWID] = '|';
    line[TOWERWID+1] = 0;
    fprintf(out, "%s\n", line);
  }

  fprintf(out, "[%s]\n", tss_string_demo);
  fprintf(out, "%i\n", towerdemo_len);
  if (towerdemo && (towerdemo_len > 0)) {
    for (int idx = 0; idx < towerdemo_len; idx++) {
      fprintf(out, "%hu\n", towerdemo[idx]);
    }
  }

  fclose(out);

  return true;
}

/* rotate row clock and counter clockwise */
void lev_rotaterow(int row, bool clockwise) {
  if (clockwise) {
    int k = tower[row][0];
    for (int i = 1; i < TOWERWID; i++)
      tower[row][i - 1] = tower[row][i];
    tower[row][TOWERWID-1] = k;
  } else {
    int k = tower[row][TOWERWID-1];
    for (int i = TOWERWID-1; i >= 0; i++)
      tower[row][i] = tower[row][i - 1];
    tower[row][0] = k;
  }
}

/* insert and delete one row */
void lev_insertrow(int position) {
  if ((towerheight < 255) && (position < towerheight)) {
    int k = towerheight - 1;
    while (k >= position) {
      for (int i = 0; i < TOWERWID; i++)
        tower[k + 1][i] = tower[k][i];
      k--;
    }
    for (int i = 0; i < TOWERWID; i++)
      tower[position][i] = 0;
    towerheight++;
    return;
  }
  if (towerheight == 0) {
    for (int i = 0; i < TOWERWID; i++)
      tower[0][i] = 0;
    towerheight = 1;
  }
}

void lev_deleterow(int position) {
  if ((position < towerheight) && (position >= 0)) {
    int k = position + 1;
    while (k < towerheight) {
      for (int i = 0; i < TOWERWID; i++)
        tower[k - 1][i] = tower[k][i];
      k++;
    }
    towerheight--;
  }
}

void lev_new(Uint8 hei) {
  towerheight = hei;
  lev_clear_tower();
}

void lev_putspace(int row, int col) {

  // always delete the whole door
  if (lev_is_door(row, col)) {
    int r = row - 1;
    while (lev_is_door(r, col)) {
      tower[r][col] = TB_EMPTY;
      r--;
    }
    r = row + 1;
    while (lev_is_door(r, col)) {
      tower[r][col] = TB_EMPTY;
      r++;
    }
  }
  tower[row][col] = TB_EMPTY;
}
void lev_putrobot1(int row, int col) { tower[row][col] = TB_ROBOT1; }
void lev_putrobot2(int row, int col) { tower[row][col] = TB_ROBOT2; }
void lev_putrobot3(int row, int col) { tower[row][col] = TB_ROBOT3; }
void lev_putrobot4(int row, int col) { tower[row][col] = TB_ROBOT4; }
void lev_putrobot5(int row, int col) { tower[row][col] = TB_ROBOT5; }
void lev_putrobot6(int row, int col) { tower[row][col] = TB_ROBOT6; }
void lev_putrobot7(int row, int col) { tower[row][col] = TB_ROBOT7; }
void lev_putstep(int row, int col) { tower[row][col] = TB_STEP; }
void lev_putvanishingstep(int row, int col) { tower[row][col] = TB_STEP_VANISHER; }
void lev_putslidingstep_left(int row, int col) { tower[row][col] = TB_STEP_LSLIDER; }
void lev_putslidingstep_right(int row, int col) { tower[row][col] = TB_STEP_RSLIDER; }

void lev_putdoor(int row, int col) {

  if (row + 2 < towerheight) {

    tower[row][col] = TB_DOOR;
    tower[row + 1][col] = TB_DOOR;
    tower[row + 2][col] = TB_DOOR;

    if ((tower[row][(col + (TOWERWID/2)) % TOWERWID] == 0) &&
        (tower[row + 1][(col + (TOWERWID/2)) % TOWERWID] == 0) &&
        (tower[row + 2][(col + (TOWERWID/2)) % TOWERWID] == 0)) {
      tower[row][(col + (TOWERWID/2)) % TOWERWID] = TB_DOOR;
      tower[row + 1][(col + (TOWERWID/2)) % TOWERWID] = TB_DOOR;
      tower[row + 2][(col + (TOWERWID/2)) % TOWERWID] = TB_DOOR;
    }
  }
}

void lev_puttarget(int row, int col) {
  if (row + 2 < towerheight) {
    tower[row][col] = TB_DOOR_TARGET;
    tower[row + 1][col] = TB_DOOR_TARGET;
    tower[row + 2][col] = TB_DOOR_TARGET;
  }
}

void lev_putstick(int row, int col) { tower[row][col] = TB_STICK; }
void lev_putbox(int row, int col) { tower[row][col] = TB_BOX; }
void lev_putelevator(int row, int col) { tower[row][col] = TB_ELEV_BOTTOM; }
void lev_putmiddlestation(int row, int col) { tower[row][col] = TB_STATION_MIDDLE; }
void lev_puttopstation(int row, int col) { tower[row][col] = TB_STATION_TOP; }


void lev_save(unsigned char *&data) {
  data = new unsigned char[256*TOWERWID+1];

  data[0] = towerheight;
  memmove(&data[1], tower, 256 * TOWERWID);
}

void lev_restore(unsigned char *&data) {
  memmove(tower, &data[1], 256 * TOWERWID);
  towerheight = data[0];

  delete [] data;
}

lev_problem lev_is_consistent(int &row, int &col) {

  int y;
  bool has_exit = false;
  // check first, if the starting point is correctly organized
  // so that there is no obstacle and we can survive there
  if ((tower[1][0] != TB_STICK) && (tower[1][0] != TB_STEP) &&
       (tower[1][0] != TB_STEP_LSLIDER) && (tower[1][0] != TB_STEP_RSLIDER) &&
       (tower[1][0] != TB_BOX) &&
       (tower[1][0] != TB_ELEV_BOTTOM) &&
       (tower[0][0] != TB_STICK) && (tower[0][0] != TB_STEP) &&
       (tower[0][0] != TB_STEP_LSLIDER) && (tower[0][0] != TB_STEP_RSLIDER) &&
       (tower[0][0] != TB_BOX) &&
       (tower[0][0] != TB_ELEV_BOTTOM)) {
    row = 1;
    col = 0;
    return TPROB_NOSTARTSTEP;
  }
  for (y = 2; y < 5; y++)
    if ((towerblockdata[tower[y][0]].tf & TBF_DEADLY) ||
	!(towerblockdata[tower[y][0]].tf & TBF_EMPTY)) {
      row = y;
      col = 0;
      return TPROB_STARTBLOCKED;
    }

  if (towerheight < 4) return TPROB_SHORTTOWER;

  for (int r = 0; r < towerheight; r++)
    for (int c = 0; c < TOWERWID; c++) { 
      // check for undefined symbols
      if (tower[r][c] >= NUM_TBLOCKS) {
	  row = r;
	  col = c;
	  return TPROB_UNDEFBLOCK;
      }

      // check if elevators always have an opposing end without unremovable
      // obstacles
      if (tower[r][c] == TB_ELEV_BOTTOM) {
        int d = r + 1;
        while ((tower[d][c] != TB_STATION_TOP) && (d < towerheight)) {
          if ((tower[d][c] != TB_EMPTY) &&
              (tower[d][c] != TB_ROBOT1) &&
              (tower[d][c] != TB_ROBOT2) &&
              (tower[d][c] != TB_ROBOT3) &&
              (tower[d][c] != TB_ROBOT4) &&
              (tower[d][c] != TB_ROBOT5) &&
              (tower[d][c] != TB_ROBOT6) &&
              (tower[d][c] != TB_ROBOT7) &&
              (tower[d][c] != TB_BOX) &&
              (tower[d][c] != TB_STATION_MIDDLE) &&
              (tower[d][c] != TB_STEP_VANISHER)) {
            row = r;
            col = c;
            return TPROB_ELEVATORBLOCKED;
          }
          d++;
        }
        if (d >= towerheight) {
          row = r;
          col = c;
          return TPROB_NOELEVATORSTOP;
        }
      }

      if (tower[r][c] == TB_STATION_MIDDLE) {
        int d = r + 1;
        while ((tower[d][c] != TB_STATION_TOP) && (d < towerheight)) {
          if ((tower[d][c] != TB_EMPTY) &&
              (tower[d][c] != TB_ROBOT1) &&
              (tower[d][c] != TB_ROBOT2) &&
              (tower[d][c] != TB_ROBOT3) &&
              (tower[d][c] != TB_ROBOT4) &&
              (tower[d][c] != TB_ROBOT5) &&
              (tower[d][c] != TB_ROBOT6) &&
              (tower[d][c] != TB_ROBOT7) &&
              (tower[d][c] != TB_BOX) &&
              (tower[d][c] != TB_STATION_MIDDLE) &&
              (tower[d][c] != TB_STEP_VANISHER)) {
            row = r;
            col = c;
            return TPROB_ELEVATORBLOCKED;
          }
          d++;
        }
        if (d >= towerheight) {
          row = r;
          col = c;
          return TPROB_NOELEVATORSTOP;
        }
        d = r - 1;
        while ((tower[d][c] != TB_ELEV_BOTTOM) && (d >= 0)) {
          if ((tower[d][c] != TB_EMPTY) &&
              (tower[d][c] != TB_ROBOT1) &&
              (tower[d][c] != TB_ROBOT2) &&
              (tower[d][c] != TB_ROBOT3) &&
              (tower[d][c] != TB_ROBOT4) &&
              (tower[d][c] != TB_ROBOT5) &&
              (tower[d][c] != TB_ROBOT6) &&
              (tower[d][c] != TB_ROBOT7) &&
              (tower[d][c] != TB_BOX) &&
              (tower[d][c] != TB_STATION_MIDDLE) &&
              (tower[d][c] != TB_STEP_VANISHER)) {
            row = r;
            col = c;
            return TPROB_ELEVATORBLOCKED;
          }
          d--;
        }
        if (d < 0) {
          row = r;
          col = c;
          return TPROB_NOELEVATORSTOP;
        }
      }

      if (tower[r][c] == TB_STATION_TOP) {
        int d = r - 1;
        while ((tower[d][c] != TB_ELEV_BOTTOM) && (d >= 0)) {
          if ((tower[d][c] != TB_EMPTY) &&
              (tower[d][c] != TB_ROBOT1) &&
              (tower[d][c] != TB_ROBOT2) &&
              (tower[d][c] != TB_ROBOT3) &&
              (tower[d][c] != TB_ROBOT4) &&
              (tower[d][c] != TB_ROBOT5) &&
              (tower[d][c] != TB_ROBOT6) &&
              (tower[d][c] != TB_ROBOT7) &&
              (tower[d][c] != TB_BOX) &&
              (tower[d][c] != TB_STATION_MIDDLE) &&
              (tower[d][c] != TB_STEP_VANISHER)) {
            row = r;
            col = c;
            return TPROB_ELEVATORBLOCKED;
          }
          d--;
        }
        if (d < 0) {
          row = r;
          col = c;
          return TPROB_NOELEVATORSTOP;
        }
      }

      /* check for exit, and that it's reachable */
      if (tower[r][c] == TB_DOOR_TARGET) {
        int d = r - 1;

        if (d < 0) {
          row = 0;
          col = c;
          return TPROB_UNREACHABLEEXIT;
        }

        while ((d >= 0) && (tower[d][c] == TB_DOOR_TARGET))  d--;
        if (d >= 0) {
          if ((tower[d][c] != TB_STICK) && (tower[d][c] != TB_STEP) &&
              (tower[d][c] != TB_BOX) && (tower[d][c] != TB_ELEV_BOTTOM) &&
              (tower[d][c] != TB_STICK)) {
            row = r;
            col = c;
            return TPROB_UNREACHABLEEXIT;
          }
        }
        has_exit = true;
      }

      // check doors
      if ((tower[r][c] == TB_DOOR) &&
          !lev_is_door(r, (c + (TOWERWID/2)) % TOWERWID)) {
        row = r;
        col = c;
        return TPROB_NOOTHERDOOR;
      }
      if (lev_is_door(r,c)) {
        bool A = (r > 0) && (tower[r-1][c] == tower[r][c]);
        bool B = (r > 1) && (tower[r-2][c] == tower[r][c]);
        bool D = (r + 1 < towerheight) && (tower[r+1][c] == tower[r][c]);
        bool E = (r + 2 < towerheight) && (tower[r+2][c] == tower[r][c]);

        if (!(A&&B||A&&D||D&&E)) {
          row = r;
          col = c;
          return TPROB_BROKENDOOR;
        }
      }
    }

  if (!has_exit) return TPROB_NOEXIT;

  /* other, non-tower related problems */
  if (lev_towertime() < 5) return TPROB_SHORTTIME;
  if (!strlen(lev_towername())) return TPROB_NONAME;

  return TPROB_NONE;
}

/* the functions for mission creation */

static FILE * fmission = NULL;
static Uint8 nmission = 0;
static Uint32 missionidx[256];

bool lev_mission_new(char * name, Uint8 prio) {
  fatal_assert(!fmission, "called mission_finish twice");

  char fname[200];
  snprintf(fname, 200, "%s.ttm", name);

  fmission = create_local_data_file(fname);

  if (!fmission) return false;

  unsigned char tmp = strlen(name);

  /* write out name */
  fwrite(&tmp, 1 ,1, fmission);
  fwrite(name, 1, tmp, fmission);

  fwrite(&prio, 1, 1, fmission);

  /* placeholders for towernumber and indexstart */
  fwrite(&tmp, 1, 1, fmission);
  fwrite(&tmp, 1, 4, fmission);

  nmission = 0;

  return true;
}

void write_fmission_section(Uint8 section, Uint32 section_len) {
    Uint8 tmp;
    fwrite(&section, 1, 1, fmission);
    
    tmp = section_len & 0xff;
    fwrite(&tmp, 1, 1, fmission);
    tmp = (section_len >> 8) & 0xff;
    fwrite(&tmp, 1, 1, fmission);
    tmp = (section_len >> 16) & 0xff;
    fwrite(&tmp, 1, 1, fmission);
    tmp = (section_len >> 24) & 0xff;
    fwrite(&tmp, 1, 1, fmission);
}

void lev_mission_addtower(char * name) {
  fatal_assert(fmission, "called mission_addtower without mission_new");

  Uint8 rows, col;
  Sint16 row;
  Uint8 namelen, tmp;
  Uint32 section_len;
  int idx;

  if (!tower) return;

  missionidx[nmission] = ftell(fmission);
  nmission++;

  if (!lev_loadtower(name)) return;

  namelen = strlen(towername);
  write_fmission_section(TSS_TOWERNAME, namelen);
  fwrite(towername, 1, namelen, fmission);

  write_fmission_section(TSS_ROBOT, 1);
  fwrite(&towerrobot, 1, 1, fmission);

  write_fmission_section(TSS_TOWERTIME, 2);
  tmp = towertime & 0xff;
  fwrite(&tmp, 1, 1, fmission);
  tmp = (towertime >> 8) & 0xff;
  fwrite(&tmp, 1, 1, fmission);

  write_fmission_section(TSS_TOWERCOLOR, 3);
  fwrite(&towercolor_red, 1, 1, fmission);
  fwrite(&towercolor_green, 1, 1, fmission);
  fwrite(&towercolor_blue, 1, 1, fmission);

  rows = towerheight;

  /* calculate tower data section length */
  section_len = 2*towerheight;
  for (row = 0; row < rows; row++)
    for (col = 0; col < TOWERWID; col++)
      if (tower[row][col]) section_len++;
  write_fmission_section(TSS_TOWERDATA, section_len + 1);


  fwrite(&towerheight, 1, 1, fmission);

  /* output bitmap */
  for (row = 0; row < rows; row++) {

    Uint8 c = 0;
    for (col = 0; col < 8; col ++)
      if (tower[row][col])
        c |= (0x80 >> col);

    fwrite(&c, 1, 1, fmission);

    c = 0;
    for (col = 0; col < 8; col ++)
      if (tower[row][col + 8])
        c |= (0x80 >> col);

    fwrite(&c, 1, 1, fmission);
  }

  /* output bytemap */
  for (row = 0; row < rows; row++)
    for (col = 0; col < TOWERWID; col++)
      if (tower[row][col])
        fwrite(&tower[row][col], 1, 1, fmission);

  /* output towerdemo */

  if (towerdemo && (towerdemo_len > 0)) {
    Uint8 run;
    Uint16 data;

    /* calc data length */
    run = 1;
    data = towerdemo[0];
    section_len = 2;

    for (idx = 1; idx < towerdemo_len; idx++) {
      if ((data != towerdemo[idx]) || (run == 0xff)) {
        section_len += 3;

        data = towerdemo[idx];
        run = 1;
      } else
        run ++;
    }
    if (run) section_len += 3;

    write_fmission_section(TSS_DEMO, section_len);

    /* output length */
    tmp = towerdemo_len & 0xff;
    fwrite(&tmp, 1, 1, fmission);
    tmp = (towerdemo_len >> 8) & 0xff;
    fwrite(&tmp, 1, 1, fmission);

    /* output data using a simple runlength encoder */
    run = 1;
    data = towerdemo[0];

    for (idx = 1; idx < towerdemo_len; idx++) {
      if ((data != towerdemo[idx]) || (run == 0xff)) {
        fwrite(&run, 1, 1, fmission);
        tmp = data & 0xff;
        fwrite(&tmp, 1, 1, fmission);
        tmp = (data >> 8) & 0xff;
        fwrite(&tmp, 1, 1, fmission);

        data = towerdemo[idx];
        run = 1;
      } else
        run ++;
    }

    if (run) {
      fwrite(&run, 1, 1, fmission);
      tmp = data & 0xff;
      fwrite(&tmp, 1, 1, fmission);
      tmp = (data >> 8) & 0xff;
      fwrite(&tmp, 1, 1, fmission);
    }
  }

  write_fmission_section(TSS_END, 0);
}

void lev_mission_finish() {
  fatal_assert(fmission, "called mission_finish without mission_new");

  Uint8 c;

  /* save indexstart and write out index */
  Uint32 idxpos = ftell(fmission);
  for (Uint8 i = 0; i < nmission; i++) {

    c = missionidx[i] & 0xff;
    fwrite(&c, 1, 1, fmission);
    c = (missionidx[i] >> 8) & 0xff;
    fwrite(&c, 1, 1, fmission);
    c = (missionidx[i] >> 16) & 0xff;
    fwrite(&c, 1, 1, fmission);
    c = (missionidx[i] >> 24) & 0xff;
    fwrite(&c, 1, 1, fmission);
  }

  /* write out the number of towers in this mission */
  fseek(fmission, 0, SEEK_SET);
  fread(&c, 1, 1, fmission);

  fseek(fmission, c + 2, SEEK_SET);
  fwrite(&nmission, 1, 1, fmission);

  /* write out index position */
  c = idxpos & 0xff;
  fwrite(&c, 1, 1, fmission);
  c = (idxpos >> 8) & 0xff;
  fwrite(&c, 1, 1, fmission);
  c = (idxpos >> 16) & 0xff;
  fwrite(&c, 1, 1, fmission);
  c = (idxpos >> 24) & 0xff;
  fwrite(&c, 1, 1, fmission);

  fclose(fmission);

  fmission = NULL;
}

