/*
** our include
*/

#include "jsupport.h"

/*
** private structure(s)
*/

struct ReadData {
  struct jpeg_decompress_struct cinfo;
  UWORD  ham;
  dt_error_mgr  jerr;
  dt_client_mgr jmgr;
  APTR   obj,
         bmhd,
         handle,
         rdarg,
         bm1,
         bm2,
         buf;
  ULONG  title,
         depth,
         flags,
         mode,
         fmt;
  VOID   (*flip_x)(UBYTE *,ULONG);
  ULONG  (*flip_y)(ULONG  ,ULONG);
  VOID   (*encode)(UBYTE *,ULONG);
  union {
    struct pdtBlitPixelArray pbpa;
    struct RastPort rp[2];
    ULONG p[2];
  } u;
};

#define LINEBUF *((UBYTE **)&rd->u.pbpa.pbpa_PixelData)

#define r_fhd 0
#define r_src 1
#define r_lev 0
#define r_num 1
#define r_col 0
#define r_crg 1

/*
** defines for picture.datatype V43+
*/

#if !defined(PBPAFMT_RGB)
#define PBPAFMT_RGB        0
#define PBPAFMT_LUT8       3
#define PDTA_DitherQuality 0x800010DE
#endif

#if defined(__GNUC__)

/*
** set dt attribute(s)
*/

STATIC ULONG set_dt_attrs(struct ClassBase *cb,Object *obj,Tag tag,...)
{
  return SetDTAttrsA(obj,NULL,NULL,(struct TagItem *)&tag);
}

#define SetDTAttrs(a,b,c,d...) set_dt_attrs(cb,a,d)

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

STATIC ULONG get_dt_attrs(struct ClassBase *cb,Object *obj,Tag tag,...)
{
  return GetDTAttrsA(obj,(struct TagItem *)&tag);
}

#define GetDTAttrs(a...) get_dt_attrs(cb,a)

#endif /* __GNUC__ */

/*
** get and parse user settings then start decoder/encoder
*/

#define PREFSFILE (STRPTR)"classes/datatypes/jfif44.prefs"

enum {
  RD_V42MODE=0,RD_GRAY,RD_HAM,RD_UNSAFE,RD_DEPTH,RD_SCALE,RD_DITHER,RD_DCT,RD_FLIPX,RD_FLIPY,ARG_IGNORE,ARG_VERBOSE,ARG_LAST
};

#define TEMPLATE "V42MODE/S,GRAY/S,HAM/K/N,UNSAFE/S,DEPTH/K/N,SCALE/K/N,DITHER/K,DCT/K,FLIPX/S,FLIPY/S,IGNORE/S,VERBOSE/S"

STATIC VOID startup(VOID (*start)(struct ClassBase *,LONG *,APTR,APTR),struct ClassBase *cb,APTR data,APTR pool)
{ LONG prefs[ARG_LAST],len;
  struct RDArgs *rdarg;
  UBYTE *buf,p[4];

  /* clear argument vector */
  { ULONG *m=(ULONG *)prefs,l=sizeof(prefs)/sizeof(prefs[0]);
    do;while(*m++=0,--l); }

  /* get && parse prefs file */
  if (rdarg=NULL,GetVar(PREFSFILE,&p[0],1,0) >= 0)
    if ((buf=AllocPooled(pool,len=1+IoErr())) != NULL)
      if ((len=GetVar(PREFSFILE,buf,len,0)) >= 0)
        if (buf[len++]='\n',(rdarg=AllocDosObject(DOS_RDARGS,NULL)) != NULL) {
          rdarg->RDA_Source.CS_Buffer = buf;
          rdarg->RDA_Source.CS_Length = len;
          rdarg->RDA_Flags = RDAF_NOPROMPT;
          ReadArgs((STRPTR)TEMPLATE,&prefs[0],rdarg);
        }
  
  /* start decoder/encoder */
  (*start)(cb,&prefs[0],data,rdarg);
}

/*
** do nothing
*/

STATIC VOID stub(VOID)
{
  /* no work */
}

/*
** mirror support
*/

STATIC VOID flipx_24bit(UBYTE *buf,ULONG width)
{ UBYTE r,g,b,*end = &buf[2*width-3];

  end += width; width /= 2;
  do {
    r = *buf++; g = *buf++; b = *buf++; buf -= 3;
    *buf++ = *end++; *buf++ = *end++; *buf++ = *end++; end -= 3;
    *end++ = r; *end++ = g; *end++ = b; end -= 6;
  } while (--width);
}

STATIC VOID flipx_8bit(UBYTE *buf,ULONG width)
{ UBYTE c,*end = &buf[width];

  width /= 2;
  do {
    c = *buf; *buf++ = *--end; *end = c;
  } while (--width);
}

STATIC ULONG flipy_no(ULONG height,ULONG y)
{
  return y;
}

STATIC ULONG flipy_yes(ULONG height,ULONG y)
{
  return height - y - 1;
}

/*
** V43 interface
*/

STATIC LONG decode_picture_v43(struct ClassBase *cb,struct ReadData *rd)
{ ULONG height,y;
  LONG ret;

  rd->u.p[r_num] = 0; rd->u.p[r_lev] = 0;

  SetDTAttrs(rd->obj, NULL, NULL,
             PDTA_SourceMode,  PMODE_V43,
             PDTA_ModeID,      0,
             DTA_ErrorLevel,   &rd->u.p[r_lev],
             DTA_ErrorNumber,  &rd->u.p[r_num],
             DTA_NominalHoriz, rd->cinfo.output_width,
             DTA_NominalVert,  height=rd->cinfo.output_height,
             DTA_ObjName,      rd->title,
             TAG_DONE);

  if (ret=rd->u.p[r_num],rd->u.p[r_lev])
    return ret;

  if ((LINEBUF=AllocPooled(rd->jmgr.pool,rd->cinfo.output_components*rd->cinfo.output_width))) {

    rd->u.pbpa.MethodID           = PDTM_WRITEPIXELARRAY;
  //rd->u.pbpa.pbpa_PixelData     = lbuf;
    rd->u.pbpa.pbpa_PixelFormat   = rd->fmt;
    rd->u.pbpa.pbpa_PixelArrayMod = 0;
    rd->u.pbpa.pbpa_Left          = 0;
  //rd->u.pbpa.pbpa_Top           = y;
    rd->u.pbpa.pbpa_Width         = rd->cinfo.output_width;
    rd->u.pbpa.pbpa_Height        = 1;

    y = 0;
    do {
      jpeg_read_scanlines(&rd->cinfo,(JSAMPARRAY)&LINEBUF,1);
      (*rd->flip_x)(LINEBUF,rd->cinfo.output_width);
      rd->u.pbpa.pbpa_Top = (*rd->flip_y)(height,y);
      DoSuperMethodA(PictureClass,rd->obj,(Msg)&rd->u.pbpa);
    } while (++y < height);

    return 0;
  }

  return ERROR_NO_FREE_STORE;
}

/*
** RGB -> HAM encoding
*/

STATIC VOID RGBtoHAM6(UBYTE *pixelbuf,ULONG width)
{
  CreateHAM6Line(pixelbuf+0,pixelbuf+1,pixelbuf+2,pixelbuf,3,width);
}

STATIC VOID RGBtoHAM8(UBYTE *pixelbuf,ULONG width)
{
  CreateHAM8Line(pixelbuf+0,pixelbuf+1,pixelbuf+2,pixelbuf,3,width);
}

/*
** obtain best modeID
*/

INLINE ULONG GetBestModeID(struct ClassBase *cb,struct ReadData *rd,ULONG w,ULONG h)
{ UWORD id = LORES_KEY;

  if ((UWORD)w > 362)
    id = HIRES;
  if ((UWORD)h > 283)
    id |= LACE;
  if (rd->ham && ModeNotAvailable(id|=HAM))
    id &= ~HIRES;

  return id;
}

/*
** V42 interface
*/

STATIC LONG decode_picture_v42(struct ClassBase *cb,struct ReadData *rd)
{ ULONG height,y;
  UBYTE *lbuf;

  InitRastPort(&rd->u.rp[0]);

  if ((rd->u.rp[0].BitMap=rd->bm1=AllocBitMap(rd->cinfo.output_width,height=rd->cinfo.output_height,rd->depth,rd->flags|BMF_CLEAR,NULL)) != NULL) {

    InitRastPort(&rd->u.rp[1]);

    if ((rd->u.rp[1].BitMap=rd->bm2=AllocBitMap(rd->cinfo.output_width,1,rd->depth,BMF_CLEAR,NULL)) != NULL) {

      if ((rd->buf=lbuf=AllocPooled(rd->jmgr.pool,(rd->cinfo.output_components*rd->cinfo.output_width+15)&~15))) {

        y = 0;
        do {
          jpeg_read_scanlines(&rd->cinfo,(JSAMPARRAY)&rd->buf,1);
          (*rd->flip_x)(lbuf,rd->cinfo.output_width);
          (*rd->encode)(lbuf,rd->cinfo.output_width);
          WritePixelLine8(&rd->u.rp[0],0,(*rd->flip_y)(height,y),rd->cinfo.output_width,lbuf,&rd->u.rp[1]);
        } while (++y < height);

        WaitBlit();

        SetDTAttrs(rd->obj, NULL, NULL,
                   PDTA_ModeID,      GetBestModeID(cb,rd,rd->cinfo.output_width,height),
                   PDTA_BitMap,      rd->bm1,
                   DTA_NominalHoriz, rd->cinfo.output_width,
                   DTA_NominalVert,  height,
                   DTA_ObjName,      rd->title,
                   TAG_DONE);

        rd->bm1 = NULL; return 0;
      }
    }
  }

  return ERROR_NO_FREE_STORE;
}

/*
** start decoder with internal/user settings
*/

STATIC VOID decode_start(struct ClassBase *cb,LONG *prefs,APTR data,APTR rdarg)
{ struct ReadData *rd = (struct ReadData *)data;
  ULONG *methods,*cregs,depth,mask,tmp,val,i;
  struct BitMapHeader *bmhd;
  JSAMPARRAY colormap;
  UBYTE *cmap,*s,r,g,b;
  LREG(a0,APTR ptr);
  UWORD ham;

  /* init for cleanup */
  rd->rdarg = rdarg;

  /* set behaviour of failure display */
  rd->jmgr.verbose = prefs[ARG_VERBOSE];

  /* set behaviour of error handling */
  rd->jerr.ignore = prefs[ARG_IGNORE];

  /* create and initialize decompression object */
  jpeg_create_decompress(&rd->cinfo);

  /* initialize source data manager */
  jpeg_load_src(&rd->cinfo,rd->handle);

  /* get image info - sets decompression defaults */
  jpeg_read_header(&rd->cinfo,TRUE);

  /* check for V43 compatibility */
  if (val=~0,!prefs[RD_V42MODE] && (methods=GetDTMethods(rd->obj)))
    do;while(((val=*methods++) != ~0) && (val-=PDTM_WRITEPIXELARRAY));
  rd->mode = val;

  /* deal with colorspace */
  if (depth=0,ptr=(APTR)stub,val=JCS_GRAYSCALE,!prefs[RD_GRAY] && rd->cinfo.out_color_space != val)
    if (val=JCS_RGB,prefs[RD_HAM] && (tmp=*(ULONG *)prefs[RD_HAM],tmp == 6 || tmp == 8))
      if (ptr=(APTR)RGBtoHAM6,depth=tmp,(rd->mode=tmp) != 6)
        ptr=(APTR)RGBtoHAM8;
  rd->cinfo.out_color_space = val; rd->encode = (VOID (*)())ptr; rd->ham = depth;

  /* deal with FBlit 'optimization' */
  if (val=BMF_DISPLAYABLE,cb->FBlit && prefs[RD_UNSAFE])
    val=0;
  rd->flags=val;

  /* basic options */
  if (!rd->ham) {
    if (depth=8,prefs[RD_DEPTH] && (tmp=*(ULONG *)prefs[RD_DEPTH]) <= 8) {
      if (depth=3,tmp > depth)
        depth = tmp;
    } else if (val == JCS_RGB && !rd->mode) depth = 24;
  }
  rd->depth = depth;
  
//rd->cinfo.scale_num = 1;

  if (val=0,prefs[RD_SCALE])
    if ((LONG)(tmp=*(ULONG *)prefs[RD_SCALE]) > 0)
      if (val=3,tmp < val)
        val = tmp;
  rd->cinfo.scale_denom = 1 << val;

  if (val=JDCT_IFAST,(s=(UBYTE *)prefs[RD_DCT])) {
    if (Stricmp("SLOW",s)) {
#if defined(DCT_FLOAT_SUPPORTED)
      if (!Stricmp("FLOAT",s)) val = JDCT_FLOAT;
#endif
    } else val = JDCT_ISLOW;
  }
  rd->cinfo.dct_method = val;
  
  rd->cinfo.do_fancy_upsampling = FALSE;

  if (tmp=PBPAFMT_RGB,!(ham=rd->ham) && depth <= 8) {
    rd->cinfo.quantize_colors = TRUE;
    rd->cinfo.desired_number_of_colors = 1 << depth;
    if (val=JDITHER_FS,(s=(UBYTE *)prefs[RD_DITHER])) {
      if (Stricmp("NONE",s)) {
        if (!Stricmp("ORDERED",s)) {
          rd->cinfo.two_pass_quantize = FALSE; val = JDITHER_ORDERED;
        }
      } else val = JDITHER_NONE;
    }
    rd->cinfo.dither_mode = val; tmp = PBPAFMT_LUT8;
  }
  rd->fmt = tmp;

  /* mirror horizontally */
  if (ptr=(APTR)stub,prefs[RD_FLIPX])
    if (ptr=(APTR)flipx_8bit,rd->fmt == PBPAFMT_RGB)
      ptr=(APTR)flipx_24bit;
  rd->flip_x = (VOID (*)())ptr;

  /* mirror vertically */
  if (ptr=(APTR)flipy_no,prefs[RD_FLIPY])
    ptr=(APTR)flipy_yes;
  rd->flip_y = (ULONG (*)())ptr;

  /* start decompressor */
  jpeg_start_decompress(&rd->cinfo);

  /* fill BitMapHeader */
  bmhd = (struct BitMapHeader *)rd->bmhd;
  bmhd->bmh_Depth = depth;
  bmhd->bmh_PageWidth = bmhd->bmh_Width = rd->cinfo.output_width;
  bmhd->bmh_PageHeight = bmhd->bmh_Height = rd->cinfo.output_height;

  /* deal with colormap */
  if (depth <= 8) {

    if (val=rd->cinfo.actual_number_of_colors,ham)
      val=1,val<<=(depth-=2);

    SetDTAttrs(rd->obj, NULL, NULL, PDTA_NumColors,val, TAG_DONE);
    if (GetDTAttrs(rd->obj, PDTA_ColorRegisters,&rd->u.p[r_col], PDTA_CRegs,&rd->u.p[r_crg], TAG_DONE) != 2)
      ERREXIT(&rd->cinfo,JMSG_NOMESSAGE);

    cregs = (ULONG *)rd->u.p[r_crg]; cmap = (UBYTE *)rd->u.p[r_col]; colormap = rd->cinfo.colormap;
    mask = rd->cinfo.out_color_space == JCS_GRAYSCALE ? 0 : ~0; i = 0;
    if (ham)
      do {
        tmp = ((i * 255) / (val - 1)) * 0x01010101;
        *cmap++ = tmp; *cregs++ = tmp; *cmap++ = tmp; *cregs++ = tmp; *cmap++ = tmp; *cregs++ = tmp;
      } while (++i < val);
    else
      do {
        *cmap++ = (r = colormap[0&mask][i]); *cregs++ = r * 0x01010101;
        *cmap++ = (g = colormap[1&mask][i]); *cregs++ = g * 0x01010101;
        *cmap++ = (b = colormap[2&mask][i]); *cregs++ = b * 0x01010101;
      } while (++i < val);
  }
}

/*
** free any allocated stuff
*/

INLINE VOID decode_cleanup(struct ClassBase *cb,struct ReadData *rd)
{ APTR rdarg;

  /* free V42 gfx stuff */
  WaitBlit();
  FreeBitMap(rd->bm2); FreeBitMap(rd->bm1);

  /* release decompression object */
  jpeg_destroy_decompress(&rd->cinfo);

  /* free dos stuff */
  if ((rdarg=rd->rdarg)) {
    FreeArgs(rdarg); FreeDosObject(DOS_RDARGS,rdarg);
  }

  /* release ffr stuff */
  CloseFFR(rd->handle);
}

/*
** load JFIF picture
*/

STATIC LONG LoadJFIF(struct ClassBase *cb,Object *obj)
{ LONG (*decode)(struct ClassBase *,struct ReadData *),error;
  struct ReadData *rd,*dt;
  APTR pool;

  if (error=ERROR_NO_FREE_STORE,(pool=CreatePool(MEMF_CLEAR,64*1024,40*1024))) {

    if ((dt=rd=AllocPooled(pool,sizeof(*dt)))) {

      rd->jerr.error = error;
      rd->jmgr.pool  = pool;
      rd->jmgr.cb    = cb;
    //rd->bmhd       = NULL;
    //rd->u.p[r_fhd] = 0;
      rd->u.p[r_src] = DTST_FILE;
    //rd->title      = 0;

      GetDTAttrs(rd->obj=obj,
                 PDTA_BitMapHeader, &rd->bmhd,
                 DTA_Handle,        &rd->u.p[r_fhd],
                 DTA_SourceType,    &rd->u.p[r_src],
                 DTA_Name,          &rd->title,
                 TAG_DONE);

      rd->cinfo.client_data = &rd->jmgr; rd->cinfo.err = jpeg_std_error(&rd->jerr.pub);

      if (error=0,rd->u.p[r_src] != DTST_RAM) {

        if (error=ERROR_OBJECT_WRONG_TYPE,rd->u.p[r_src] == DTST_FILE && rd->bmhd && rd->u.p[r_fhd]) {

          if ((rd->handle=OpenFFR(cb,pool,FFR_MODE_READ,(BPTR)rd->u.p[r_fhd]))) {

            if (!setjmp(rd->jerr.jmpbuf)) {

              rd = dt; startup(decode_start,rd->jmgr.cb,rd,rd->jmgr.pool);

              if (decode=decode_picture_v42,!rd->mode)
                decode=decode_picture_v43;

              if ((rd->jerr.error=(*decode)(rd->jmgr.cb,rd)) == 0)
                jpeg_finish_decompress(&rd->cinfo);
            }
            rd = dt; decode_cleanup(cb=rd->jmgr.cb,rd); pool = rd->jmgr.pool;
          }
          error = rd->jerr.error;
        }
      }
    }
    DeletePool(pool);
  }

  if (error) {
    if (error > 0) SetIoErr(error); return FALSE;
  }

  return TRUE;
}

/*
** save as JFIF file
*/

INLINE LONG SaveJFIF(struct ClassBase *cb,Object *obj,struct dtWrite *dtw)
{
  SetIoErr(ERROR_NOT_IMPLEMENTED); return FALSE;
}

/*
** class dispatcher
*/

ASM(Object *) Dispatch(REG(a0,struct IClass *cl),REG(a2,Object *obj),REG(a1,Msg msg))
{ static const Tag tags[] = { PDTA_DitherQuality,0,TAG_DONE };
  struct ClassBase *cb = (struct ClassBase *)cl->cl_UserData;
  struct RastPort *rp;
  struct Screen *scr;
  Object *o = obj;

  if (0 && (msg->MethodID == DTM_WRITE) && (((struct dtWrite *)msg)->dtw_Mode == DTWM_RAW))
    return (Object *)SaveJFIF(cb,obj,(struct dtWrite *)msg);

  if ((msg->MethodID == GM_LAYOUT) && (cb->SuperClassBase->lib_Version >= 44)) {
    struct gpLayout *gpl = (struct gpLayout *)msg;
    if (gpl->gpl_Initial && gpl->gpl_GInfo && (scr=gpl->gpl_GInfo->gi_Screen))
      if (GetBitMapAttr(scr->RastPort.BitMap,BMA_DEPTH) > 8)
        SetDTAttrsA(obj,NULL,NULL,(struct TagItem *)tags);
  }

  if (msg->MethodID != OM_UPDATE || !DoMethod(obj,ICM_CHECKLOOP)) {
    if ((obj=(Object *)DoSuperMethodA(cl,obj,msg))) {
      if (msg->MethodID == OM_NEW) {
        if (cb->SuperClassBase->lib_Version >= 44) {
          #define tlist ((struct opSet *)msg)->ops_AttrList
          if ((scr=(struct Screen *)GetTagData(PDTA_Screen,0,tlist)) && (GetTagData(PDTA_Remap,TRUE,tlist)))
            if (GetBitMapAttr(scr->RastPort.BitMap,BMA_DEPTH) > 8)
              SetDTAttrsA(obj,NULL,NULL,(struct TagItem *)tags);
        }
        if ((o != (Object *)cl) || !LoadJFIF(cb,obj))
          { CoerceMethod(cl,obj,OM_DISPOSE); if (obj) ; obj = NULL; }
      }
      else if (msg->MethodID == OM_UPDATE || msg->MethodID == OM_SET) {
        if ((rp=ObtainGIRPort(((struct opSet *)msg)->ops_GInfo)) != NULL) {
          DoMethod(o,GM_RENDER,((struct opSet *)msg)->ops_GInfo,rp,GREDRAW_UPDATE);
          ReleaseGIRPort(rp); obj = NULL;
        }
      }
    }
  }
  else obj = NULL;

  return obj;
}
