/*  
    level.c

    Copyright (C) 2010-2019 Amf

    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 <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>

#include "chroma.h"
#include "level.h"
#include "util.h"

extern char *piece_name[];

char piecetochar(int piece)
{
    switch(piece)
    {
        case PIECE_SPACE:
            return ' ';
        case PIECE_WALL:
            return '%';
        case PIECE_PLAYER_ONE:
            return '1';
        case PIECE_PLAYER_TWO:
            return '2';
        case PIECE_DOTS:
            return '.';
        case PIECE_ARROW_RED_LEFT:
            return 'a'; 
        case PIECE_ARROW_RED_UP:
            return 'b'; 
        case PIECE_ARROW_RED_RIGHT:
            return 'c'; 
        case PIECE_ARROW_RED_DOWN:
            return 'd'; 
        case PIECE_BOMB_RED_LEFT:
            return 'A'; 
        case PIECE_BOMB_RED_UP:
            return 'B'; 
        case PIECE_BOMB_RED_RIGHT:
            return 'C'; 
        case PIECE_BOMB_RED_DOWN:
            return 'D'; 
        case PIECE_ARROW_GREEN_LEFT:
            return 'e'; 
        case PIECE_ARROW_GREEN_UP:
            return 'f'; 
        case PIECE_ARROW_GREEN_RIGHT:
            return 'g'; 
        case PIECE_ARROW_GREEN_DOWN:
            return 'h'; 
        case PIECE_BOMB_GREEN_LEFT:
            return 'E'; 
        case PIECE_BOMB_GREEN_UP:
            return 'F'; 
        case PIECE_BOMB_GREEN_RIGHT:
            return 'G'; 
        case PIECE_BOMB_GREEN_DOWN:
            return 'H'; 
        case PIECE_ARROW_BLUE_LEFT:
            return 'i'; 
        case PIECE_ARROW_BLUE_UP:
            return 'j'; 
        case PIECE_ARROW_BLUE_RIGHT:
            return 'k'; 
        case PIECE_ARROW_BLUE_DOWN:
            return 'l'; 
        case PIECE_BOMB_BLUE_LEFT:
            return 'I'; 
        case PIECE_BOMB_BLUE_UP:
            return 'J'; 
        case PIECE_BOMB_BLUE_RIGHT:
            return 'K'; 
        case PIECE_BOMB_BLUE_DOWN:
            return 'L'; 
        case PIECE_CIRCLE:
            return 'o'; 
        case PIECE_STAR:
            return '*'; 
        case PIECE_DOOR:
            return '/'; 
#ifdef ENIGMA_COMPATIBILITY
        case PIECE_DOTS_DOUBLE:
            return ':'; 
        case PIECE_CIRCLE_DOUBLE:
            return '8'; 
#endif
#ifdef XOR_COMPATIBILITY
        case PIECE_DOTS_X:
            return '-'; 
        case PIECE_DOTS_Y:
            return '|'; 
        case PIECE_SWITCH:
            return 'S'; 
        case PIECE_TELEPORT:
            return 'T'; 
        case PIECE_MAP_TOP_LEFT:
            return 'M'; 
        case PIECE_MAP_TOP_RIGHT:
            return 'm'; 
        case PIECE_MAP_BOTTOM_LEFT:
            return 'N'; 
        case PIECE_MAP_BOTTOM_RIGHT:
            return 'n'; 
#endif
        case PIECE_GONE:
            return '!';
        case PIECE_EXPLOSION_RED_LEFT:
            return 'p';
        case PIECE_EXPLOSION_RED_HORIZONTAL:
            return 'q';
        case PIECE_EXPLOSION_RED_RIGHT:
            return 'r';
        case PIECE_EXPLOSION_RED_TOP:
            return 'P';
        case PIECE_EXPLOSION_RED_VERTICAL:
            return 'Q';
        case PIECE_EXPLOSION_RED_BOTTOM:
            return 'R';
        case PIECE_EXPLOSION_GREEN_LEFT:
            return 'u';
        case PIECE_EXPLOSION_GREEN_HORIZONTAL:
            return 'v';
        case PIECE_EXPLOSION_GREEN_RIGHT:
            return 'w';
        case PIECE_EXPLOSION_GREEN_TOP:
            return 'U';
        case PIECE_EXPLOSION_GREEN_VERTICAL:
            return 'V';
        case PIECE_EXPLOSION_GREEN_BOTTOM:
            return 'W';
        case PIECE_EXPLOSION_BLUE_LEFT:
            return 'x';
        case PIECE_EXPLOSION_BLUE_HORIZONTAL:
            return 'y';
        case PIECE_EXPLOSION_BLUE_RIGHT:
            return 'z';
        case PIECE_EXPLOSION_BLUE_TOP:
            return 'X';
        case PIECE_EXPLOSION_BLUE_VERTICAL:
            return 'Y';
        case PIECE_EXPLOSION_BLUE_BOTTOM:
            return 'Z';

        default:
            return '?';
    }
}

int chartopiece(char c)
{
    switch(c)
    {
        case ' ':
            return PIECE_SPACE; 
        case '%':
            return PIECE_WALL; 
        case '1':
            return PIECE_PLAYER_ONE; 
        case '2':
            return PIECE_PLAYER_TWO; 
        case '.':
            return PIECE_DOTS; 
        case 'a':
            return PIECE_ARROW_RED_LEFT; 
        case 'b':
            return PIECE_ARROW_RED_UP; 
        case 'c':
            return PIECE_ARROW_RED_RIGHT; 
        case 'd':
            return PIECE_ARROW_RED_DOWN; 
        case 'A':
            return PIECE_BOMB_RED_LEFT; 
        case 'B':
            return PIECE_BOMB_RED_UP; 
        case 'C':
            return PIECE_BOMB_RED_RIGHT; 
        case 'D':
            return PIECE_BOMB_RED_DOWN; 
        case 'e':
            return PIECE_ARROW_GREEN_LEFT; 
        case 'f':
            return PIECE_ARROW_GREEN_UP; 
        case 'g':
            return PIECE_ARROW_GREEN_RIGHT; 
        case 'h':
            return PIECE_ARROW_GREEN_DOWN; 
        case 'E':
            return PIECE_BOMB_GREEN_LEFT; 
        case 'F':
            return PIECE_BOMB_GREEN_UP; 
        case 'G':
            return PIECE_BOMB_GREEN_RIGHT; 
        case 'H':
            return PIECE_BOMB_GREEN_DOWN; 
        case 'i':
            return PIECE_ARROW_BLUE_LEFT; 
        case 'j':
            return PIECE_ARROW_BLUE_UP; 
        case 'k':
            return PIECE_ARROW_BLUE_RIGHT; 
        case 'l':
            return PIECE_ARROW_BLUE_DOWN; 
        case 'I':
            return PIECE_BOMB_BLUE_LEFT; 
        case 'J':
            return PIECE_BOMB_BLUE_UP; 
        case 'K':
            return PIECE_BOMB_BLUE_RIGHT; 
        case 'L':
            return PIECE_BOMB_BLUE_DOWN; 
        case 'o':
            return PIECE_CIRCLE; 
        case '*':
            return PIECE_STAR; 
        case '/':
            return PIECE_DOOR; 
#ifdef ENIGMA_COMPATIBILITY
        case '8':
            return PIECE_CIRCLE_DOUBLE; 
        case ':':
            return PIECE_DOTS_DOUBLE; 
#endif
#ifdef XOR_COMPATIBILITY
        case '-':
            return PIECE_DOTS_X; 
        case '|':
            return PIECE_DOTS_Y; 
        case 'S':
            return PIECE_SWITCH; 
        case 'T':
            return PIECE_TELEPORT; 
        case 'M':
            return PIECE_MAP_TOP_LEFT; 
        case 'm':
            return PIECE_MAP_TOP_RIGHT; 
        case 'N':
            return PIECE_MAP_BOTTOM_LEFT; 
        case 'n':
            return PIECE_MAP_BOTTOM_RIGHT; 
#endif
        case '!':
            return PIECE_GONE;
        case 'p':
            return PIECE_EXPLOSION_RED_LEFT;
        case 'q':
            return PIECE_EXPLOSION_RED_HORIZONTAL;
        case 'r':
            return PIECE_EXPLOSION_RED_RIGHT;
        case 'P':
            return PIECE_EXPLOSION_RED_TOP;
        case 'Q':
            return PIECE_EXPLOSION_RED_VERTICAL;
        case 'R':
            return PIECE_EXPLOSION_RED_BOTTOM;
        case 'u':
            return PIECE_EXPLOSION_GREEN_LEFT;
        case 'v':
            return PIECE_EXPLOSION_GREEN_HORIZONTAL;
        case 'w':
            return PIECE_EXPLOSION_GREEN_RIGHT;
        case 'U':
            return PIECE_EXPLOSION_GREEN_TOP;
        case 'V':
            return PIECE_EXPLOSION_GREEN_VERTICAL;
        case 'W':
            return PIECE_EXPLOSION_GREEN_BOTTOM;
        case 'x':
            return PIECE_EXPLOSION_BLUE_LEFT;
        case 'y':
            return PIECE_EXPLOSION_BLUE_HORIZONTAL;
        case 'z':
            return PIECE_EXPLOSION_BLUE_RIGHT;
        case 'X':
            return PIECE_EXPLOSION_BLUE_TOP;
        case 'Y':
            return PIECE_EXPLOSION_BLUE_VERTICAL;
        case 'Z':
            return PIECE_EXPLOSION_BLUE_BOTTOM;
        default:
            return PIECE_UNKNOWN;
    }
}

char directiontochar(int direction)
{
    switch(direction)
    {
        case MOVE_LEFT:
            return 'l';
        case MOVE_UP:
            return 'u';
        case MOVE_RIGHT:
            return 'r';
        case MOVE_DOWN:
            return 'd';
        case MOVE_SWAP:
            return 's';
        case MOVE_SWAPPED:
            return 'w';
        case MOVE_NONE:
            return 'n';
        default:
            return '?';
    }
}

int chartodirection(char c)
{
    switch(tolower(c))
    {
        case 'l':
            return MOVE_LEFT;
        case 'u':
            return MOVE_UP;
        case 'r':
            return MOVE_RIGHT;
        case 'd':
            return MOVE_DOWN;
        case 's':
            return MOVE_SWAP;
        case 'w':
            return MOVE_SWAPPED;
        case 'n':
            return MOVE_NONE;
        default:
            return MOVE_UNKNOWN;
    }
}

struct level* level_new()
{
    struct level* plevel;
    int i;

    plevel = (struct level*)malloc(sizeof(struct level));

    if(plevel == NULL)
        fatal("Out of memory in level_new()");

    plevel->size_x = 0;
    plevel->size_y = 0;

    plevel->player = 0;

    plevel->data_pieces = NULL;
    plevel->data_moving = NULL;
    plevel->data_previous = NULL;
    plevel->data_previousmoving = NULL;
    plevel->data_detonator = NULL;
    plevel->data_detonatormoving = NULL;
    plevel->data_data = NULL;

    plevel->move_first = NULL;
    plevel->move_last = NULL;
    plevel->move_current = NULL;

    plevel->mover_first = NULL;
    plevel->mover_last = NULL;

    plevel->stack_first = NULL;
    plevel->stack_last = NULL;

    plevel->stars_caught = 0;
    plevel->stars_exploded = 0;
    plevel->stars_total = 0;

    plevel->moves = 0;

    plevel->flags = 0;

#ifdef XOR_COMPATIBILITY
    plevel->switched = 0;
    plevel->mapped = 0;
#endif

    plevel->mode = MODE_CHROMA;

    plevel->level = 0;

    plevel->title = NULL;

    for(i = 0; i < 2; i++)
    {
        plevel->alive[i] = 0;
        plevel->teleport_x[0] = -1;
        plevel->teleport_y[0] = -1;
        plevel->view_teleport_x[0] = 0;
        plevel->view_teleport_y[0] = 0;
    }

    for(i = 0; i < 3; i++)
    {
        plevel->player_x[i] = 0;
        plevel->player_y[i] = 0;
        plevel->view_x[i] = 0;
        plevel->view_y[i] = 0;
    }

    return plevel;
}

struct level* level_create(int size_x, int size_y)
{
    struct level *pnew;
    int x, y;

    pnew = level_new();

    pnew->size_x = size_x;
    pnew->size_y = size_y;
    
    pnew->data_pieces = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
    pnew->data_moving = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
    pnew->data_previous = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
    pnew->data_previousmoving = malloc(sizeof(char) * pnew->size_x * pnew->size_y);  
    pnew->data_detonator = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
    pnew->data_detonatormoving = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
    pnew->data_data = (unsigned int *)malloc(sizeof(unsigned int) * pnew->size_x * pnew->size_y);

    if(pnew->data_pieces == NULL || pnew->data_moving == NULL ||
        pnew->data_previous == NULL || pnew->data_previousmoving == NULL ||
        pnew->data_detonator == NULL || pnew->data_detonatormoving == NULL ||
        pnew->data_data == NULL )
        fatal("Out of memory in level_create()");

    for(y = 0; y < pnew->size_y; y++)
    {
        for(x = 0; x < pnew->size_x; x++)
        { 
            level_setpiece(pnew, x, y, PIECE_WALL);
            level_setmoving(pnew, x, y, MOVE_NONE);
            level_setprevious(pnew, x, y, PIECE_SPACE);
            level_setpreviousmoving(pnew, x, y, MOVE_NONE);
            level_setdetonator(pnew, x, y, PIECE_SPACE);
            level_setdetonatormoving(pnew, x, y, MOVE_NONE);
            level_setdata(pnew, x, y, 0);
        }
    }

    for(y = 1; y < pnew->size_y - 1; y++)
    {
        for(x = 1; x < pnew->size_x - 1; x++)
        { 
            level_setpiece(pnew, x, y, PIECE_SPACE);
        }
    }

    return pnew;
}

void level_delete(struct level* plevel)
{
    struct mover *pmover;
    struct mover *ptmp;
    struct move *pmove;
    struct move *pmovetmp;

    if(plevel == NULL)
    return;
    
    if(plevel->title != NULL)
        free(plevel->title);

    if(plevel->data_pieces != NULL)
        free(plevel->data_pieces);
    if(plevel->data_moving != NULL)
        free(plevel->data_moving);
    if(plevel->data_previous != NULL)
        free(plevel->data_previous);
    if(plevel->data_previousmoving != NULL)
        free(plevel->data_previousmoving);
    if(plevel->data_detonator != NULL)
        free(plevel->data_detonator);
    if(plevel->data_detonatormoving != NULL)
        free(plevel->data_detonatormoving);
    if(plevel->data_data != NULL)
        free(plevel->data_data);

    pmover = plevel->mover_first;
    while(pmover != NULL)
    {
        ptmp = pmover;
        pmover = pmover->next;
        free(ptmp);
    }

    pmover = plevel->stack_first;
    while(pmover != NULL)
    {
        ptmp = pmover;
        pmover = pmover->next;
        free(ptmp);
    }

    pmove = plevel->move_first;
    while(pmove != NULL)
    {
        pmover = pmove->mover_first;
        while(pmover != NULL)
        {
            ptmp = pmover;
            pmover = pmover->next;
            free(ptmp);
        }

        pmovetmp = pmove;
        pmove = pmove->next;
        free(pmovetmp);
    }

    free(plevel);
}

char level_piece(struct level* plevel, int x, int y)
{ 
    if(plevel == NULL || plevel->data_pieces == NULL ||
        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
    return PIECE_WALL;

    return *(plevel->data_pieces + x + y * plevel->size_x);
} 
  
void level_setpiece(struct level* plevel, int x, int y, char piece)
{ 
    if(plevel == NULL || plevel->data_pieces == NULL ||
        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
    return;

    *(plevel->data_pieces + x + y * plevel->size_x) = piece;
}

char level_moving(struct level* plevel, int x, int y)
{
    if(plevel == NULL || plevel->data_moving == NULL ||
        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
    return MOVE_NONE;
    
    return *(plevel->data_moving + x + y * plevel->size_x);
}

void level_setmoving(struct level* plevel, int x, int y, char moving)
{
    if(plevel == NULL || plevel->data_moving == NULL ||
        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
    return;

    *(plevel->data_moving + x + y * plevel->size_x) = moving;
}

char level_previous(struct level* plevel, int x, int y)
{
    if(plevel == NULL || plevel->data_previous == NULL ||
        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
        return PIECE_WALL;
    
    return *(plevel->data_previous + x + y * plevel->size_x);
}

void level_setprevious(struct level* plevel, int x, int y, char previous)
{
    if(plevel == NULL || plevel->data_previous == NULL ||
        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
    return;

    *(plevel->data_previous + x + y * plevel->size_x) = previous;
}

char level_previousmoving(struct level* plevel, int x, int y)
{
    if(plevel == NULL || plevel->data_previousmoving == NULL ||
        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
    return MOVE_NONE;
    
    return *(plevel->data_previousmoving + x + y * plevel->size_x);
}

void level_setpreviousmoving(struct level* plevel, int x, int y, char previousmoving)
{
    if(plevel == NULL || plevel->data_previousmoving == NULL ||
        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
    return;

    *(plevel->data_previousmoving + x + y * plevel->size_x) = previousmoving;
}

char level_detonator(struct level* plevel, int x, int y)
{
    if(plevel == NULL || plevel->data_detonator == NULL ||
        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
    return PIECE_SPACE;
    
    return *(plevel->data_detonator + x + y * plevel->size_x);
}

void level_setdetonator(struct level* plevel, int x, int y, char detonator)
{
    if(plevel == NULL || plevel->data_detonator == NULL ||
        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
    return;

    *(plevel->data_detonator + x + y * plevel->size_x) = detonator;
}

char level_detonatormoving(struct level* plevel, int x, int y)
{
    if(plevel == NULL || plevel->data_detonatormoving == NULL ||
        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
    return MOVE_NONE;

    return *(plevel->data_detonatormoving + x + y * plevel->size_x);
}

void level_setdetonatormoving(struct level* plevel, int x, int y, char moving)
{
    if(plevel == NULL || plevel->data_detonator == NULL ||
        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
    return;

    *(plevel->data_detonatormoving + x + y * plevel->size_x) = moving;
}

unsigned int level_data(struct level* plevel, int x, int y)
{
    if(plevel == NULL || plevel->data_pieces == NULL ||
        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
    return 0;

    return *(plevel->data_data + x + y * plevel->size_x);
}

void level_setdata(struct level* plevel, int x, int y, unsigned int data)
{
    if(plevel == NULL || plevel->data_pieces == NULL ||
        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
    return;

    *(plevel->data_data + x + y * plevel->size_x) = data;
}

struct level* level_copy(struct level* pold)
{
    struct level* pnew;
    struct move* pmove;
    struct mover* pmover;
    int x, y;
    int i;

    pnew = level_new();

    pnew->size_x = pold->size_x;
    pnew->size_y = pold->size_y;

    pnew->player = pold->player;

    pnew->data_pieces = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
    pnew->data_moving = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
    pnew->data_previous = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
    pnew->data_previousmoving = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
    pnew->data_detonator = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
    pnew->data_detonatormoving = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
    pnew->data_data = (unsigned int *)malloc(sizeof(unsigned int) * pnew->size_x * pnew->size_y);

    if(pnew->data_pieces == NULL || pnew->data_moving == NULL ||
        pnew->data_previous == NULL || pnew->data_previousmoving == NULL ||
        pnew->data_detonator == NULL || pnew->data_detonatormoving == NULL ||
        pnew->data_data == NULL )
        fatal("Out of memory in level_copy");

    pnew->mover_first = NULL;
    pnew->mover_last = NULL;

    pnew->stack_first = NULL;
    pnew->stack_last = NULL;

    pnew->stars_caught = pold->stars_caught;
    pnew->stars_exploded = pold->stars_exploded;
    pnew->stars_total = pold->stars_total;

    pnew->moves = pold->moves;
  
    pnew->flags = pold->flags;

#ifdef XOR_COMPATIBILITY
    pnew->switched = pold->switched;
    pnew->mapped = pold->mapped;
#endif

    pnew->mode = pold->mode;

    pnew->level = pold->level;

    level_settitle(pnew, pold->title);

    for(i = 0; i < 2; i ++)
    {
        pnew->alive[i] = pold->alive[i];
        pnew->teleport_x[i] = pold->teleport_x[i];
        pnew->teleport_y[i] = pold->teleport_y[i];
        pnew->view_teleport_x[i] = pold->view_teleport_x[i];
        pnew->view_teleport_y[i] = pold->view_teleport_y[i];
        pnew->player_x[i] = pold->player_x[i];
        pnew->player_y[i] = pold->player_y[i];
        pnew->view_x[i] = pold->view_x[i];
        pnew->view_y[i] = pold->view_y[i];
    }

    for(y = 0; y < pnew->size_y; y++)
    {
        for(x = 0; x < pnew->size_x; x++)
        {
            level_setpiece(pnew, x, y, level_piece(pold, x, y));
            level_setmoving(pnew, x, y, MOVE_NONE);
            level_setprevious(pnew, x, y, PIECE_SPACE);
            level_setpreviousmoving(pnew, x, y, MOVE_NONE);
            level_setdetonator(pnew, x, y, PIECE_SPACE);
            level_setdetonatormoving(pnew, x, y, MOVE_NONE);
            level_setdata(pnew, x, y, level_data(pold, x, y));
        }
    }

    /* Copy moves and undo data */
    pmove = pold->move_first;
    while(pmove != NULL)
    {
        level_addmove(pnew, pmove->direction);

        pnew->move_current = pnew->move_last;

        pmover = pmove->mover_first;
        while(pmover != NULL)
        {
            mover_newundo(pnew, pmover->x, pmover->y, pmover->direction, pmover->piece, pmover->piece_previous, MOVER_STORE | (pmover->fast ? MOVER_FAST : 0));
            pmover = pmover->next;
        }
        pmove = pmove->next;
    }

    pnew->move_current = pnew->move_last;

    return pnew;
}

struct level* level_load(char *filename, int partial)
{
    struct level* plevel;
    FILE *level;
    char buffer[4096];
    int state;
    int x, y, z;
#ifdef XOR_COMPATIBILITY
    int w;
#endif
    int i;
    char c;
    int piece, previous, direction;
    int teleport;
    int move;
    int loop;

    if(!isfile(filename))
        return NULL;

    level = fopen(filename, "r");
    if(level == NULL)
        return NULL;

    plevel = level_new();

    state = 0;
    while(!feof(level))
    {
        file_readline(level, buffer, 4096);

        /* Ignore comments and blank lines */
        if(buffer[0] == '#' || buffer[0] == 0)
        continue;

        /* and everything before the "chroma level" line */
        if(strncmp(buffer, "chroma level", 12) == 0)
        state = 1;
  
        if(state == 1)
        {
            if(strncmp(buffer, "mode: ", 6) == 0)
            {
                plevel->mode = MODE_MAX;
#ifdef XOR_COMPATIBILITY
                if(strncmp(buffer, "mode: xor", 9) == 0)
                    plevel->mode = MODE_XOR;
#endif
#ifdef ENIGMA_COMPATIBILITY
                if(strncmp(buffer, "mode: enigma", 12) == 0)
                    plevel->mode = MODE_ENIGMA;
#endif
                /* Unrecognised mode */
                if(plevel->mode == MODE_MAX)
                {
                    level_delete(plevel);
                    return NULL;
                }
            }
        
            /* Level data comes after level options */
            if(strncmp(buffer, "data:", 5) == 0)
            {
                state = 2;
                break;
            }

            /* Read level size */
            if(strncmp(buffer, "size:", 5) == 0)
            {
                i = sscanf(buffer, "size: %d %d", &x, &y);
                if(i == 2)
                {
                    plevel->size_x = x;
                    plevel->size_y = y; 
                }
            }

            /* Read star totals */
            if(strncmp(buffer,"stars:",6)==0)
            {
                i = sscanf(buffer, "stars: %d %d %d", &x, &y, &z);
                if(i == 3)
                {
                    plevel->stars_caught = x;
                    plevel->stars_exploded = y;
                    plevel->stars_total = z; 
                }
            }

            /* Read move total */
            if(strncmp(buffer,"moves:",6)==0)
            {
                i = sscanf(buffer, "moves: %d", &x);
                if(i == 1)
                    plevel->moves = x; 
            }

            /* Read player */
            if(strncmp(buffer,"player:",7)==0)
            {
                i = sscanf(buffer, "player: %d", &x);
                if(i == 1 && (x == 1 || x == 2))
                    plevel->player = x - 1; 
            }

            /* Read level number */
            if(strncmp(buffer,"level:",6)==0)
            {
                i = sscanf(buffer, "level: %d", &x);
                if(i == 1)
                plevel->level = x;
            }

            /* Read title */
            if(strncmp(buffer,"title: ",7)==0)
                level_settitle(plevel, buffer + 7);

            if(strcmp(buffer, "solved: 1") == 0)
                plevel->flags |= LEVELFLAG_SOLVED;

            if(strcmp(buffer, "failed: 1") == 0)
                plevel->flags |= LEVELFLAG_FAILED;

#ifdef XOR_COMPATIBILITY
            /* Read switched */
            if(strncmp(buffer,"switched:",9)==0)
            {
                i = sscanf(buffer, "switched: %d", &x);
                if(i == 1)
                    plevel->switched = x; 
            }
            /* Read viewpoints */
            if(strncmp(buffer,"view1: ",7)==0)
            {
                i = sscanf(buffer, "view1: %d %d", &x, &y);
                if(i == 2)
                {
                    plevel->view_x[0] = x;
                    plevel->view_y[0] = y;
                }
            }
            if(strncmp(buffer,"view2: ",7)==0)
            {
                i = sscanf(buffer, "view2: %d %d", &x, &y);
                if(i == 2)
                {
                    plevel->view_x[1] = x;
                    plevel->view_y[1] = y;
                }
            }
            if(strncmp(buffer,"viewteleport1: ",15)==0)
            {
                i = sscanf(buffer, "viewteleport1: %d %d (%d %d)", &x, &y, &z, &w);
                if(i == 4)
                {
                    plevel->teleport_x[0] = z;
                    plevel->teleport_y[0] = w;
                    plevel->view_teleport_x[0] = x;
                    plevel->view_teleport_y[0] = y;
                    teleport ++;

                }
            }
            if(strncmp(buffer,"viewteleport2: ",15)==0)
            {
                i = sscanf(buffer, "viewteleport2: %d %d (%d %d)", &x, &y, &z, &w);
                if(i == 4)
                {
                    plevel->teleport_x[1] = z;
                    plevel->teleport_y[1] = w;
                    plevel->view_teleport_x[1] = x;
                    plevel->view_teleport_y[1] = y;
                    teleport ++;
                }
            }
            if(strncmp(buffer, "mapped: ", 8) == 0)
            {
                if(strstr(buffer, "top_left") != NULL)
                    plevel->mapped |= MAPPED_TOP_LEFT;
                if(strstr(buffer, "top_right") != NULL)
                    plevel->mapped |= MAPPED_TOP_RIGHT;
                if(strstr(buffer, "bottom_left") != NULL)
                    plevel->mapped |= MAPPED_BOTTOM_LEFT;
                if(strstr(buffer, "bottom_right") != NULL)
                    plevel->mapped |= MAPPED_BOTTOM_RIGHT;
            }
#endif
        }
    }

    /* If the file ended before the level data started, it's no good */
    if(state != 2)
    {
        fclose(level);
        level_delete(plevel);
        return NULL;
    }

    /* Don't load level or move data if we're indexing for a menu */
    if(partial)
    {
        fclose(level);
        return plevel;
    }

    plevel->data_pieces = malloc(sizeof(char) * plevel->size_x * plevel->size_y);
    plevel->data_moving = malloc(sizeof(char) * plevel->size_x * plevel->size_y);
    plevel->data_previous = malloc(sizeof(char) * plevel->size_x * plevel->size_y);
    plevel->data_previousmoving = malloc(sizeof(char) * plevel->size_x * plevel->size_y);
    plevel->data_detonator = malloc(sizeof(char) * plevel->size_x * plevel->size_y);
    plevel->data_detonatormoving = malloc(sizeof(char) * plevel->size_x * plevel->size_y);
    plevel->data_data = malloc(sizeof(int) * plevel->size_x * plevel->size_y);

    if(plevel->data_pieces == NULL || plevel->data_moving == NULL ||
            plevel->data_previous == NULL || plevel->data_previousmoving == NULL ||
            plevel->data_detonator == NULL || plevel->data_detonatormoving == NULL ||
            plevel->data_data == NULL )
        fatal("Out of memory in level_load");

    /* Seed random number generator based on level title */
    x = 7;
    if(plevel->title != NULL)
    {
        for(i = 0; i < strlen(plevel->title); i ++)
        x = x ^ (plevel->title[i] << (i % 24));
    }
    srand(x);

    for(y = 0; y < plevel->size_y; y++)
    {
        for(x = 0; x < plevel->size_x; x++)
        {
            level_setpiece(plevel, x, y, PIECE_WALL);
            level_setmoving(plevel, x, y, MOVE_NONE);
            level_setprevious(plevel, x, y, PIECE_SPACE);
            level_setpreviousmoving(plevel, x, y, MOVE_NONE);
            level_setdetonator(plevel, x, y, PIECE_SPACE);
            level_setdetonatormoving(plevel, x, y, MOVE_NONE);
            level_setdata(plevel, x, y, rand() % 0xffff);
        }
    }

    /* Recalculate stars_total - use the saved value in partial loads only */
    plevel->stars_total = plevel->stars_caught + plevel->stars_exploded;

    teleport = 0;

    x = 0; y = 0;
    while(!feof(level) && y <= plevel->size_y)
    {
        c = fgetc(level);
        if(feof(level))
        break;
        if(c == 10 || c == 13 || c == 0 || c == -1)
        continue;

        piece = chartopiece(c);
      
        switch(piece)
        {
            case PIECE_PLAYER_ONE:
                plevel->player_x[0] = x;
                plevel->player_y[0] = y;
                plevel->alive[0] = 1;
                break;

            case PIECE_PLAYER_TWO:
                plevel->player_x[1] = x;
                plevel->player_y[1] = y;
                plevel->alive[1] = 1;
                break;

            case PIECE_STAR:
                plevel->stars_total ++;
                break;

#ifdef XOR_COMPATIBILITY
            case PIECE_TELEPORT:
                if(teleport < 2)
                {
                    plevel->teleport_x[teleport] = x;
                    plevel->teleport_y[teleport] = y;
                    teleport ++;
                }
                break;
#endif
            case PIECE_UNKNOWN:
                piece = PIECE_SPACE;
                break;

            default:
                break;
        }

        level_setpiece(plevel, x, y, piece);

        x++;
        if(x == plevel->size_x)
        {
            x = 0;
            y ++;
        }
        if(y == plevel->size_y)
            break;
    }

    /* Search for move data */
    state = 0;
    while(!feof(level))
    {
        file_readline(level, buffer, 4096);
        if(strncmp(buffer,"movedata:",9)==0)
        {
            state = 1;
            break;
        }
    }
    /* and if we find it, read it */
    if(state == 1)
    {
        i = 0;

        while(i < plevel->moves && !feof(level))
        {
            move = chartodirection(fgetc(level));
            if(move != MOVE_NONE && move != MOVE_UNKNOWN)
            {
                level_addmove(plevel, move);
                i ++;
            }
        }
    }

    /* Search for undo data */
    state = 0;
    while(!feof(level))
    {
        file_readline(level, buffer, 4096);
        if(strncmp(buffer,"undodata:",9)==0)
        {
            state = 1;
            break;
        }
    }
    /* and if we find it, read it */
    if(state == 1)
    {
        plevel->move_current = plevel->move_first;

        state = 0;

        x = 0;
        y = 0;
        piece = PIECE_UNKNOWN;
        previous = PIECE_UNKNOWN;
        direction = MOVE_UNKNOWN;

        while(plevel->move_current != NULL && !feof(level))
        {
            c = fgetc(level);

            if(c == 13 || c == 10)
            continue;

            /* Undo data is read by means of a state machine. */
            /* [x]:[y][direction][piece][previous piece][state of next move] */
            loop = 1;
            while(loop)
            {
                loop = 0;
                switch(state)
                {
                    /* Read x */
                    case 0:
                        if(c >= '0' && c <='9')
                        {
                            x = x * 10 + (c - '0');
                        }
                        else
                        {
                            state = 1; loop = 1;
                        }
                        break;

                    /* Read : */
                    case 1:
                        if(c == ':')
                            state = 2;
                        break;

                    /* Read y */
                    case 2:
                        if(c >= '0' && c <='9')
                        {
                            y = y * 10 + (c - '0');
                        }
                        else
                        {
                            state = 3; loop = 1;
                        }
                        break;

                    /* Read direction */
                    case 3:
                        if(chartodirection(c) != MOVE_UNKNOWN)
                        {
                            direction = chartodirection(c);
                            state = 4;
                        }
                        break;

                    /* Read piece */
                    case 4:
                        if(chartopiece(c) != PIECE_UNKNOWN)
                        {
                            piece = chartopiece(c);
                            state = 5;
                        }
                        break;

                    /* Read previous piece */
                    case 5:
                        if(chartopiece(c) != PIECE_UNKNOWN)
                        {
                            previous = chartopiece(c);
                            state = 6;
                        }
                        break;

                    /* Read state of next move */
                    case 6:
                        if(c == ',' || c == ';' || c == '.')
                        {
                            /* Add mover to current move */
                            mover_newundo(plevel, x, y, direction, piece, previous, MOVER_STORE | (c == ',' ? MOVER_FAST : 0));
                            /* Next move if this is the last mover for this one */
                            if(c == '.')
                                plevel->move_current = plevel->move_current->next;

                            /* Reset state machine for next mover */
                            state = 0;
                            x = 0;
                            y = 0;
                            piece = PIECE_UNKNOWN;
                            previous = PIECE_UNKNOWN;
                            direction = MOVE_UNKNOWN;
                        }
                        break;

                    default:
                        break;
            }
        }
    }

    plevel->move_current = plevel->move_last;
    }

    fclose(level);
    
    return plevel;
}

int level_save(struct level* plevel, char *filename, int partial)
{
    FILE *level;
    char c;
    int x, y;
    int i, j;
    char buffer[256];
    struct move* pmove;
    struct mover* pmover;

    level = fopen(filename, "w");
    if(level == NULL)
    return errno;

    fprintf(level, "chroma level\n\n");

#ifdef XOR_COMPATIBILITY
    if(plevel->mode == MODE_XOR)
        fprintf(level, "mode: xor\n\n");
#endif
#ifdef ENIGMA_COMPATIBILITY
    if(plevel->mode == MODE_ENIGMA)
        fprintf(level, "mode: enigma\n\n");
#endif

    if(plevel->title != NULL && strcmp(plevel->title, "") != 0)
        fprintf(level, "title: %s\n", plevel->title);
    if(plevel->level != 0)
        fprintf(level, "level: %d\n", plevel->level);
    fprintf(level, "size: %d %d\n", plevel->size_x, plevel->size_y);

    if(!partial || plevel->flags & LEVELFLAG_SOLVED)
    {
        fprintf(level, "player: %d\n", plevel->player + 1);
        fprintf(level, "moves: %d\n", plevel->moves);
        fprintf(level, "stars: %d %d %d\n", plevel->stars_caught, plevel->stars_exploded, plevel->stars_total);
        if(plevel->flags & LEVELFLAG_SOLVED)
            fprintf(level, "solved: 1\n");
        if(plevel->flags & LEVELFLAG_FAILED)
            fprintf(level, "failed: 1\n");
    }

#ifdef XOR_COMPATIBILITY
    if(plevel->mode == MODE_XOR)
    {
        if(plevel->switched)
            fprintf(level, "switched: %d\n", plevel->switched);
        fprintf(level, "view1: %d %d\n", plevel->view_x[0], plevel->view_y[0]);
        fprintf(level, "view2: %d %d\n", plevel->view_x[1], plevel->view_y[1]);
        if(plevel->teleport_x[0] != -1)
        {
            fprintf(level, "viewteleport1: %d %d (%d %d)\n", plevel->view_teleport_x[0], plevel->view_teleport_y[0], plevel->teleport_x[0], plevel->teleport_y[0]);
            fprintf(level, "viewteleport2: %d %d (%d %d)\n", plevel->view_teleport_x[1], plevel->view_teleport_y[1], plevel->teleport_x[1], plevel->teleport_y[1]);
        }
        if(plevel->mapped)
        {
            fprintf(level, "mapped:");
            if(plevel->mapped & MAPPED_TOP_LEFT)
            fprintf(level, " top_left");
            if(plevel->mapped & MAPPED_TOP_RIGHT)
            fprintf(level, " top_right");
            if(plevel->mapped & MAPPED_BOTTOM_LEFT)
            fprintf(level, " bottom_left");
            if(plevel->mapped & MAPPED_BOTTOM_RIGHT)
            fprintf(level, " bottom_right");
            fprintf(level, "\n");
        }

    }
#endif

    fprintf(level, "\ndata:\n");

    for(y = 0; y < plevel->size_y; y ++)
    {
        for(x = 0; x < plevel->size_x; x ++)
        {
            fputc(piecetochar(level_piece(plevel, x, y)), level);
        }
        fputc('\n', level);
    }
    fputc('\n', level);

    if(plevel->move_first != NULL && plevel->moves != 0)
    {
        fprintf(level, "movedata:\n");

        i = 0;
        pmove = plevel->move_first;
        while(pmove != NULL && i < plevel->moves)
        {
            fputc(directiontochar(pmove->direction), level);
            i ++;

            if(i % 78 == 77 && pmove->next != NULL)
            fputc('\n', level);

            pmove = pmove->next;
        }

        fprintf(level, "\n\n");

        if(!partial)
        {
        fprintf(level, "undodata:\n");

            i = 0; j = 0;
        pmove = plevel->move_first;
        while(pmove != NULL && j < plevel->moves)
        {
            pmover = pmove->mover_first;
            while(pmover != NULL)
            {
            c = ',';
            if(pmover->fast == 0)
                c = ';';
            if(pmover->next == NULL)
                c = '.';

            sprintf(buffer, "%02d:%02d%c%c%c%c", pmover->x, pmover->y, directiontochar(pmover->direction), piecetochar(pmover->piece), piecetochar(pmover->piece_previous), c);
            if(i + strlen(buffer) > 78)
            {
                fprintf(level, "\n");
                i = 0;
            }

            fprintf(level, "%s", buffer);
            i += strlen(buffer);
            pmover = pmover->next;
            }
                j ++;
            pmove = pmove->next;
        }

        fprintf(level, "\n\n");
        }
    }

    fclose(level);

    return 0;
}

void level_addmove(struct level* plevel, int move)
{
    struct move* pmove;
    struct move* ptmp;
    struct mover* pmover;
    struct mover* pmovertmp;

    /* If we are making a move after undoing some moves */
    if(plevel->move_current != plevel->move_last)
    {

    /* Find the first undone move */
    if(plevel->move_current != NULL)
        pmove = plevel->move_current->next;
    else
        pmove = plevel->move_first;

    /* Delete all moves that follow it */
    while(pmove != NULL)
    {
        ptmp = pmove->next;

        /* Delete movers associated with deleted move */
        pmover = pmove->mover_first;
        while(pmover != NULL)
        {
        pmovertmp = pmover->next;
        free(pmover);
        pmover = pmovertmp;
        }

        free(pmove);
        pmove = ptmp;
    }

    /* Fix up this move so that it appears to be the last */
    if(plevel->move_current != NULL)
        plevel->move_current->next = NULL;
    else
        plevel->move_first = NULL;

    plevel->move_last = plevel->move_current;
    }

    /* Create the new move */
    pmove = (struct move*)malloc(sizeof(struct move));
    if(pmove == NULL)
        fatal("Out of memory in level_addmove()");

    pmove->direction = move;
    pmove->previous = plevel->move_last;
    pmove->next = NULL;
    pmove->mover_first = NULL;
    pmove->mover_last = NULL;

    if(plevel->move_first == NULL)
    plevel->move_first = pmove;

    if(plevel->move_last != NULL)
    {
    plevel->move_last->next = pmove;
    pmove->count = plevel->move_last->count + 1;
    }
    else
    pmove->count = 1;

    plevel->move_last = pmove;
    plevel->move_current = pmove;

}

void level_fix(struct level *plevel)
{
    int i, j;
    int teleport;

    for(i = 0; i < 2; i++)
    {
    plevel->alive[i] = 0;
    plevel->teleport_x[i] = -1;
    plevel->teleport_y[i] = -1;
    }

    plevel->stars_caught = 0;
    plevel->stars_exploded = 0;
    plevel->stars_total = 0;

    teleport = 0;

    for(j = 0; j < plevel->size_y; j ++)
    {
        for(i = 0; i < plevel->size_x; i ++)
        {
            switch(level_piece(plevel, i, j))
            {
            case PIECE_PLAYER_ONE:
                plevel->alive[0] = 1;
                plevel->player_x[0] = i;
                plevel->player_y[0] = j;
                break;

            case PIECE_PLAYER_TWO:
                plevel->alive[1] = 1;
                plevel->player_x[1] = i;
                plevel->player_y[1] = j;
                break;

            case PIECE_STAR:
                plevel->stars_total ++;
                break;

#ifdef XOR_COMPATIBILITY
            case PIECE_TELEPORT:
                if(teleport < 2)
                {
                    plevel->teleport_x[teleport] = i;
                    plevel->teleport_y[teleport] = j;
                    teleport ++;
                }
                break;
#endif
            }
        }
    }
}

void level_settitle(struct level* plevel, char *title)
{
    if(plevel->title != NULL)
        free(plevel->title);

    if(title == NULL)
    {
        plevel->title = NULL;
        return;
    }

    plevel->title = malloc(strlen(title) + 1);

    if(plevel->title != NULL)
        strcpy(plevel->title, title);
}

