/*  main.cpp

    Copyright (c)2002 by Thomas Kindler, thomas.kindler@gmx.de

    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. Read the
    full License at http://www.gnu.org/copyleft for more details.
*/

// include files -----
//
#include "getopt.h"
#include <stdio.h>
#include <stdlib.h>

#include <qfile.h>
#include <qimage.h>
#include <qfileinfo.h>

#include "fontstruct.h"

typedef struct {
  unsigned short  fontType;
  unsigned short  firstChar;
  unsigned short  lastChar;
  unsigned short  maxWidth;
  unsigned short  kernMax;
  unsigned short  nDescent;
  unsigned short  fRectWidth;
  unsigned short  fRectHeight;
  unsigned short  owTLoc;
  unsigned short  ascent;
  unsigned short  descent;
  unsigned short  leading;
  unsigned short  rowWords;
} PalmOsFont;


typedef struct {
  char           name[32];
  unsigned short attributes;
  unsigned short version;
  unsigned int   createDate;
  unsigned int   modDate;
  unsigned int   backupDate;
  unsigned int   modNumber;
  unsigned int   appInfoArea;
  unsigned int   sortInfoArea;
  char           dbType[4];
  char           creatorId[4];
  unsigned int   uniqueIdSeed;
  unsigned int   nextRecordListId;
  unsigned short numRecords;
} PalmOsDbHeader;


typedef struct {
  unsigned int   offset;
  unsigned char  attributes;
  unsigned char  uniqueId[3];
} PalmOsDbRecord;


// command line options
//
char      opt_format[8] = "fnt";
unsigned  opt_fgColor    = 0xffffff;
unsigned  opt_bgColor    = 0x000000;
int       opt_firstChar  = 32;
bool      opt_help       = false;

CmdOpt options[] = {
  { "f",  "%4s", &opt_format,    "Output format <fnt|bmp>"            },
  { "a",  "%d",  &opt_firstChar, "Ascii offset (default: 32)"         },
  { "fg", "%x",  &opt_fgColor,   "Foreground color (default: ffffff)" },
  { "bg", "%x",  &opt_bgColor,   "Background color (default: 000000)" },
  { "h",  NULL,  &opt_help,      "Show command help"                  },
  NULL
};


QList<QImage>  tiles;
Font           font;


void myMsgHandler(QtMsgType type, const char *msg)
{
  switch ( type ) {
    case QtDebugMsg:
      fprintf( stderr, "Debug: %s\n", msg );
      break;
    case QtWarningMsg:
      fprintf( stderr, "Warning: %s\n", msg );
      break;
    case QtFatalMsg:
      fprintf( stderr, "Fatal: %s\n", msg );
      exit(1);  // dump core on purpose
  }
}


bool loadImage(QString filename)
{
  QImage img(filename);
  if (img.isNull())
    return false;

  // scan image for character tiles
  //
  for (int y=0; y<img.height(); y++) {
    int rowHeight = 0;

    for (int x=0; x<img.width(); x++) {
      int w=0, h=0;
      while (x+w<img.width() && img.pixel(x+w, y) != opt_bgColor)
        w++;
      while (y+h<img.height() && img.pixel(x, y+h) != opt_bgColor)
        h++;

      if (w>0 && h>0) {
        // copy character to 1bpp image
        //
        QImage *tile;
        tile = new QImage(w, h, 1, 2, QImage::LittleEndian);
        for (int u=0; u<w; u++) {
          for (int v=0; v<h; v++) {
            if (img.pixel(x+u, y+v) == opt_fgColor)
              tile->setPixel(u, v, 1);
            else  
              tile->setPixel(u, v, 0);
          }
        }
        tiles.append(tile);

        // skip to next character
        //
        x += w;       
        font.pageSize += w;
        if (h>rowHeight)      rowHeight = h;
        if (w>font.maxWidth)  font.maxWidth = w;
        if (h>font.height)    font.height = h;
      }
    }
    // skip to next row of characters
    //
    y += rowHeight;
  } 

  font.firstChar = opt_firstChar;
  font.numChars  = tiles.count();
  font.numPages  = (font.height+7)/8;
  return true;
}


bool loadPalmFont(QString filename)
{  
  QFile  file(filename);
  if (!file.open(IO_ReadOnly))
    qFatal("Couldn't open input file.");

  QDataStream  stream(&file);
  stream.setByteOrder(QDataStream::BigEndian);
  

  PalmOsDbHeader podbh;
  stream.readRawBytes((char*)&podbh, sizeof(podbh));
  

  // read the font header
  //
  PalmOsFont  pft = {0};
  stream >> pft.fontType;
  stream >> pft.firstChar;
  stream >> pft.lastChar;
  stream >> pft.maxWidth;
  stream >> pft.kernMax;
  stream >> pft.nDescent;
  stream >> pft.fRectWidth;
  stream >> pft.fRectHeight;
  stream >> pft.owTLoc;
  stream >> pft.ascent;
  stream >> pft.descent;
  stream >> pft.leading;
  stream >> pft.rowWords;
  if (pft.fontType != 0x9000)
    return false;

  font.firstChar = pft.firstChar;
  font.numChars  = pft.lastChar-pft.firstChar + 1;
  font.maxWidth  = pft.maxWidth;
  font.height    = pft.fRectHeight;
  font.numPages  = (font.height+7)/8;
  
  // read the bit image table
  //
  QImage img(pft.rowWords*16, font.height, 1, 2, QImage::BigEndian);

  for (int y=0; y<font.height; y++) {
    char *scanline = (char*)img.scanLine(y);
    stream.readRawBytes(scanline, pft.rowWords*2);
  }

  // slice'n dice
  //
  stream >> font.pageSize;
  for (int i=0; i<font.numChars; i++) {
    unsigned short offset;
    stream >> offset;

    int w = offset - font.pageSize;
    int h = font.height; 

    QImage *tile;
    tile = new QImage(w, h, 1, 2, QImage::LittleEndian);
  
    for (int u=0; u<w; u++)
      for (int v=0; v<h; v++)
        tile->setPixel(u, v, img.pixelIndex(font.pageSize+u, v));

    tiles.append(tile);
    font.pageSize += w;
  }

  return true;
}


bool saveFont(QString filename)
{
  // copy all tiles to a single bitmap
  //
  QImage  img(font.pageSize, font.height, 1, 2, QImage::LittleEndian);
  
  int x=0;
  QListIterator<QImage> iter(tiles);

  for (iter.toFirst(); *iter; ++iter) {
    for (int u=0; u<(*iter)->width(); u++)
      for (int v=0; v<(*iter)->height(); v++)
        img.setPixel(x+u, v, (*iter)->pixelIndex(u,v));
    x += (*iter)->width();
  } 
  
  // save font file
  //
  QFile file(filename);
  if (!file.open(IO_WriteOnly))
    qFatal("couldn't open output file.");

  QDataStream stream(&file);
  stream.setByteOrder(QDataStream::BigEndian);
  
  unsigned long magic = FONT_MAGIC;
  unsigned long size  = 0;
  stream << (Q_INT32)magic;
  stream << (Q_INT32)size;
  stream.writeRawBytes((char*)&font, sizeof(font));  
  
  unsigned short offset = 0;
  for (iter.toFirst(); *iter; ++iter) {
    stream << (Q_INT16)offset;
    offset += (*iter)->width();
  }
  stream << (Q_INT16)offset;
  
  for (int p=0; p<font.numPages; p++) {
    for (int x=0; x<font.pageSize; x++) {
      char c = 0; 
      for (int y=0; y<8; y++) {
        if (y+p*8 < font.height)
          if (img.pixelIndex(x, y+p*8))
            c |= (1<<y);
      }
      stream << (Q_INT8)c;
    }
  }

  file.at(4);
  stream << (Q_INT32)file.size()-8;
  
  return true;
}


bool saveImage(QString filename)
{
  // try to make editing easier by choosing nice image dimensions.
  //
  int w = 16*(font.maxWidth+2) * 1.5;
  int h = ((font.numChars+15)/16)*(font.height+2) * 1.5;
  int x = (font.firstChar & 15)*(font.maxWidth+2) + w/8;
  int y = h/8;
  int i = font.firstChar;
  
  QImage img(w, h, 8, 16);
  img.fill(0);
  img.setColor(0,  opt_bgColor);
  img.setColor(1,  0xff0000ff);
  img.setColor(15, opt_fgColor);

  QListIterator<QImage> iter(tiles);
  for (iter.toFirst(); *iter; ++iter) {
    if (!(*iter)->isNull()) {
      for (int u=0; u<(*iter)->width(); u++)
        for (int v=0; v<(*iter)->height(); v++)
          if ((*iter)->pixelIndex(u, v))
            img.setPixel(x+u, y+v, 15);
          else
            img.setPixel(x+u, y+v,  1);
    } else {
      img.setPixel(x, y, 1);
    }

    x += font.maxWidth+2;
    if ((i++ & 15) == 15) {
      x  = w/8;
      y += font.height+2;
    }
  }  

  if (!img.save(filename, "BMP"))
    qFatal("couldn't save output file.");
  
  return true;
}


int main(int argc, char *argv[])
{
  qInstallMsgHandler(myMsgHandler);
  
  int firstarg = getopt(argc, argv, options);
  
  if (!firstarg || opt_help || argc-firstarg < 1 || argc-firstarg > 2) {
    printf(
      "Usage   : test [opt1] [opt..] <filename> [output]\n\n"
    );
    getopt_help(argv[0], options);
    return 0;
  }

  // set alpha values to 0xff.
  //
  opt_bgColor |= 0xff000000;
  opt_fgColor |= 0xff000000;

  // parse input and output file names.
  //
  QFileInfo fii, fio;

  fii.setFile(argv[firstarg]);
  if (!fii.exists())
    qFatal("input file doesn't exist.");

  if (argc - firstarg == 2) {
    fio = QString(argv[firstarg+1]);
  } else {
    fio = fii.baseName()+"."+opt_format;
  }

  // convert file.
  // 
  if (!loadImage(fii.filePath())   &&
      !loadPalmFont(fii.filePath()) )
    qFatal("invalid input file format.");
  
  if (!stricmp(opt_format, "fnt")) {
    saveFont(fio.filePath());
  } else if (!stricmp(opt_format, "bmp")) {
    saveImage(fio.filePath());
  } else {
    qFatal("invalid output file format.");
  }

  return 0;
}
