/*
** our include
*/

#include "classbase.h"

/*
** structure(s)
*/

struct ReadData {
  ULONG width,
        height,
        maxval,
        rowsize,
        depth,
        mode,
        fmt,
        ham;
  LONG  (*rdline)(APTR,UBYTE *);
  VOID  (*encode)(APTR,ULONG  );
  APTR  obj,
        pool,
        bmhd,
        handle;
  ULONG title;
  union {
    struct RastPort rp[2];
    UBYTE  buf[3];
    ULONG  p[2];
  } u;
};

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

/*
** functions for asynchronous file I/O
*/

#if !defined(ALL_IN_ONE)

APTR OpenFFR(APTR,APTR,LONG,BPTR);
LONG FFRGetC(APTR);
LONG FFRRead(APTR,APTR,LONG);
LONG FFRWrite(APTR,APTR,LONG);
LONG CloseFFR(APTR);

/*
** functions for HAM encoding
*/

VOID CreateHAM6Line(UBYTE *,UBYTE *,UBYTE *,UBYTE *,LONG,ULONG);
VOID CreateHAM8Line(UBYTE *,UBYTE *,UBYTE *,UBYTE *,LONG,ULONG);

#endif

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

#define RECTFMT_RGB  0
#define RECTFMT_LUT8 3

/*
** shortcut
*/

#define IsWhite(c,cr,lf) (((c)==' ') || ((c)=='\t') || ((c)==cr) || ((c)==lf))

/*
** redirect library bases
*/

#define SysBase       cb->SysBase
#define DOSBase       cb->DOSBase
#define GfxBase       cb->GfxBase
#define IntuitionBase cb->IntuitionBase
#define DataTypesBase cb->DataTypesBase
#define PictureClass  cb->PictureClass

#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/pbm44.prefs"

enum {
  RD_V42MODE=0,RD_GRAY,RD_HAM,ARG_LAST
};

#define TEMPLATE "V42MODE/S,GRAY/S,HAM/K/N"

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

  /* 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 */
  result = (*start)(cb,&prefs[0],data);

  /* free args */
  if (rdarg) {
    FreeArgs(rdarg); FreeDosObject(DOS_RDARGS,rdarg);
  }

  /* status */
  return result;
}

/*
** do nothing
*/

STATIC VOID stub(VOID)
{
  /* 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);
}

/*
** read a number
*/

STATIC LONG getnum(APTR handle)
{ LONG tmp,num,(*get)(APTR)=(LONG (*)(APTR))FFRGetC,CR='\r',LF='\n',CM='#';
  LREG(d0,LONG in);

  while (((in=(*get)(handle)) == CM) || IsWhite(in,CR,LF))
    if (in == CM)
      while (((in=(*get)(handle)) != CR) && (in != LF))
        if (in < 0) return -1;

  if (num=in-'0',(num < 0) || (num > 9))
    return -1;

  while (tmp=(in=(*get)(handle))-'0',(tmp >= 0) && (tmp <= 9))
    num*=10,num+=tmp;

  if (in == CM)
    do
      do
        if ((in=(*get)(handle)) < 0)
          return -1;
      while ((in != CR) && (in != LF));
    while ((in=(*get)(handle)) == CM);

  return ((in < 0) || IsWhite(in,CR,LF)) ? num : -1;
}

/*
** read ascii or binary image line
*/

STATIC LONG read_asc_mono(struct ReadData *rd,UBYTE *lbuf)
{ ULONG rowsize,val;

  rowsize = rd->rowsize;
  do {
    if ((LONG)(val=getnum(rd->handle)) < 0)
      return 1;
  } while (*lbuf++=val?0:1,--rowsize);

  return 0;
}

STATIC LONG read_raw_mono(struct ReadData *rd,UBYTE *lbuf)
{ ULONG rowsize,lsize,mask,val;
  UBYTE *dst = lbuf;

  lsize = ((rowsize = rd->rowsize) + 7) / 8;

  if (FFRRead(rd->handle,(dst-=lsize,dst+=rowsize),lsize) == lsize) {
    mask = 0;
    do {
      if ((mask>>=1) == 0)
        val=*dst++,mask = 0x80;
    } while (*lbuf++=val&mask?0:1,--rowsize);
    return 0;
  }

  return 1;
}

STATIC LONG read_asc_rgb(struct ReadData *rd,UBYTE *lbuf)
{ ULONG rowsize,val;
  UBYTE *dst = lbuf;

  rowsize = rd->rowsize;
  do {
    if ((LONG)(val=getnum(rd->handle)) < 0)
      return 1;
  } while (*dst++=(val*255)/rd->maxval,--rowsize);

  (*rd->encode)(lbuf,rd->width); return 0;
}

STATIC LONG read_raw_rgb(struct ReadData *rd,UBYTE *lbuf)
{ ULONG rowsize,maxval,val;

  if (FFRRead(rd->handle,lbuf,rowsize=rd->rowsize) == rowsize) {
    if ((maxval=rd->maxval) < 255)
      { UBYTE *dst = lbuf;
        do;while(val=*dst,*dst++=(val*255)/maxval,--rowsize); }
    (*rd->encode)(lbuf,rd->width); return 0;
  }

  return 1;
}

/*
** V43 interface
*/

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

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

  SetDTAttrs(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,  height=rd->height,
             DTA_ObjName,      rd->title,
             TAG_DONE);

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

  if ((lbuf=AllocPooled(rd->pool,rd->rowsize)) != NULL) {

    y = 0;
    do {
      if ((*rd->rdline)(rd,lbuf))
        break;
      DoSuperMethod(PictureClass,obj,
                    PDTM_WRITEPIXELARRAY,lbuf,rd->fmt,0,0,y,rd->width,1);
    } while (++y < height);

    return ret=y-height;
  }

  return ERROR_NO_FREE_STORE;
}

/*
** 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,Object *obj,struct ReadData *rd)
{ ULONG height,y;
  UBYTE *lbuf;
  LONG ret = ERROR_NO_FREE_STORE;

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

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

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

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

      if ((lbuf=AllocPooled(rd->pool,(rd->rowsize+15)&~15)) != NULL) {

        y = 0;
        do {
          if ((*rd->rdline)(rd,lbuf))
            break;
          WritePixelLine8(&rd->u.rp[0],0,y,rd->width,lbuf,&rd->u.rp[1]);
        } while (++y < height);

        if (WaitBlit(),(y-=height) == 0) {

          SetDTAttrs(rd->obj, NULL, NULL,
                     PDTA_ModeID,      GetBestModeID(cb,rd,rd->width,height),
                     PDTA_BitMap,      rd->u.rp[0].BitMap,
                     DTA_NominalHoriz, rd->width,
                     DTA_NominalVert,  height,
                     DTA_ObjName,      rd->title,
                     TAG_DONE);

          rd->u.rp[0].BitMap = NULL;
        }
        ret = y;
      }
      FreeBitMap(rd->u.rp[1].BitMap);
    }
    FreeBitMap(rd->u.rp[0].BitMap);
  }

  return ret;
}

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

STATIC LONG read_pbm_header(struct ClassBase *cb,LONG *prefs,APTR data)
{ struct ReadData *rd = (struct ReadData *)data;
  ULONG *methods,*cregs,depth,gray,tmp,val,c;
  struct BitMapHeader *bmhd;
  LREG(a0,APTR ptr);
  UBYTE *cmap,*buf;

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

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

  /* lets go */
  if (FFRRead(rd->handle,buf=rd->u.buf,3) != 3)
    return FALSE;

  /* verify format */
  if (val=buf[1]-'1',buf[0] != 'P' || !IsWhite(buf[2],'\r','\n'))
    return FALSE;

  /* select read function */
  if (depth=1,(ptr=(APTR)read_asc_mono,val != 0) && (ptr=(APTR)read_raw_mono,val != 3))
    if (ptr=(APTR)read_asc_rgb,(depth=24,val != 2) && (depth=8,val != 1))
      if (ptr=(APTR)read_raw_rgb,(depth=8,val != 4) && (depth=24,val != 5))
        return FALSE;
  rd->rdline = (LONG (*)())ptr;

  /* retrieve image dimension and maxval */
  if (((LONG)(rd->width=getnum(rd->handle)) < 1) || ((LONG)(rd->height=getnum(rd->handle)) < 1))
    return FALSE;
  if (depth > 1) {
    LONG num = getnum(rd->handle);
    if ((num < 1) || (val >= 4 && num > 255))
      return FALSE;
    rd->maxval = num;
  }

  /* handle truecolour data */
  if (val=rd->width,ptr=(APTR)stub,depth == 24) {
    if ((tmp=rd->ham)) {
      if (ptr=(APTR)RGBtoHAM6,tmp != 6)
        ptr=(APTR)RGBtoHAM8;
      rd->mode = (depth = tmp);
    }
    if (!tmp && (gray || rd->mode)) {
      depth = 8; ptr = (APTR)grayscale;
    }
    val *= 3;
  }
  rd->rowsize = val; rd->encode = (VOID (*)())ptr; rd->depth = depth;

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

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

    SetDTAttrs(rd->obj, NULL, NULL, PDTA_NumColors,val=1<<(rd->ham?depth-=2:depth), TAG_DONE);
    if (GetDTAttrs(rd->obj, PDTA_ColorRegisters,&rd->u.p[r_col], PDTA_CRegs,&rd->u.p[r_crg], TAG_DONE) != 2)
      return FALSE;

    cregs = (ULONG *)rd->u.p[r_crg]; cmap = (UBYTE *)rd->u.p[r_col]; c = 0;
    if (tmp=depth==1?0xffffffff:0x01010101,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+=tmp,--val);

    val = RECTFMT_LUT8;
  }
  rd->fmt = val; 

  return TRUE; /* success */
}

/*
** load PBM picture
*/

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

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

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

      rd->pool       = pool;
    //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);

      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 (startup(cb,rd,read_pbm_header,pool)) {

              if (decode=decode_picture_v42,!rd->mode)
                decode=decode_picture_v43;
              error = (*decode)(cb,obj,rd);
            }
            (VOID)CloseFFR(rd->handle);
          }
          else
            error = ERROR_NO_FREE_STORE;
        }
      }
    }
    DeletePool(pool);
  }

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

  return TRUE;
}

/*
** save as PBM picture
*/

INLINE LONG SavePBM(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 *)SavePBM(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) || !LoadPBM(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;
}
