/*
** required includes
*/

#include <exec/memory.h>
#include <dos/dos.h>
#include <graphics/gfx.h>
#include <datatypes/pictureclass.h>
#include <proto/datatypes.h>
#include <proto/graphics.h>
#include <proto/utility.h>
#include <proto/exec.h>
#include <proto/dos.h>

/*
** easy access of a char-array as a "structure" (IJG sources v5.0b)
*/

#define UCH(x) \
  ((int) (x))

#define GET_2B(array,offset) \
  ((unsigned int) UCH(array[offset]) + \
   (((unsigned int) UCH(array[offset+1])) << 8))

#define GET_4B(array,offset) \
  ((ULONG) UCH(array[offset]) + \
   (((ULONG) UCH(array[offset+1])) << 8) + \
   (((ULONG) UCH(array[offset+2])) << 16) + \
   (((ULONG) UCH(array[offset+3])) << 24))

/*
** get dt attribute(s)
*/

ULONG set_dt_attrs(Object *obj, ULONG data, ...)
{
  return (SetDTAttrsA(obj,NULL,NULL,(struct TagItem *)&data));
}

/*
** get dt attribute(s)
*/

ULONG get_dt_attrs(Object *obj, ULONG data, ...)
{
  return (GetDTAttrsA(obj,(struct TagItem *)&data));
}

/*
** expand every bit to a byte
*/

UBYTE *convert_1bit(UBYTE *pixelbuf, UBYTE *outbuf, ULONG width)
{
  UBYTE *p=outbuf,c;
  int i,j;

  for(i=width/8; i>0; i--)
  {
    for(c=*pixelbuf++,j=7; j>=0; j--)
    {
      *p++ = ((c & 0x80) ? 1 : 0);
      c <<= 1;
    }
  }
  if ((i=width%8))
  {
    for(c=*pixelbuf,j=7; i>0; j--,i--)
    {
      *p++ = ((c & 0x80) ? 1 : 0);
      c <<= 1;
    }
  }
  return outbuf;
}

/*
** expand every 4 bits to a byte
*/

UBYTE *convert_4bit(UBYTE *pixelbuf, UBYTE *outbuf, ULONG width)
{
  UBYTE *p=outbuf,c;
  int i;

  for(i=width/2; i>0; i--)
  {
    c = *pixelbuf++;
    *p++ = (c >> 4); /* ((c & 0xf0) >> 4); */
    *p++ = (c & 0xf);
  }
  if (width & 1)
  {
    c = *pixelbuf; *p = (c >> 4); /* ((c & 0xf0) >> 4); */
  }
  return outbuf;
}

/*
** nothing to do :^)
*/

UBYTE *convert_8bit(UBYTE *pixelbuf, UBYTE *outbuf, ULONG width)
{
  return pixelbuf;
}

/*
** create the bitmap
*/

int decode_bmp_picture(Object *obj, BPTR fh, struct BitMapHeader *bmhd, ULONG title)
{
  UBYTE* (*convert)(UBYTE *pixelbuf, UBYTE *outbuf, ULONG width);
  struct BitMap *tbm,*bm;
  struct RastPort trp,rp;
  UBYTE *albuff,*buffer,*p;
  ULONG modeid,padwidth,width,height,depth,large;
  int line,index,success=FALSE;

  width = bmhd->bmh_Width; height = bmhd->bmh_Height; depth = bmhd->bmh_Depth;

  if ((bm=AllocBitMap(width,height,depth,BMF_CLEAR|BMF_DISPLAYABLE,NULL)) != NULL)
  {
    InitRastPort(&rp); rp.BitMap = bm;

    if ((tbm=AllocBitMap(width,1,depth,BMF_CLEAR,NULL)) != NULL)
    {
      InitRastPort(&trp); trp.BitMap = tbm;

      if (depth == 8 || ((albuff=AllocVec(((width+15)&~15),MEMF_CLEAR|MEMF_ANY)) != NULL))
      {
        padwidth = ((((width * depth) + 31) & ~31) / 8);

        large = padwidth * height;

        if ((buffer=AllocVec(large,MEMF_ANY)) != NULL)
        {
          if (Read(fh,buffer,large) != large)
          {
            FreeVec(buffer); buffer=NULL;
          }
        }
        else
        {
          buffer=AllocVec(padwidth,MEMF_ANY); large=0;
        }

        if (buffer != NULL)
        {
          switch(depth)
          {
            case 1:
              convert = convert_1bit;
              break;
            case 4:
              convert = convert_4bit;
              break;
            case 8:
              convert = convert_8bit;
              break;
          }

          for (line=(height-1),index=0; line>=0; index++,line--)
          {
            if (large)
              p=&buffer[index*padwidth];
            else
              if (Read(fh,(p=buffer),padwidth) != padwidth)
                break;

            WritePixelLine8(&rp,0,line,width,(*convert)(p,albuff,width),&trp);
          }

          if (line < 0)
          {
            modeid = LORES_KEY;
            if ((UWORD)width >= 640)
              modeid = HIRES;
            if ((UWORD)height >= 400)
              modeid |= LACE;

            set_dt_attrs(obj,
                         DTA_ObjName,      title,
                         DTA_NominalHoriz, width,
                         DTA_NominalVert,  height,
                         PDTA_BitMap,      bm,
                         PDTA_ModeID,      modeid,
                         TAG_DONE);

            success = TRUE;
          }

          FreeVec(buffer);
        }

      }

      if (depth < 8)
        FreeVec(albuff);
      FreeBitMap(tbm);
    }

    if (!success)
      FreeBitMap(bm);
  }

  return success;
}

/*
** extract all required information from the bmp header
*/

int read_bmp_header(Object *obj, BPTR fh, struct BitMapHeader *bmhd)
{
  UBYTE bmpfileheader[14+64],*cmap,*rgb;

  #define bmpinfoheader (bmpfileheader+14)

  UBYTE coltab[256*4];

  LONG *cregs;
  LONG biCompression;
  LONG biClrUsed;
  LONG biPlanes;
  LONG bfOffBits;
  LONG headerSize;
  LONG bPad;
  LONG cmapentrysize;
  LONG coltabsize;
  int i;

  if (Read(fh,bmpfileheader,18) != 18)
    return FALSE;

  if (((UWORD)GET_2B(bmpfileheader,0)) != 0x4D42)
    return FALSE;

  bfOffBits  = (LONG) GET_4B(bmpfileheader,10);
  headerSize = (LONG) GET_4B(bmpinfoheader, 0);

  if (headerSize < 12 && headerSize > 64)
    return FALSE;

  if (Read(fh,bmpinfoheader+4,headerSize-4) != (headerSize-4))
    return FALSE;

  switch(headerSize)
  {
    case 12: /* OS/2 1.x */
      bmhd->bmh_Width  = (WORD) GET_2B(bmpinfoheader, 4);
      bmhd->bmh_Height = (WORD) GET_2B(bmpinfoheader, 6);
      biPlanes         =        GET_2B(bmpinfoheader, 8);
      bmhd->bmh_Depth  = (WORD) GET_2B(bmpinfoheader,10);
      biCompression    =        0;
      biClrUsed        =        0;
      switch (bmhd->bmh_Depth)
      {
        case 1:
        case 4:
        case 8:
          cmapentrysize = 3;
          break;
        case 24:
          cmapentrysize = 0;
          break;
        default:
          cmapentrysize = 0;
          break;
      }
      break;
    case 40: /* Windows 3.x oder OS/2 2.x */
    case 64:
      bmhd->bmh_Width  = (WORD) GET_4B(bmpinfoheader, 4);
      bmhd->bmh_Height = (WORD) GET_4B(bmpinfoheader, 8);
      biPlanes         =        GET_2B(bmpinfoheader,12);
      bmhd->bmh_Depth  = (WORD) GET_2B(bmpinfoheader,14);
      biCompression    =        GET_4B(bmpinfoheader,16);
      biClrUsed        =        GET_4B(bmpinfoheader,32);
      switch (bmhd->bmh_Depth)
      {
        case 1:
        case 4:
        case 8:
          cmapentrysize = 4;
          break;
        case 24:
          cmapentrysize = 0;
          break;
        default:
          cmapentrysize = 0;
          break;
      }
      break;
    default:
      cmapentrysize = 0;
      break;
  }

  bmhd->bmh_PageWidth  = bmhd->bmh_Width;
  bmhd->bmh_PageHeight = bmhd->bmh_Height;

  if (biPlanes != 1 || biCompression != 0 || cmapentrysize == 0)
    return FALSE;

  if (biClrUsed <= 0)
    biClrUsed = 1L << bmhd->bmh_Depth;

  if (biClrUsed > 256)
    return FALSE;

  (void)set_dt_attrs(obj, PDTA_NumColors,(ULONG)biClrUsed, TAG_DONE);

  if ((get_dt_attrs(obj, PDTA_ColorRegisters,(ULONG)&cmap,PDTA_CRegs,(ULONG)&cregs, TAG_DONE)) != 2)
    return FALSE;

  coltabsize = biClrUsed * cmapentrysize;

  bPad = bfOffBits - (14 + headerSize + coltabsize);

  if (Read(fh,&coltab[0],coltabsize) != coltabsize)
    return FALSE;

  for(i=0,rgb=&coltab[0]; i<biClrUsed; rgb+=cmapentrysize,i++)
  {
    cmap[0] = rgb[2];
    cmap[1] = rgb[1];
    cmap[2] = rgb[0];
    cmap += 3; /* DON'T TOUCH THIS with (AMIGA) GNU-C !!! */
    cregs[i*3+0] = rgb[2] << 24;
    cregs[i*3+1] = rgb[1] << 24;
    cregs[i*3+2] = rgb[0] << 24;
  }

  for(;bPad>0;)
  {
    LONG size = (bPad < sizeof(coltab) ? bPad : sizeof(coltab));
    if (Read(fh,&coltab[0],size) != size)
      break;
    bPad -= size;
  }

  return (bPad == 0 ? TRUE : FALSE);
}

/*
** load the picture
*/

int GetBMP(Class *cl, Object *obj, struct TagItem *attrs)
{
  struct BitMapHeader *bmhd;
  LONG ioerr;
  BPTR fh;
  int success=FALSE;

  if ((get_dt_attrs(obj, DTA_Handle,&fh,PDTA_BitMapHeader,&bmhd, TAG_DONE) == 2) && fh)
  {
    ioerr=ERROR_OBJECT_WRONG_TYPE;
    if ((success=read_bmp_header(obj,fh,bmhd)) != FALSE)
    {
      if ((success=decode_bmp_picture(obj,fh,bmhd,GetTagData(DTA_Name,NULL,attrs))) != FALSE)
        return success;
      ioerr=ERROR_NO_FREE_STORE;
    }
    SetIoErr(ioerr);
  }
  return success;
}

#if defined(__GNUC__)

/*
** GNU-C requirements..
*/

struct GfxBase *GfxBase;
struct ExecBase *SysBase;
struct DosLibrary *DOSBase;
struct UtilityBase *UtilityBase;
struct Library *DataTypesBase;

void Export(ULONG *p)
{
  SysBase       = *p++;
  DOSBase       = *p++;
                   p++;
  GfxBase       = *p++;
  UtilityBase   = *p++;
  DataTypesBase = *p;
}

asm(".stabs \"___UtilityBase\",11,0,0,0\n\t.stabs \"_UtilityBase\",1,0,0,0");

#endif
