#ifndef XPKMASTER_SEEK_C
#define XPKMASTER_SEEK_C

/* Routinesheader

	Name:		seek.c
	Main:		xpkmaster
	Versionstring:	$VER: seek.c 1.0 (30.10.1998)
	Author:		SDI
	Distribution:	Freeware
	Description:	Implementation of XpkSeek

 1.0   30.10.98 : first version
*/

#include <exec/memory.h>
#include <proto/exec.h>
#include <proto/xpkmaster.h>
#include "xpkmaster.h"

/* get chunk header and and seek data when necessary */
XPK_ALLINONE LONG addseek(struct XpkBuffer *xbuf)
{
  LONG ulen;
  struct SeekDataList *sdl;

  if(!(xbuf->xb_Flags & XMF_SEEK))
    return 0;

  ulen = xbuf->xb_UCur;

  /* check if already included */
  sdl = xbuf->xb_SeekDataList;
  while(sdl && sdl->sdl_Next)
    sdl = sdl->sdl_Next;
  if(sdl && sdl->sdl_Data[sdl->sdl_Used-1].sd_ULen >= ulen)
    return 0; /* already done, so we quit */
  if(!sdl || sdl->sdl_Used == SEEKENTRYNUM)
  {
    struct SeekDataList *sdl2;
    if(!(sdl2 = (struct SeekDataList *)
    AllocMem(sizeof(struct SeekDataList), MEMF_CLEAR|MEMF_PUBLIC)))
      return (xbuf->xb_Result = XPKERR_NOMEM);
    if(!xbuf->xb_SeekDataList)
      xbuf->xb_SeekDataList = sdl2;
    else
      sdl->sdl_Next = sdl2;
    sdl = sdl2;
  }

  sdl->sdl_Data[sdl->sdl_Used].sd_FilePos = xbuf->xb_InBufferPos;
  sdl->sdl_Data[sdl->sdl_Used].sd_ULen = ulen;
  sdl->sdl_Data[(sdl->sdl_Used)++].sd_CLen = xbuf->xb_CCur;

#ifdef DEBUG
  DebugRunTime("addseek: added entry (pos %ld, ulen %ld)",
  xbuf->xb_InBufferPos, ulen);
#endif

  return 0;
}

XPK_ALLINONE void freeseek(struct XpkBuffer *xbuf)
{
  struct SeekDataList *sdl, *sdl2;
  
  sdl = xbuf->xb_SeekDataList;
  while(sdl)
  {
    sdl2 = sdl->sdl_Next;
    FreeMem(sdl, sizeof(struct SeekDataList));
    sdl = sdl2;
  }
  xbuf->xb_SeekDataList = 0;
}

LONG doseek(struct XpkBuffer *xbuf, LONG pos)
{
  LONG ulen, clen;
  XpkChunkHeader *lochdr = &(xbuf->xb_Headers.h_Loc);
  struct SeekDataList *sdl;

  if(pos > xbuf->xb_Fib.xf_ULen)
    return XPKERR_BADPARAMS;

  if(xbuf->xb_Format == XPKMODE_UPUP)
  {
    xbuf->xb_Flags &= ~XMF_EOF;

    if(!(hookread(xbuf, XIO_SEEK, 0, pos - xbuf->xb_InBufferPos)))
      return xbuf->xb_Result;

    xbuf->xb_Fib.xf_CCur = xbuf->xb_Fib.xf_UCur = xbuf->xb_InBufferPos;
    xbuf->xb_Fib.xf_NLen = Min(xbuf->xb_InLen - xbuf->xb_Fib.xf_UCur,
    DEFAULTCHUNKSIZE) + XPK_MARGIN;

    return 0;
  }

  for(sdl = xbuf->xb_SeekDataList; sdl; sdl = sdl->sdl_Next)
  {
    LONG i;
    for(i = 0; i < sdl->sdl_Used; ++i)
    {
      if(sdl->sdl_Data[i].sd_ULen > pos)
      {
        if(!(hookread(xbuf, XIO_SEEK, 0, sdl->sdl_Data[i].sd_FilePos -
        xbuf->xb_Headers.h_LocSize - xbuf->xb_InBufferPos)))
	  return xbuf->xb_Result;
        if(!(hookread(xbuf, XIO_READ, lochdr, xbuf->xb_Headers.h_LocSize)))
          return xbuf->xb_Result;
        getUClen(xbuf, &ulen, &clen);
        xbuf->xb_UCur = sdl->sdl_Data[i].sd_ULen - ulen;
        xbuf->xb_CCur = sdl->sdl_Data[i].sd_CLen - clen;
        updatefib(xbuf);
        return (LONG) (pos - xbuf->xb_Fib.xf_UCur);
      }
    }
  }

  /* this is called when need a forward seek and we do not have the seek
     entries */
  while(xbuf->xb_UCur <= pos)
  {
    if(lochdr->xch_Word.xchw_Type == XPKCHUNK_END)
      return XPKERR_BADPARAMS;

    getUClen(xbuf, &ulen, &clen);
    if(!(hookread(xbuf, XIO_SEEK, 0, ROUNDLONG(clen))))
      return xbuf->xb_Result;
    if(!(hookread(xbuf, XIO_READ, lochdr, xbuf->xb_Headers.h_LocSize)))
      return xbuf->xb_Result;
    if(updatefib(xbuf))
      return xbuf->xb_Result;
  }
  return (LONG) (pos - xbuf->xb_Fib.xf_UCur);
}

/**************************************************************************
 *
 *   XpkSeek() - move around on a compressed file
 *
 */

/* Return codes < 0 are error codes. Codes >= 0 are file position */
LONG LIBXpkSeek(void)
{
	struct XpkBuffer *xbuf = (struct XpkBuffer *)REG_A0;
	LONG dist = (LONG)REG_D0;
	LONG mode = (LONG)REG_D1;

  LONG err = XPKERR_BADPARAMS;

  if((xbuf->xb_Format != XPKMODE_UPSTD && xbuf->xb_Format != XPKMODE_UPUP) ||
  (xbuf->xb_SubInfo->xi_Flags & (XPKIF_NOSEEK|XPKIF_PREREADHDR)))
    err = XPKERR_NOFUNC;
  else if(xbuf->xb_Flags & XMF_SEEK)
  {
    switch(mode)
    {
    case XPKSEEK_CURRENT: err = doseek(xbuf, xbuf->xb_Fib.xf_UCur + dist); break;
    case XPKSEEK_BEGINNING: err = doseek(xbuf, dist); break;
    case XPKSEEK_END: err = doseek(xbuf, xbuf->xb_Fib.xf_ULen + dist); break;
/*  default: err = XPKERR_BADPARAMS; break; */
    }
#ifdef DEBUG
  DebugRunTime("XpkSeek: position after seek (pos %ld, ulen %ld)",
  xbuf->xb_InBufferPos, xbuf->xb_Fib.xf_UCur);
#endif
  }

  return err;
}

#endif /* XPKMASTER_SEEK_C */
