/*
** our include
*/

#include "jsupport.h"

/*
** private structures
*/

struct ReadData {
  ULONG  depth,
         width,
         height,
         mode,
         fmt;
  VOID   (*flip_x)(UBYTE *,ULONG);
  LONG   (*flip_y)(ULONG  ,ULONG);
  APTR   obj,
         pool,
         bmhd,
         handle,
         rdarg,
         bm1,
         bm2;
  ULONG  title,
         p[2];
  struct jpeg_decompress_struct cinfo;
  UWORD  nix;
};

#define r_fh  0
#define r_src 1
#define r_lev 0
#define r_num 1
#define r_col 0
#define r_crg 1
#define r_buf 0

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

#define RECTFMT_RGB  0
#define RECTFMT_LUT8 3

#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 than start the decoder or encoder
*/

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

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

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

STATIC LONG startup(struct ClassBase *cb,APTR data,LONG (*start)(struct ClassBase *,LONG *,APTR,APTR),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,GVF_GLOBAL_ONLY) >= 0)
    if ((buf=AllocPooled(pool,len=1+IoErr())) != NULL) {
      if ((len=GetVar(PREFSFILE,buf,len,GVF_GLOBAL_ONLY)) >= 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);
        }
      }
    }
  
  /* now start decoder/encoder */
  return (*start)(cb,&prefs[0],data,rdarg);
}

/*
** mirror support functions
*/

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 VOID flipx_no(UBYTE *buffer,ULONG width)
{
  /* nothing */
}

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

STATIC LONG 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,width,y;
  LONG ret;

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

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

  if (ret=rd->p[r_num],!rd->p[r_lev]) {

    ret = ERROR_NO_FREE_STORE;

    if ((rd->p[r_buf]=(ULONG)AllocPooled(rd->pool,width*rd->cinfo.output_components))) {

      y = 0;
      do {
        jpeg_read_scanlines(&rd->cinfo,(JSAMPARRAY)&rd->p[r_buf],1);
        (*rd->flip_x)((UBYTE *)rd->p[r_buf],width);
        DoSuperMethod(PictureClass,rd->obj,
                      PDTM_WRITEPIXELARRAY,rd->p[r_buf],rd->fmt,0,0,(*rd->flip_y)(height,y),width,1);
      } while (++y < height);

      ret = 0;
    }
  }

  return ret;
}

/*
** V42 interface
*/

STATIC LONG decode_picture_v42(struct ClassBase *cb,struct ReadData *rd)
{ ULONG modeid,width,height,depth,y;
  struct RastPort trp,rp;
  LONG ret = ERROR_NO_FREE_STORE;

  width = rd->width; height = rd->height; depth = rd->depth; InitRastPort(&rp);

  if ((rd->bm1=rp.BitMap=AllocBitMap(width,height,depth,BMF_CLEAR|BMF_DISPLAYABLE,NULL)) != NULL) {

    InitRastPort(&trp);

    if ((rd->bm2=trp.BitMap=AllocBitMap(width,1,depth,BMF_CLEAR,NULL)) != NULL) {

      if ((rd->p[r_buf]=(ULONG)AllocPooled(rd->pool,(width+15)&~15))) {

        y = 0;
        do {
          jpeg_read_scanlines(&rd->cinfo,(JSAMPARRAY)&rd->p[r_buf],1);
          (*rd->flip_x)((UBYTE *)rd->p[r_buf],width);
          WritePixelLine8(&rp,0,(*rd->flip_y)(height,y),width,(UBYTE *)rd->p[r_buf],&trp);
        } while (++y < height);

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

        SetDTAttrs(rd->obj, NULL, NULL,
                   PDTA_BitMap,      rd->bm1,
                   PDTA_ModeID,      modeid,
                   DTA_NominalHoriz, width,
                   DTA_NominalVert,  height,
                   DTA_ObjName,      rd->title,
                   TAG_DONE);

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

  return ret;
}

/*
** start decoder based on internal/user prefs
*/

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

  /* init for cleanup */
  rd->rdarg = rdarg; rd->bm1= NULL; rd->bm2 = NULL;

  /* set behaviour of failure display */
  ((dt_client_mgr *)rd->cinfo.client_data)->verbose = prefs[ARG_VERBOSE];

  /* 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);

  /* now deal with arguments */

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

  if (val=JCS_GRAYSCALE,!prefs[RD_GRAY])
    val=JCS_RGB;
  rd->cinfo.out_color_space = val;

  if (depth=24,prefs[RD_DEPTH])
    if ((tmp=*(ULONG *)prefs[RD_DEPTH]) <= 8)
      if (depth=3,tmp > depth)
        depth = tmp;
  if (depth == 24 && (rd->cinfo.out_color_space != JCS_RGB || rd->mode))
    depth = 8;
  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 = 1L << val;

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

  if (tmp=RECTFMT_RGB,depth <= 8) {
    rd->cinfo.quantize_colors = TRUE;
    rd->cinfo.desired_number_of_colors = 1L << depth;
    if (val=JDITHER_FS,prefs[RD_DITHER]) {
      if (Stricmp("NONE",(STRPTR)prefs[RD_DITHER])) {
        if (!Stricmp("ORDERED",(STRPTR)prefs[RD_DITHER])) {
          rd->cinfo.two_pass_quantize = FALSE; val = JDITHER_ORDERED;
        }
      } else val = JDITHER_NONE;
    }
    rd->cinfo.dither_mode = val; tmp = RECTFMT_LUT8;
  }
  rd->fmt = tmp;

  /* mirror horizontally */
  if (ptr=flipx_no,prefs[RD_FLIPX])
    if (ptr=flipx_8bit,depth > 8)
      ptr=flipx_24bit;
  rd->flip_x = ptr;

  /* mirror vertically */
  if (ptr=flipy_no,prefs[RD_FLIPY])
    ptr=flipy_yes;
  rd->flip_y = 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->width = rd->cinfo.output_width;
  bmhd->bmh_PageHeight = bmhd->bmh_Height = rd->height = rd->cinfo.output_height;

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

    SetDTAttrs(rd->obj, NULL, NULL, PDTA_NumColors,rd->cinfo.actual_number_of_colors, TAG_DONE);
    if (GetDTAttrs(rd->obj, PDTA_ColorRegisters,&rd->p[r_col], PDTA_CRegs,&rd->p[r_crg], TAG_DONE) != 2)
      return FALSE;

    cregs = (ULONG *)rd->p[r_crg]; cmap = (UBYTE *)rd->p[r_col]; colormap=rd->cinfo.colormap;
    mask = rd->cinfo.out_color_space == JCS_GRAYSCALE ? 0 : ~0; i = 0;
    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 < rd->cinfo.actual_number_of_colors);
  }

  /* success */
  return TRUE;
}

/*
** free any allocated stuff
*/

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

  /* free V42 gfx stuff */
  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 *);
  struct ReadData rd;
  dt_client_mgr jmgr;
  dt_error_mgr jerr;
  LONG error;

  rd.cinfo.client_data = &jmgr; jerr.error = ERROR_NO_FREE_STORE; jmgr.cb = cb;

  if ((jmgr.pool=rd.pool=CreatePool(MEMF_CLEAR,16*1024,16*1024)) != NULL) {

    jerr.error = 0; rd.p[r_fh] = 0; rd.bmhd = NULL; rd.p[r_src] = DTST_FILE; rd.title = 0;

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

    if (rd.p[r_src] != DTST_RAM) {

      jerr.error = ERROR_OBJECT_WRONG_TYPE;

      if (rd.p[r_src] == DTST_FILE && rd.bmhd && rd.p[r_fh]) {

        jerr.error = ERROR_NO_FREE_STORE;

        if ((rd.handle=OpenFFR(cb,rd.pool,FFR_MODE_READ,(BPTR)rd.p[r_fh]))) {

          rd.cinfo.err = jpeg_std_error(&jerr.pub);

          if (!setjmp(((dt_error_mgr *)rd.cinfo.err)->jmpbuf) && startup(jmgr.cb,&rd,decode_start,rd.pool)) {

            if (decode=decode_picture_v42,!rd.mode)
              decode=decode_picture_v43;
            jerr.error = (*decode)(jmgr.cb,&rd);

            jpeg_finish_decompress(&rd.cinfo);
          }
          decode_cleanup(cb=jmgr.cb,&rd);
        }
      }
    }
    DeletePool(rd.pool);
  }

  if ((error=jerr.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))
{ struct ClassBase *cb = (struct ClassBase *)cl->cl_UserData;
  struct RastPort *rp;
  Object *o = obj;

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

  if (msg->MethodID != OM_UPDATE || !DoMethod(obj,ICM_CHECKLOOP)) {
    if ((obj=(Object *)DoSuperMethodA(cl,obj,msg))) {
      if (msg->MethodID == OM_NEW) {
        if (!LoadJFIF(cb,obj))
          { CoerceMethod(cl,obj,OM_DISPOSE); 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;
}
