/*  This file is part of the ksquirrel-libs (http://ksquirrel.sf.net)

    Copyright (c) 2004,2005 Dmitry Baryshev <ksquirrel@tut.by>
    
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License (LGPL) as published by the Free Software Foundation;
    either version 2 of the License, or (at your option) any later
    version.
			
    This library 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
    Lesser General Public License for more details.
					
    You should have received a copy of the GNU Library General Public License
    as32 with this library; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "ksquirrel-libs/fmt_types.h"
#include "ksquirrel-libs/fileio.h"
#include "ksquirrel-libs/error.h"
#include "ksquirrel-libs/fmt_utils.h"

#include "fmt_codec_gif_defs.h"

extern "C" {
#include "gif_lib.h"
}

#include "fmt_codec_gif.h"

#include "../xpm/codec_gif.xpm"

/* GIFLIB_MAJOR is only defined in libgif >= 4.2.0 */ 
/* libgif 4.2.0 has retired PrintGifError() and added GifErrorString() */ 
#if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR) && \ 
        ((GIFLIB_MAJOR == 4 && GIFLIB_MINOR >= 2) || GIFLIB_MAJOR > 4) 
#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR > 4
static void PrintGifError(int ErrorCode)
#else
static void PrintGifError(void)
#endif
{
#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR > 4
  const char *Err = GifErrorString(ErrorCode);
#else
  const char *Err = GifErrorString();
#endif

  if (Err != NULL) {
    fprintf(stderr, "\nGIF-LIB error: %s.\n", Err);
  } else {
#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR > 4
    fprintf(stderr, "\nGIF-LIB undefined error %d.\n", ErrorCode);
#else
    fprintf(stderr, "\nGIF-LIB undefined error %d.\n", GifError());
#endif
  }
} 
#endif 

/*
 *
 * Originally designed to facilitate image transfer and online
 * storage for use by CompuServe and its customers,
 * GIF is primarily an exchange and storage
 * format, although it is based on, and is supported by, many
 * applications.
 * 
 */

static s32
InterlacedOffset[] = { 0, 4, 2, 1 }, /* The way Interlaced image should. */
InterlacedJumps[] = { 8, 8, 4, 2 };    /* be read - offsets and jumps... */

fmt_codec::fmt_codec() : fmt_codec_base()
{}

fmt_codec::~fmt_codec()
{}

void fmt_codec::options(codec_options *o)
{
    o->version = "1.3.1";
    o->name = "Compuserve GIF";
    o->filter = "*.gif ";
    o->config = "";
    o->mime = "\x0047\x0049\x0046\x0038[\x0039\x0037]\x0061";
    o->mimetype = "image/gif";
    o->pixmap = codec_gif;
    o->readable = true;
    o->canbemultiple = true;
    o->writestatic = false;
    o->writeanimated = false;
    o->needtempfile = false;
}

s32 fmt_codec::read_init(const std::string &file)
{
    frs.open(file.c_str(), ios::binary | ios::in);
    
    if(!frs.good())
        return SQE_R_NOFILE;
	    
    frs.close();

    transIndex = -1;

    Last = 0;
    Lines = 0;
    buf = 0;
    saved = 0;

#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR > 4
    int ErrorCode;
    gif = DGifOpenFileName(file.c_str(), &ErrorCode);
#else
    gif = DGifOpenFileName(file.c_str());
#endif

    // for safety...
    if(!gif)
        return SQE_R_BADFILE;

    linesz = gif->SWidth * sizeof(GifPixelType);

    if((buf = (u8*)malloc(linesz)) == NULL)
        return SQE_R_NOMEMORY;

    if((saved = (RGBA *)calloc(linesz, sizeof(RGBA))) == NULL)
        return SQE_R_NOMEMORY;

    if(gif->SColorMap)
    {
	back.r = gif->SColorMap->Colors[gif->SBackGroundColor].Red;
	back.g = gif->SColorMap->Colors[gif->SBackGroundColor].Green;
	back.b = gif->SColorMap->Colors[gif->SBackGroundColor].Blue;
	back.a = 255;
    }
    else
	memset(&back, 0, sizeof(RGBA));

    layer = -1;
    line = 0;
    curLine = 0;

    Lines_h = gif->SHeight;
    Lines = (RGBA **)malloc(Lines_h * sizeof(RGBA*));

    if(!Lines)
	return SQE_R_NOMEMORY;

    for(s32 i = 0;i < Lines_h;i++)
	Lines[i] = (RGBA *)0;

    map = (gif->Image.ColorMap) ? gif->Image.ColorMap : gif->SColorMap;

    Last = (RGBA **)malloc(gif->SHeight * sizeof(RGBA*));

    if(!Last)
	return SQE_R_NOMEMORY;

    for(s32 i = 0;i < gif->SHeight;i++)
	Last[i] = (RGBA *)0;

    for(s32 i = 0;i < gif->SHeight;i++)
    {
	Last[i] = (RGBA *)calloc(gif->SWidth, sizeof(RGBA));

	if(!Last[i])
	    return SQE_R_NOMEMORY;

//	for(s32 k = 0;k < gif->SWidth;k++)
//	    memcpy(Last[i]+k, &back, sizeof(RGBA));
    }

    currentImage = -1;
    lastDisposal = DISPOSAL_NO;

    finfo.animated = false;

    return SQE_OK;
}

s32 fmt_codec::read_next_pass()
{
    layer++;
    currentPass++;
    line = 0;
    curLine = 0;

    return SQE_OK;
}

s32 fmt_codec::read_next()
{
    bool foundExt = false;

    currentImage++;

    fmt_image image;

    image.interlaced = gif->Image.Interlace;
    image.passes = (gif->Image.Interlace) ? 4 : 1;

    while(true)
    {
        if (DGifGetRecordType(gif, &record) == GIF_ERROR)
	{
#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR > 4
	    PrintGifError(gif->Error);
#else
	    PrintGifError();
#endif
	    return SQE_R_BADFILE;
	}

	switch(record)
	{
	    case IMAGE_DESC_RECORD_TYPE:
		if(DGifGetImageDesc(gif) == GIF_ERROR)
		{
#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR > 4
		    PrintGifError(gif->Error);
#else
		    PrintGifError();
#endif
		    return SQE_R_BADFILE;
		}

		if(!foundExt)
		{
		    lastDisposal = disposal;
		    disposal = DISPOSAL_NO;
		    image.delay = 100;
		    transIndex = -1;
		    image.hasalpha = true;
		}

		lastRow = (currentImage) ? Row : gif->Image.Top;
		lastCol = (currentImage) ? Col : gif->Image.Left;
		Row = gif->Image.Top;
		Col = gif->Image.Left;
		image.w = gif->SWidth;
		image.h = gif->SHeight;
		lastWidth = (currentImage) ? Width : gif->Image.Width;
		lastHeight = (currentImage) ? Height : gif->Image.Height;
		Width = gif->Image.Width;
		Height = gif->Image.Height;
		image.bpp = 8;

		curLine = 0;

		if(gif->Image.Left + gif->Image.Width > gif->SWidth || gif->Image.Top + gif->Image.Height > gif->SHeight)
		{
	    	    return SQE_R_BADFILE;
		}
	    break;

	    case EXTENSION_RECORD_TYPE:
		if(DGifGetExtension(gif, &ExtCode, &Extension) == GIF_ERROR)
		{
#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR > 4
		    PrintGifError(gif->Error);
#else
		    PrintGifError();
#endif
		    return SQE_R_BADFILE;
		}

                if(!Extension)
                    break;

		if(ExtCode == 249)
		{
		    foundExt = true;

		    lastDisposal = disposal;
		    disposal = (Extension[1] >> 2) & 7;
		    bool b = Extension[1] & 1;
		    s32 u = (unsigned)*(Extension + 2);
		    image.delay = (!u) ? 100 : (u * 10);

		    if(b)
		      transIndex = Extension[4];
		      
		    image.hasalpha = b;
		}
		else if(ExtCode == 254 && Extension[0])
		{
    		    fmt_metaentry mt;
    		    s8 d[Extension[0]+1];

                    memcpy(d, (s8*)Extension+1, Extension[0]);
                    d[Extension[0]] = '\0';

                    for(s32 s = 0;s < Extension[0];s++)
                        if(d[s] == '\n')
                            d[s] = ' ';

                    mt.group = "Comment";
                    mt.data = d;

                    addmeta(mt);
		}

		while(Extension)
		{
		    if(DGifGetExtensionNext(gif, &Extension) == GIF_ERROR)
		    {
#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR > 4
		    PrintGifError(gif->Error);
#else
			PrintGifError();
#endif
			return SQE_R_BADFILE;
		    }
		}
	    break;

	    case TERMINATE_RECORD_TYPE:
		return SQE_NOTOK;

	    default: ;
	}

	if(record == IMAGE_DESC_RECORD_TYPE)
	{
	    if(currentImage >= 1)
		finfo.animated = true;

	    map = (gif->Image.ColorMap) ? gif->Image.ColorMap : gif->SColorMap;

	    back.a = (transIndex != -1) ? 0 : 255;

	    for(s32 k = 0;k < gif->SWidth;k++)
    		memcpy(saved+k, &back, sizeof(RGBA));

	    image.compression = "LZW";
    	    image.colorspace = "Color indexed";
	    image.interlaced = gif->Image.Interlace;
	    image.passes = (gif->Image.Interlace) ? 4 : 1;
    	
	    finfo.image.push_back(image);

	    layer = -1;
	    currentPass = -1;

	    return SQE_OK;
	}
    }
}

s32 fmt_codec::read_scanline(RGBA *scan)
{
    fmt_image *im = image(currentImage);
    fmt_utils::fillAlpha(scan, im->w);

    if(curLine < Row || curLine >= Row + Height)
    {
    	if(currentPass == im->passes-1)
	{
	    memcpy(scan, Last[curLine], im->w * sizeof(RGBA));

	    if(lastDisposal == DISPOSAL_BACKGROUND)
		if(curLine >= lastRow && curLine < lastRow+lastHeight)
		{
		    memcpy(scan+lastCol, saved, lastWidth * sizeof(RGBA));
		    memcpy(Last[curLine], scan, im->w * sizeof(RGBA));
		}
	}
	
	curLine++;

	return SQE_OK;
    }

    curLine++;

    s32 i;
    s32 index;

    if(gif->Image.Interlace)
    {
	memcpy(scan, Last[curLine-1], im->w * sizeof(RGBA));

	if(line == 0)
	    j = InterlacedOffset[layer];

	if(line == j)
	{
	    if(DGifGetLine(gif, buf, Width) == GIF_ERROR)
	    {
#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR > 4
		    PrintGifError(gif->Error);
#else
	        PrintGifError();
#endif
	        memset(scan, 255, im->w * sizeof(RGBA));
	        return SQE_R_BADFILE;
	    }
	    else
	    {
		j += InterlacedJumps[layer];

		for(i = 0;i < Width;i++)
		{
		    index = Col + i;

		    if(buf[i] == transIndex && transIndex != -1)
		    {
			RGB rgb = *((RGB *)&(map->Colors[buf[i]]));

			if(back == rgb && !currentImage)
			    (scan+index)->a = 0;
			else if(back == rgb && lastDisposal != DISPOSAL_BACKGROUND && currentImage)
			{
			    RGBA *t = &Last[curLine-1][index];
			    memcpy(scan+index, t, sizeof(RGBA));
			}
			else if(back == rgb && lastDisposal == DISPOSAL_BACKGROUND && currentImage)
			{
			    (scan+index)->a = 0;
			}
			else if(currentImage)
			{
			    RGBA *t = &Last[curLine-1][index];

			    if(lastDisposal == DISPOSAL_BACKGROUND)
			    {
				memcpy(scan+index, &back, sizeof(RGBA));//(scan+index)->a=0;

				if(t->a == 0)
				(scan+index)->a=0;
			    }
			}
		    }
		    else
		    {
		        memcpy(scan+index, &(map->Colors[buf[i]]), sizeof(RGB));
		        (scan+index)->a = 255;
		    }
		}

		Lines[line] = (RGBA*)realloc(Lines[line], im->w * sizeof(RGBA));

		if(!Lines[line])
		    return SQE_R_NOMEMORY;
			
		memcpy(Lines[line], scan, im->w * sizeof(RGBA));
	    }
	} // if(line == j)
	else
	{
	    if(Lines[line])
	        memcpy(scan, Lines[line], im->w * sizeof(RGBA));
	    else
	        memset(scan, 255, im->w * sizeof(RGBA));
	}

	if(currentPass == im->passes-1)
	    memcpy(Last[curLine-1], scan, im->w * sizeof(RGBA));

	line++;
    }
    else // !s32erlaced
    {
        if(DGifGetLine(gif, buf, Width) == GIF_ERROR)
        {
	    memset(scan, 255, im->w * sizeof(RGBA));
#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR > 4
	    PrintGifError(gif->Error);
#else
	    PrintGifError();
#endif
	    return SQE_R_BADFILE;
	}
	else
	{
	    memcpy(scan, Last[curLine-1], im->w * sizeof(RGBA));

	    if(lastDisposal == DISPOSAL_BACKGROUND)
	    {
		if(curLine-1 >= lastRow && curLine-1 < lastRow+lastHeight)
		    memcpy(scan+lastCol, saved, lastWidth * sizeof(RGBA));
	    }

	    for(i = 0;i < Width;i++)
	    {
	        index = Col + i;

		if(buf[i] == transIndex && transIndex != -1)
		{
		    RGB rgb = *((RGB *)&(map->Colors[buf[i]]));

		    if(back == rgb && !currentImage)
			(scan+index)->a = 0;
		    else if(back == rgb && lastDisposal != DISPOSAL_BACKGROUND && currentImage)
		    {
			RGBA *t = &Last[curLine-1][index];
			memcpy(scan+index, t, sizeof(RGBA));// = 255;
		    }
		    else if(back == rgb && lastDisposal == DISPOSAL_BACKGROUND && currentImage)
		    {
			(scan+index)->a = 0;
		    }
		    else if(currentImage)
		    {
			RGBA *t = &Last[curLine-1][index];

			if(lastDisposal == DISPOSAL_BACKGROUND)
			{
			    memcpy(scan+index, &back, sizeof(RGBA));//(scan+index)->a=0;

			    if(t->a == 0)
				(scan+index)->a=0;
			}
		    }
		}// if transIndex
		else
		{
		    memcpy(scan+index, &(map->Colors[buf[i]]), sizeof(RGB));
		    (scan+index)->a = 255;
		}
	    } // for

	    memcpy(Last[curLine-1], scan, im->w * sizeof(RGBA));
	}
    }

    return SQE_OK;
}

void fmt_codec::read_close()
{
    if(buf)   free(buf);
    if(saved) free(saved);

    if(Lines)
    {
	for(s32 i = 0;i < Lines_h;i++)
	    if(Lines[i])
		free(Lines[i]);

	free(Lines);
	Lines = 0;
    }

    if(Last)
    {
	for(s32 i = 0;i < gif->SHeight;i++)
	    if(Last[i])
		free(Last[i]);

	free(Last);
	Last = 0;
    }

    finfo.meta.clear();
    finfo.image.clear();

    if(gif) {
#if defined(GIFLIB_MAJOR) && (GIFLIB_MAJOR > 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1))
	DGifCloseFile(gif, NULL);
#else
	DGifCloseFile(gif);
#endif
    }
}

#include "fmt_codec_cd_func.h"
