// -*- C++ -*-
/* $Id: Sprite.cc 1.1 1998/04/23 17:46:25 atterer Exp $
  __   _
  |_) /|  Copyright Richard Atterer
  | \/|  <atterer@informatik.tu-muenchen.de>
   ` 
  Sprite and Spritearea classes.
  OSLib needed.

  $Log: Sprite.cc $
  Revision 1.1  1998/04/23 17:46:25  atterer
  Initial revision

  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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

const char* rcsid_sprite_cc =
"$Id: Sprite.cc 1.1 1998/04/23 17:46:25 atterer Exp $";

// OSLib typedef's "bool", which causes a compiler warning
#include <osfile.h>
#include <osspriteop.h>
#include "Sprite.h"

#ifdef DEBUGGING
  #include <stdio.h>
  #define DEBUG(cmd) { cmd ; }
  #define NODEBUG(cmd) { }
#else
  #define DEBUG(cmd) { }
  #define NODEBUG(cmd) { cmd ; }
#endif

const struct os_error Sprite::error_nomem = {
    Sprite::errnum_nomem, "Out of memory" };

// two bits of bpp information per mode number
#define _Sprite_bits(a, b, c, d) (a | b << 2 | c << 4 | d << 6)
const byte Sprite::modebpp[] = {
  _Sprite_bits(0, 1, 2, 0), _Sprite_bits(0, 1, 0, 2),
  _Sprite_bits(1, 2, 3, 1), _Sprite_bits(2, 3, 2, 3),
  _Sprite_bits(2, 2, 0, 1), _Sprite_bits(2, 3, 2, 0),
  _Sprite_bits(3, 0, 1, 2), _Sprite_bits(3, 0, 1, 2),
  _Sprite_bits(3, 0, 1, 2), _Sprite_bits(3, 0, 1, 2),
  _Sprite_bits(3, 0, 1, 2), _Sprite_bits(0, 1, 2, 0)
};
//______________________________________________________________________

Spritearea::Spritearea(os_error*& error, int size = 16, int extsize = 0)
    : data(0), firstptr(0) {
  if (size < 16) size = 16;
  //DEBUG( printf("Spritearea: Creating sprite area (size %d)\n", size); )
  data = static_cast<osspriteop_area*>(new byte[size]); // allocate
  if (data == 0) {
    error = const_cast<os_error*>(&Sprite::error_nomem);
    delete this;
    return;
  }
  data->size = size;
  data->first = extsize;
  error = xosspriteop_clear_sprites(osspriteop_USER_AREA, data);
  if (error) delete this;
}
//________________________________________

// reserves (file_length + 20 + size) bytes for the area and loads file
Spritearea::Spritearea(os_error*& error, const char* filename,
    int size = 0) : data(0), firstptr(0) {
  //DEBUG(
  //  printf("Spritearea: Creating sprite area with file %s\n", filename))
  fileswitch_object_type obj;
  int filesize;
  error = xosfile_read_no_path(filename, &obj, SKIP, SKIP, &filesize, SKIP);
  if (obj == osfile_NOT_FOUND || obj == osfile_IS_DIR)
    error = xosfile_make_error(filename, obj);
  if (error) return;
  // 4 bytes more because sprite *file* lacks 1st word, 16 bytes for header
  size += filesize + 20;

  // init area as with other constructor
  if (size < 16) size = 16;
  data = static_cast<osspriteop_area*>(new byte[size]); // allocate
  //DEBUG(printf("Spritearea: Area at %X\n", int(data)))
  if (data == 0) {
    error = const_cast<os_error*>(&Sprite::error_nomem);
    return;
  }
  data->size = size;
  data->first = 16; // non-null ext area not supported here
  error = xosspriteop_clear_sprites(osspriteop_USER_AREA, data);
  if (error) return;

  // area has been allocated & initialised => merge file
  error = xosspriteop_merge_sprite_file(osspriteop_USER_AREA,
      data, filename);
  if (error) return;

  // finally, create sprite objects
  rescan(error);
}
//________________________________________

Spritearea::~Spritearea() {
  //DEBUG(printf("~Spritearea: Deleting sprite area at &%X\n",int(this)))
  // delete all sprite *objects*
  Sprite* sprobj = firstptr;
  while (sprobj) {
    /*DEBUG(printf("~Spritearea:   Deleting sprite object at &%X\n",
        int(sprobj)))*/
    Sprite* tmpobj = sprobj;
    sprobj = &sprobj->next();
    /* Delete object data only, not corresponding sprite.
       Cast prevents call to ~Sprite() */
    delete static_cast<byte*>(tmpobj);
  }
  if (data) delete data;
  //DEBUG(printf("~Spritearea:   Area deleted\n"))
}
//________________________________________

/* Create sprite objects to mirror the actual sprite area data. Delete old
   sprite objects unless the sprite name also appears in the new data.
   This must be called after OS_SpriteOp calls that cause the sprite
   addresses to change. */
void Spritearea::rescan(os_error*& error) {
  Sprite* newfirst = 0; // overwrites firstptr at end of method
  Sprite* newprev = 0;
  Sprite* oldspr;
  osspriteop_header* sprdata = static_cast<osspriteop_header*>(
      static_cast<byte*>(data) + data->first); // ptr to current sprite
  for (int i = data->sprite_count; i; i--) {
    //DEBUG(printf("rescan: %d sprites left; first=%X\n", i,int(firstptr)))
    oldspr = firstptr; // old list of sprite objects
    while (oldspr != 0 && !Sprite::samename(sprdata, oldspr->name()))
      oldspr = &oldspr->next(); // look for object with same sprite name
    if (oldspr) {
      //DEBUG(printf("rescan: found\n"))
      // object found in old list; unlink it there
      if (oldspr->prevptr) oldspr->prevptr->nextptr = oldspr->nextptr;
      if (oldspr->nextptr) oldspr->nextptr->prevptr = oldspr->prevptr;
      if (oldspr == firstptr) firstptr = &oldspr->next();
      oldspr->data = sprdata;
      oldspr->areaptr = this;
      oldspr->rawareaptr = data;
    } else {
      //DEBUG(printf("rescan: not found\n"))
      // object with same sprite name not found, so create new one
      oldspr = new Sprite(sprdata, this, newprev);
      if (oldspr == 0) {
        error = const_cast<os_error*>(&Sprite::error_nomem);
        return;
      }
    }
    //DEBUG(printf("rescan: add to new list\n"))
    // now add to new list
    if (newfirst == 0) newfirst = oldspr;
    if (newprev) newprev->nextptr = oldspr;
    oldspr->prevptr = newprev;
    oldspr->nextptr = 0;
    newprev = oldspr;
    sprdata = static_cast<osspriteop_header*>(
        static_cast<byte*>(sprdata) + sprdata->size); // move to next sprite
  }
  // delete remaining objects of old sprite list
  oldspr = firstptr;
  firstptr = newfirst; // new list;
  while (oldspr) {
    Sprite* tmpobj = oldspr;
    oldspr = &oldspr->next();
    delete static_cast<byte*>(tmpobj); // don't call ~Sprite()
  }
  return;
}
//______________________________________________________________________

Sprite::~Sprite() {
  xosspriteop_delete_sprite(osspriteop_PTR, rawareaptr,
      static_cast<osspriteop_id>(data));
  // remove from linked list
  if (prevptr) prevptr->nextptr = nextptr;
  if (nextptr) nextptr->prevptr = prevptr;
  if (this == areaptr->firstptr) areaptr->firstptr = nextptr;
  return;
}
//________________________________________

// compare (possibly unterminated) sprite name with string
bool Sprite::samename(osspriteop_header* rawsprite,
    const char* name) {
  const char* rawname = rawsprite->name;
  DEBUG(
    char tmpchar = rawname[12];
    const_cast<char*>(rawname)[12] = 0;
    //printf("samename: compare raw %s with string %s\n", rawname, name);
    const_cast<char*>(rawname)[12] = tmpchar)
  if (rawname[0] != name[0] ||
      rawname[1] != name[1]) return false;
  if (rawname[1] == 0) return true;
  if (rawname[2] != name[2]) return false;
  if (rawname[2] == 0) return true;
  if (rawname[3] != name[3]) return false;
  if (rawname[3] == 0) return true;
  if (rawname[4] != name[4]) return false;
  if (rawname[4] == 0) return true;
  if (rawname[5] != name[5]) return false;
  if (rawname[5] == 0) return true;
  if (rawname[6] != name[6]) return false;
  if (rawname[6] == 0) return true;
  if (rawname[7] != name[7]) return false;
  if (rawname[7] == 0) return true;
  if (rawname[8] != name[8]) return false;
  if (rawname[8] == 0) return true;
  if (rawname[9] != name[9]) return false;
  if (rawname[9] == 0) return true;
  if (rawname[10] != name[10]) return false;
  if (rawname[10] == 0) return true;
  if (rawname[11] != name[11]) return false;
  if (rawname[11] == 0 || name[12] == 0) return true; else return false;
}
