/*
** our include
*/

#include "psupport.h"

/*
** private structure(s)
*/

struct ReadData {
  ULONG  width,
         height,
         depth,
         mode,
         gray,
         ham,
         fmt;
  VOID   (*flip_x)(UBYTE *,ULONG);
  ULONG  (*flip_y)(ULONG  ,ULONG);
  VOID   (*encode)(UBYTE *,ULONG);
  APTR   obj,
         bmhd,
         coltab,
         rdarg,
         bm1,
         bm2;
  int    bits,
         type,
         lace,
         passes,
         numcol;
  double gamma;
  ULONG  title;
  dt_client_mgr pmgr;
  png_structp png_ptr;
  png_infop info_ptr;
  union {
    struct RastPort rp[2];
    png_color_16 backgnd;
    ULONG p[2];
  } u;
};

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

/*
** 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 then start decoder/encoder
*/

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

enum {
  RD_V42MODE=0,RD_GRAY,RD_HAM,RD_GAMMA,RD_FLIPX,RD_FLIPY,ARG_VERBOSE,ARG_LAST
};

#define TEMPLATE "V42MODE/S,GRAY/S,HAM/K/N,GAMMA/K/N,FLIPX/S,FLIPY/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()
{
  /* no work */
}

/*
** reduce 24bit data to 8bit gray
*/

STATIC VOID grayscale(UBYTE *pixelbuf,ULONG width)
{ UBYTE *outbuf = pixelbuf;
  ULONG r,c;

  do {
    c = *pixelbuf++; c *= 11; r = *pixelbuf++; c += 16*r; r = *pixelbuf++; c += 5*r; c >>= 5;
  } while (*outbuf++=c,--width);
}

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

/*
** 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 modulo,passes,size,y;
  UBYTE *buffer,*buf;
  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->width,
             DTA_NominalVert,  rd->height,
             DTA_ObjName,      rd->title,
             TAG_DONE);

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

  size = png_get_rowbytes(rd->png_ptr,rd->info_ptr);

  if (modulo=0,rd->lace != PNG_INTERLACE_NONE) {
    modulo = size; size *= rd->height;
  }

  buffer = png_malloc(rd->png_ptr,size); passes = rd->passes;
  do {

    y = 0; buf = buffer; --passes;
    do {

      png_read_row(rd->png_ptr,buf,NULL);
      if (!passes) {
        (*rd->flip_x)(buf,rd->width);
        (*rd->encode)(buf,rd->width);
        DoSuperMethod(PictureClass,rd->obj,
                      PDTM_WRITEPIXELARRAY,buf,rd->fmt,0,0,(*rd->flip_y)(rd->height,y),rd->width,1);
      }

    } while (buf+=modulo,++y < rd->height);

  } while ((ret=passes));

  return ret;
}

/*
** 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 modulo,passes,size,y;
  UBYTE *buffer,*buf;

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

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

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

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

      size = (png_get_rowbytes(rd->png_ptr,rd->info_ptr)+15)&~15;

      if (modulo=0,rd->lace != PNG_INTERLACE_NONE) {
        modulo = size; size *= rd->height;
      }

      buffer = png_malloc(rd->png_ptr,size); passes = rd->passes;
      do {

        y = 0; buf = buffer; --passes;
        do {

          png_read_row(rd->png_ptr,buf,NULL);
          if (!passes) {
            (*rd->flip_x)(buf,rd->width);
            (*rd->encode)(buf,rd->width);
            WritePixelLine8(&rd->u.rp[0],0,(*rd->flip_y)(rd->height,y),rd->width,buf,&rd->u.rp[1]);
          }

        } while (buf+=modulo,++y < rd->height);

      } while (passes);

      SetDTAttrs(rd->obj, NULL, NULL,
                 PDTA_ModeID,      GetBestModeID(cb,rd,rd->width,rd->height),
                 PDTA_BitMap,      rd->bm1,
                 DTA_NominalHoriz, rd->width,
                 DTA_NominalVert,  rd->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,val,tmp,r,g,b,c;
  struct BitMapHeader *bmhd;
  double s_gamma,f_gamma;
  png_colorp coltab;
  LREG(a0,APTR ptr);
  UBYTE *cmap;

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

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

  /* handle GRAY and HAM */
  if (rd->gray=prefs[RD_GRAY],!rd->gray && prefs[RD_HAM]) {
    if (val=*(ULONG *)prefs[RD_HAM],val != 6 && val != 8) val = 0;
  } else val = 0;
  rd->ham = val;

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

  /* get all image information */
  png_read_info(rd->png_ptr,rd->info_ptr);

  /* retrieve information */
  png_get_IHDR(rd->png_ptr,rd->info_ptr,
               &rd->width,&rd->height,&rd->bits,&rd->type,&rd->lace,NULL,NULL);

  /* request 8bit data */
  if ((depth=rd->bits) == 16) {
    png_set_strip_16(rd->png_ptr);
  }

  /* handle small depths */
  if (depth < 8) {

    /* request 1 pixel per byte */
    png_set_packing(rd->png_ptr);

    /* expand gray to full 8bit */
    if (rd->type == PNG_COLOR_TYPE_GRAY) {
      png_set_expand(rd->png_ptr);
    }
  }

  /* check for file gamma */
  if (f_gamma=0.50,png_get_gAMA(rd->png_ptr,rd->info_ptr,&rd->gamma))
    f_gamma = rd->gamma;

  /* get screen gamma from prefs */
  if (val=2200,prefs[RD_GAMMA])
    if ((tmp=*(LONG *)prefs[RD_GAMMA]) >= 1000)
      val = tmp;
  s_gamma = val / 1000.0;

  /* handle gamma correction */
  png_set_gamma(rd->png_ptr,s_gamma,f_gamma);

  /* handle background */
  rd->u.backgnd.red = 0; rd->u.backgnd.green = 0; rd->u.backgnd.blue = 0; rd->u.backgnd.gray = 0;
  if (png_get_bKGD(rd->png_ptr,rd->info_ptr,(png_color_16p *)&rd->coltab))
    png_set_background(rd->png_ptr,rd->coltab,PNG_BACKGROUND_GAMMA_FILE,1,1.0);
  else
    png_set_background(rd->png_ptr,&rd->u.backgnd,PNG_BACKGROUND_GAMMA_SCREEN,0,s_gamma);

  /* turn on interlace handling */
  rd->passes = png_set_interlace_handling(rd->png_ptr);

  /* update info structure */
  png_read_update_info(rd->png_ptr,rd->info_ptr);

  /* check for GRAY and RGB */
  if (val=RECTFMT_LUT8,ptr=(APTR)stub,(rd->type&~PNG_COLOR_MASK_ALPHA) == PNG_COLOR_TYPE_GRAY) {
    rd->ham = 0; depth = 8; val = RECTFMT_RGB;
  }
  else if ((rd->type&~PNG_COLOR_MASK_ALPHA) == PNG_COLOR_TYPE_RGB) {
    if ((depth=rd->ham)) {
      if (ptr=(APTR)RGBtoHAM6,(rd->mode=depth) != 6) ptr=(APTR)RGBtoHAM8;
    }
    else if (depth=24,rd->gray || (rd->gray=rd->mode,rd->gray)) {
      ptr=(APTR)grayscale; depth = 8;
    }
    val = RECTFMT_RGB;
  }
  rd->encode = (VOID (*)())ptr; rd->fmt = val;

  /* mirror horizontally */
  if (ptr=(APTR)stub,prefs[RD_FLIPX])
    if (ptr=(APTR)flipx_8bit,depth > 8)
      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;

  /* fill BitMapHeader */
  bmhd = (struct BitMapHeader *)rd->bmhd;
  bmhd->bmh_Depth = rd->depth = depth;
  bmhd->bmh_PageWidth = bmhd->bmh_Width = rd->width;
  bmhd->bmh_PageHeight = bmhd->bmh_Height = rd->height;

  /* colormap handling */
  if (depth <= 8) {

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

    if (rd->type & PNG_COLOR_MASK_PALETTE) {
      if (!png_get_PLTE(rd->png_ptr,rd->info_ptr,(png_colorp *)&rd->coltab,&rd->numcol))
        png_error(rd->png_ptr,"not enough data");
      val = rd->numcol;
    }

    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)
      png_error(rd->png_ptr,"not enough data");

    /*
    ** do not use "struct ColorRegister" for cmap since with the amiga port
    ** of gcc a sizeof() for this structure is four and not three.. oh well
    */

    cregs = (ULONG *)rd->u.p[r_crg]; cmap = (UBYTE *)rd->u.p[r_col]; coltab = rd->coltab;
    if (c=0,rd->fmt == RECTFMT_RGB) {
      if (rd->ham)
        do {
          tmp = ((c * 255) / (val - 1)) * 0x01010101;
          *cmap++ = tmp; *cregs++ = tmp;
          *cmap++ = tmp; *cregs++ = tmp;
          *cmap++ = tmp; *cregs++ = tmp;
        } while (++c < val);
      else
        do {
          *cmap++ = c; *cregs++ = c;
          *cmap++ = c; *cregs++ = c; 
          *cmap++ = c; *cregs++ = c;
        } while (c+=0x01010101,--val);
      rd->fmt += RECTFMT_LUT8;
    }
    else {
      if (rd->gray)
        do {
           c = coltab->red; c *= 11; r = coltab->green; c += 16*r; r = coltab->blue; c += 5*r; c >>= 5; c *= 0x01010101;
           *cmap++ = c; *cregs++ = c;
           *cmap++ = c; *cregs++ = c;
           *cmap++ = c; *cregs++ = c;
        } while (++coltab,--val);
      else
        do {
          *cmap++ = (r = coltab->red  ); *cregs++ = (r *= 0x01010101);
          *cmap++ = (g = coltab->green); *cregs++ = (g *= 0x01010101);
          *cmap++ = (b = coltab->blue ); *cregs++ = (b *= 0x01010101);
        } while (++coltab,--val);
      rd->ham = rd->gray = 0;
    }
  }
}

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

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

/*
** load PNG picture
*/

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

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

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

      rd->pmgr.error = error; rd->bmhd = NULL; rd->u.p[r_fh] = 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_fh],
                 DTA_SourceType,    &rd->u.p[r_src],
                 DTA_Name,          &rd->title,
                 TAG_DONE);

      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_fh]) {

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

            if ((rd->png_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING,&rd->pmgr,NULL,NULL))) {

              if ((rd->info_ptr=png_create_info_struct(rd->png_ptr))) {

                rd->pmgr.error = DTERROR_INVALID_DATA;

                if (!setjmp(rd->png_ptr->jmpbuf)) {

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

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

                  if ((rd->pmgr.error=(*decode)(rd->pmgr.cb,rd)) == 0)
                    png_read_end(rd->png_ptr,NULL);
                }
                rd = dt; decode_cleanup(cb=rd->pmgr.cb,rd);
              }
              png_destroy_read_struct(&rd->png_ptr,&rd->info_ptr,NULL);
            }
            CloseFFR(rd->pmgr.handle); pool = rd->pmgr.pool;
          }
          error = rd->pmgr.error;
        }
      }
    }
    DeletePool(pool);
  }

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

  return TRUE;
}

/*
** save as PNG file
*/

INLINE LONG SavePNG(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 *)SavePNG(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 ((o != (Object *)cl) || !LoadPNG(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;
}
