/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2010 Robert Bieber
 *
 * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include "rbfont.h"
#include "rbfontcache.h"
#include "rbtextcache.h"

#include <QFont>
#include <QBrush>
#include <QFile>
#include <QPainter>
#include <QBitmap>
#include <QImage>
#include <QSettings>

#include <QDebug>

quint16 RBFont::maxFontSizeFor16BitOffsets = 0xFFDB;

RBFont::RBFont(QString file)
    : valid(false), imageData(0), offsetData(0), widthData(0)
{

    bool badFile = false;

    /* Attempting to locate the correct file name */
    if(!QFile::exists(file))
    {
        /* Checking in the fonts repository */
        QSettings settings;
        settings.beginGroup("RBFont");

        file = file.split("/").last();
        file = settings.value("fontDir", "").toString() + "/" + file;

        settings.endGroup();

        if(!QFile::exists(file))
        {
            file = ":/fonts/08-Schumacher-Clean.fnt";

            badFile = true;
        }
    }
    header.insert("filename", file);

    /* Checking for a cache entry */
    RBFontCache::CacheInfo* cache = RBFontCache::lookup(file);
    if(cache)
    {
        imageData = cache->imageData;
        offsetData = cache->offsetData;
        widthData = cache->widthData;
        header = cache->header;

        if(!badFile)
            valid = true;

        return;
    }

    /* Opening the file */
    QFile fin(file);
    fin.open(QFile::ReadOnly);

    /* Loading the header info */
    quint8 byte;
    quint16 word;
    quint32 dword;

    QDataStream data(&fin);
    data.setByteOrder(QDataStream::LittleEndian);

    /* Grabbing the magic number and version */
    data >> dword;
    header.insert("version", dword);

    /* Max font width */
    data >> word;
    header.insert("maxwidth", word);

    /* Font height */
    data >> word;
    header.insert("height", word);

    /* Ascent */
    data >> word;
    header.insert("ascent", word);

    /* Padding */
    data >> word;

    /* First character code */
    data >> dword;
    header.insert("firstchar", dword);

    /* Default character code */
    data >> dword;
    header.insert("defaultchar", dword);

    /* Number of characters */
   data >> dword;
   header.insert("size", dword);

   /* Bytes of imagebits in file */
   data >> dword;
   header.insert("nbits", dword);

   /* Longs (dword) of offset data in file */
   data >> dword;
   header.insert("noffset", dword);

   /* Bytes of width data in file */
   data >> dword;
   header.insert("nwidth", dword);

   /* Loading the image data */
   imageData = new quint8[header.value("nbits").toInt()];
   data.readRawData(reinterpret_cast<char*>(imageData),
                    header.value("nbits").toInt());

   /* Aligning on 16-bit boundary */
   if(header.value("nbits").toInt() % 2 == 1)
       data >> byte;

   /* Loading the offset table if necessary */
   if(header.value("noffset").toInt() > 0)
   {
       int bytesToRead;
       if(header.value("nbits").toInt() > maxFontSizeFor16BitOffsets)
           bytesToRead = 4 * header.value("noffset").toInt();
       else
           bytesToRead = 2 * header.value("noffset").toInt();
       offsetData = new quint16[bytesToRead];
       data.readRawData(reinterpret_cast<char*>(offsetData), bytesToRead);
   }

   /* Loading the width table if necessary */
   if(header.value("nwidth").toInt() > 0)
   {
       widthData = new quint8[header.value("nwidth").toInt()];
       data.readRawData(reinterpret_cast<char*>(widthData),
                        header.value("nwidth").toInt());
   }

   fin.close();

   /* Caching the font data */
   cache = new RBFontCache::CacheInfo;
   cache->imageData = imageData;
   cache->offsetData = offsetData;
   cache->widthData = widthData;
   cache->header = header;
   RBFontCache::insert(file, cache);

   if(!badFile)
       valid = true;

}

RBFont::~RBFont()
{
}

RBText* RBFont::renderText(QString text, QColor color, int viewWidth,
                           QGraphicsItem *parent)
{

    /* Checking for a cache hit first */
    QImage* image = RBTextCache::lookup(header.value("filename").toString()
                                        + text);
    if(image)
        return new RBText(image, viewWidth, parent);

    int firstChar = header.value("firstchar").toInt();
    int height = header.value("height").toInt();
    int maxWidth = header.value("maxwidth").toInt();

    bool extendedSet = header.value("nbits").
                       toUInt() > maxFontSizeFor16BitOffsets;

    /* First we determine the width of the combined text */
    QList<int> widths;
    for(int i = 0; i < text.length(); i++)
    {
        if(widthData)
            widths.append(widthData[text[i].unicode() - firstChar]);
        else
            widths.append(maxWidth);
    }

    int totalWidth = 0;
    for(int i = 0; i < widths.count(); i++)
        totalWidth += widths[i];

    image = new QImage(totalWidth, height, QImage::Format_Indexed8);

    image->setColor(0, qRgba(0,0,0,0));
    image->setColor(1, color.rgb());

    /* Drawing the text */
    int startX = 0;
    for(int i = 0; i < text.length(); i++)
    {
        unsigned int offset;
        if(offsetData)
        {
            if(extendedSet)
                offset = reinterpret_cast<quint32*>(offsetData)[text[i].unicode() - firstChar];
            else
                offset = offsetData[text[i].unicode() - firstChar];
        }
        else
        {
            offset = (text[i].unicode() - firstChar) * maxWidth;
        }

        int bytesHigh = height / 8;
        if(height % 8 > 0)
            bytesHigh++;

        int bytes = bytesHigh * widths[i];

        for(int byte = 0; byte < bytes; byte++)
        {
            int x = startX + byte % widths[i];
            int y = byte / widths[i] * 8;
            quint8 data = imageData[offset];
            quint8 mask = 0x1;
            for(int bit = 0; bit < 8; bit++)
            {
                if(mask & data)
                    image->setPixel(x, y, 1);
                else
                    image->setPixel(x, y, 0);

                y++;
                mask <<= 1;
                if(y >= height)
                    break;
            }

            offset++;
        }

        startX += widths[i];
    }

    RBTextCache::insert(header.value("filename").toString() + text, image);
    return new RBText(image, viewWidth, parent);

}
