
//***************************************************
// Generalised Bitmap Module C++ adapter for GBM plugins
//
// Requires: GBM.DLL version 1.35 or higher (with multipage support)
//
// Copyright (C) 2006-2008 Heiko Nitzsche
//
//   This software is provided 'as-is', without any express or implied
//   warranty.  In no event will the author be held liable for any damages
//   arising from the use of this software.
//
//   Permission is granted to anyone to use this software for any purpose,
//   including commercial applications, and to alter it and redistribute it
//   freely, subject to the following restrictions:
//
//   1. The origin of this software must not be misrepresented; you must not
//      claim that you wrote the original software. If you use this software
//      in a product, an acknowledgment in the product documentation would be
//      appreciated but is not required.
//   2. Altered source versions must be plainly marked as such, and must not be
//      misrepresented as being the original software.
//   3. This notice may not be removed or altered from any source distribution.
//
//***************************************************

#ifdef __IBMCPP__
  #pragma strings( readonly )
#endif

#include "os2defs.h"

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

#include "GbmDocument.hpp"

// ---------------------------------------------------------
// ---------------------------------------------------------

static BOOL isEqualLowerCase(const char *s1, const char *s2, int n)
{
  for ( ; n--; s1++, s2++ )
  {
    if ( tolower(*s1) != tolower(*s2) )
    {
      return FALSE;
    }
  }
  return TRUE;
}

// ---------------------------------------------------------

static const char *findWord(const char *str, const char *substr)
{
         char * buf, *s;
         char   strbuf[51];
         int    len            = strlen(substr);
   const int    strlength      = strlen(str);
   const BOOL   needsDynBuffer = (strlength > (sizeof(strbuf)-1));

   /* only use dynamic buffer if needed to speedup a bit */
   if (needsDynBuffer)
   {
     buf = (char *) malloc(strlength + 1);
     if (buf == NULL)
     {
       return NULL;
     }
   }
   else
   {
     buf = strbuf;
   }

   for ( s  = strtok(strcpy(buf, str), " \t,");
         s != NULL;
         s  = strtok(NULL, " \t,") )
   {
     if ( isEqualLowerCase(s, substr, len) && s[len] == '\0' )
     {
       int inx = s - buf;
       if (needsDynBuffer)
       {
         free(buf);
       }
       return str + inx;
     }
   }
   if (needsDynBuffer)
   {
     free(buf);
   }
   return NULL;
}

// ---------------------------------------------------------

// Lookup GBM file type. If extension is specified, use this one
// and ignore the filename.

static int lookupGbmFileType(const GbmAccessor & gbmAccessor, const char * filename, const char * extension)
{
    if (extension != NULL)
    {
        int numFileTypes = -1;

        if (gbmAccessor.Gbm_query_n_filetypes(&numFileTypes) == GBM_ERR_OK)
        {
          int   ft;
          GBMFT gbmft;

          for (ft = 0; ft < numFileTypes; ft++)
          {
            if (gbmAccessor.Gbm_query_filetype(ft, &gbmft) == GBM_ERR_OK)
            {
              if (findWord(gbmft.extensions, extension) != NULL)
              {
                return ft;
              }
            }
          }
        }
    }
    else
    {
        int ft;
        if (gbmAccessor.Gbm_guess_filetype(filename, &ft) == GBM_ERR_OK)
        {
            // additionally check if the header can be decoded
            int     fd = -1;
            GBM     gbm;
            GBM_ERR gbmRc;

            if ( (fd = gbmAccessor.Gbm_io_open(filename, GBM_O_RDONLY)) == -1 )
            {
                return -1;
            }
            gbmRc = gbmAccessor.Gbm_read_header(filename, fd, ft, &gbm, "");

            gbmAccessor.Gbm_io_close(fd);

            if (gbmRc == GBM_ERR_OK)
            {
                return ft;
            }
        }

        // last chance is to test load the header
        int fd = -1;

        if ( (fd = gbmAccessor.Gbm_io_open(filename, GBM_O_RDONLY)) == -1 )
        {
            return -1;
        }

        int numFileTypes = -1;

        if (gbmAccessor.Gbm_query_n_filetypes(&numFileTypes) == GBM_ERR_OK)
        {
          GBM gbm;
          int formatCounter = 0;
          int foundFormat   = -1;

          for (ft = 0; ft < numFileTypes; ft++)
          {
            if (gbmAccessor.Gbm_read_header(filename, fd, ft, &gbm, "") == GBM_ERR_OK)
            {
                // There might be some formats that overlap, so count the
                // detected format types. If we find more than 1, we have
                // to decode the data to be sure to find the right format.
                formatCounter++;
                foundFormat = ft;
            }
          }

          gbmAccessor.Gbm_io_close(fd);

          if (formatCounter == 0)
          {
              // nothing found
              return -1;
          }

          if (formatCounter == 1)
          {
              // found the format, don't need to decode the data
              return foundFormat;
          }

          for (ft = 0; ft < numFileTypes; ft++)
          {
              // Now try to decode the data and take the first successfully decoded format.
              try
              {
                  GbmDocumentPage testPage(gbmAccessor);

                  // test loading always page 0
                  testPage.loadFromFile(filename, 0, ft, FALSE, FALSE);

                  // no exception -> we found the correct format
                  return ft;
              }
              catch(GbmException & ex)
              {
              }
          }
        }
    }

    return -1;
}

// ---------------------------------------------------------
// ---------------------------------------------------------

GbmDocument::GbmDocument(const GbmAccessor & accessor)
    : mGbmAccessor(accessor),
      pages(NULL),
      numPages(0),
      filename(NULL),
      force24bpp(FALSE),
      gbmFileType(-1)
{
    memset(&formatDescription, 0, sizeof(GBMFT));
}

// ---------------------------------------------------------

GbmDocument::~GbmDocument()
{
    deleteAllPages();

    delete [] this->filename;
    this->filename = NULL;

    delete [] formatDescription.short_name;
    formatDescription.short_name = NULL;

    delete [] formatDescription.long_name;
    formatDescription.long_name = NULL;

    delete [] formatDescription.extensions;
    formatDescription.extensions = NULL;
}

// ---------------------------------------------------------

void GbmDocument::deleteAllPages()
{
    if (numPages > 0)
    {
        for (int p = 0; p < numPages; p++)
        {
            delete pages[p];
            pages[p] = NULL;
        }
        delete [] pages;
        pages = NULL;
        numPages = 0;
    }
}

// ---------------------------------------------------------

// Checks if the file contains a supported image format.
//
// If fileExtension is specified, only the filename is parsed
// the extension and then the extension is checked.
// If fileExtension is NULL, the file content is analyzed.

BOOLEAN GbmDocument::isSupportedFormat(const char * filename,
                                       const char * fileExtension) const
{

    if (lookupGbmFileType(mGbmAccessor, filename, fileExtension) == -1)
    {
        return FALSE;
    }
    return TRUE;
}

// ---------------------------------------------------------

BOOLEAN GbmDocument::hasContent() const
{
    return (numPages > 0);
}

// ---------------------------------------------------------

// Sets the file as active document. The existing document content is replaced.
//
// If fileExtension is specified, only the filename is parsed
// the extension and then the extension is checked.
// If fileExtension is NULL, the file content is analyzed.

void GbmDocument::setDocumentFile(const char    * filename,
                                  const char    * fileExtension,
                                  const BOOLEAN   allowDelayedDataLoading,
                                  const BOOLEAN   force24bpp) throw ( GbmException )
{
    delete [] this->filename;
    this->filename = NULL;

    // alloc memory for the filename and copy the filename to the buffer
    this->filename = new char[strlen(filename) + 1];
    strcpy(this->filename, filename);

    this->force24bpp = force24bpp;

    gbmFileType = lookupGbmFileType(mGbmAccessor, filename, fileExtension);
    if (gbmFileType == -1)
    {
        throw GbmException(GBM_ERR_NOT_SUPP, "File format is not supported");
    }

    deleteAllPages();

    // forward a thrown exception to the caller
    readFileFormatDescriptionFromFile();

    // forward a thrown exception to the caller
    numPages = readNumberOfPagesFromFile();

    // allocate the required number of pages
    try
    {
        pages = new GbmDocumentPage*[numPages];
    }
    catch(...)
    {
    }
    // for compiler that don't throw std::bad_allow (e.g. IBM VAC)
    if (pages == NULL)
    {
        throw GbmException(GBM_ERR_MEM);
    }

    BOOL allocationError = FALSE;
    try
    {
        for (int p = 0; p < numPages; p++)
        {
            pages[p] = NULL;
            pages[p] = new GbmDocumentPage(mGbmAccessor);
            allocationError = allocationError || (pages[p] == NULL);
        }
    }
    catch(...)
    {
        allocationError = TRUE;
    }
    // for compiler that don't throw std::bad_allow (e.g. IBM VAC)
    if (allocationError)
    {
        deleteAllPages();
        throw GbmException(GBM_ERR_MEM);
    }

    if (numPages > 0)
    {
        try
        {
            // read at least the first page to verify image data decoding
            pages[0]->loadFromFile(filename,
                                   0,
                                   gbmFileType,
                                   FALSE, // enforce full decoding
                                   force24bpp);

            // read the remaining pages which can use delayed data loading
            for (int p = 1; p < numPages; p++)
            {
                // forward a thrown exception to the caller
                pages[p]->loadFromFile(filename,
                                       p,
                                       gbmFileType,
                                       allowDelayedDataLoading,
                                       force24bpp);
            }
        }
        catch(GbmException & ex)
        {
            deleteAllPages();
            throw ex;
        }
    }
}

// ---------------------------------------------------------

void GbmDocument::writeDocumentPageToFile(const char * filename,
                                          const char * options,
                                          const int    pageNumber) throw ( GbmException )
{
    GbmDocumentPage & page(getPage(pageNumber));
    page.writeToFile(filename, options);
}

// ---------------------------------------------------------

const GbmAccessor & GbmDocument::getGbmAccessor()
{
    return mGbmAccessor;
}

// ---------------------------------------------------------

GbmRenderer & GbmDocument::getRenderer()
{
    return renderer;
}

// ---------------------------------------------------------

GbmDocumentPage & GbmDocument::getPage(const int pageNumber) throw ( GbmException )
{
    if (pageNumber < 0 || pageNumber >= numPages)
    {
        throw GbmException(GBM_ERR_READ, "Requested page is not available");
    }

    // check if the page has already been loaded
    if (! pages[pageNumber]->isValid())
    {
        // unregister clear the renderer cache as we replace page data
        if (renderer.getRenderPage() == pages[pageNumber])
        {
            renderer.setRenderPage(NULL);
        }

        // load the page data

        // forward a thrown exception to the caller
        pages[pageNumber]->loadFromFile(filename, pageNumber, gbmFileType, TRUE, force24bpp);
    }

    return *pages[pageNumber];
}

// ---------------------------------------------------------

int GbmDocument::getNumberOfPages() const
{
    return numPages;
}

// ---------------------------------------------------------

const char * GbmDocument::getShortDescription() const
{
    if (hasContent())
    {
        return formatDescription.short_name;
    }
    return "";
}

const char * GbmDocument::getLongDescription() const
{
    if (hasContent())
    {
        return formatDescription.long_name;
    }
    return "";
}

const char * GbmDocument::getExtensions() const
{
    if (hasContent())
    {
        return formatDescription.extensions;
    }
    return "";
}

// ---------------------------------------------------------

void GbmDocument::readFileFormatDescriptionFromFile() throw ( GbmException )
{
    int     fd = -1;
    GBM_ERR gbmRc;

    delete [] formatDescription.short_name;
    formatDescription.short_name = NULL;

    delete [] formatDescription.long_name;
    formatDescription.long_name = NULL;

    delete [] formatDescription.extensions;
    formatDescription.extensions = NULL;

    GBMFT gbmft;
    gbmRc = mGbmAccessor.Gbm_query_filetype(gbmFileType, &gbmft);
    if (gbmRc != GBM_ERR_OK)
    {
        throw GbmException(gbmRc, "Can't extract file format description");
    }

    formatDescription.short_name = new char[strlen(gbmft.short_name) + 1];
    strcpy(formatDescription.short_name, gbmft.short_name);

    formatDescription.long_name = new char[strlen(gbmft.long_name) + 1];
    strcpy(formatDescription.long_name, gbmft.long_name);

    formatDescription.extensions = new char[strlen(gbmft.extensions) + 1];
    strcpy(formatDescription.extensions, gbmft.extensions);
}

// ---------------------------------------------------------

int GbmDocument::readNumberOfPagesFromFile() const throw ( GbmException )
{
    int     fd = -1;
    GBM_ERR gbmRc;

    if ( (fd = mGbmAccessor.Gbm_io_open(filename, GBM_O_RDONLY)) == -1 )
    {
        throw GbmException(GBM_ERR_READ, "Can't open file");
    }

    /* call gbm_read_imgcount(const char *fn, int fd, int ft, int *pimgcnt) */
    int pageCount = 1;

    gbmRc = mGbmAccessor.Gbm_read_imgcount(filename, fd, gbmFileType, &pageCount);
    if (gbmRc != GBM_ERR_OK)
    {
        mGbmAccessor.Gbm_io_close(fd);
        throw GbmException(gbmRc, "Can't read number of images");
    }
    mGbmAccessor.Gbm_io_close(fd);
    return pageCount;
}



