/*      Copyright (C) 2001, 2002, 2003, 2004 Stijn van Dongen
 *
 * This file is part of Zoem. You can redistribute and/or modify Zoem under the
 * terms of the GNU General Public License;  either version 2 of the License or
 * (at your option) any later  version.  You should have received a copy of the
 * GPL along with Zoem, in the file COPYING.
*/


/* TODO
 *    The sink stack design should be extensively documented.
 *    Basically, the lowest element denotes the default output file.
 *    which can be changed by writeto#3.
 *    Each sink carries an env stack and an xml stack with it.
*/

#include <stdio.h>

#include "filter.h"
#include "util.h"
#include "entry.h"
#include "key.h"
#include "read.h"
#include "sink.h"

#include "util/hash.h"
#include "util/ting.h"
#include "util/types.h"
#include "util/io.h"
#include "util/minmax.h"


typedef struct
{  mcxLink*       sp
;
}  sinkStack      ;


static sinkStack sst = {  NULL  }  ;
static sinkStack* sinkStack_g = &sst;

static mcxHash*   wrtTable_g     =  NULL;    /* open output files */

static sink* sd_g = NULL;

void mod_sink_init
(  int   n
)
   {  wrtTable_g        =  yamHashNew(n)
   ;  sinkStack_g->sp   =  mcxLinkNew(10, NULL, MCX_GRIM_ARITHMETIC)
;  }

void warn_stack_free
(  yamStack** st
,  const char* type
)
   {  mcxLink* lk = (*st)->sp
   ;  while (lk->prev)
         mcxErr("env", "open %s <%s>", type, ((mcxTing*)lk->val)->str)
      ,  lk = lk->prev
   ;  ting_stack_free(st)
;  }

void sinkFree
(  sink* sd
)
   {  filterFree(sd->fd)
   ;  warn_stack_free(&(sd->envStack), "environment")
   ;  warn_stack_free(&(sd->xmlStack), "tag")
   ;  mcxFree(sd->fname)
   ;  mcxFree(sd)
;  }

/* this was part of attempt to solve system#3 problems,
 * but the problem was pbb entirely due to unflushed buffers.
 * fsync does not seem totally portable.
*/
#if 0
void yamOutputFDSync
(  void
)
   {  mcxHashWalk* walk    =  mcxHashWalkNew(wrtTable_g)
   ;  mcxKV* kv
   ;  while((kv = mcxHashWalkStep(walk)))
      {  mcxIO *xf = (mcxIO*) kv->val
      ;  fsync(fileno(xf->fp))
   ;  }
   }

void yamOutputFDClose
(  void
)
   {  mcxHashWalk* walk    =  mcxHashWalkNew(wrtTable_g)
   ;  mcxKV* kv
   ;  while((kv = mcxHashWalkStep(walk)))
      {  mcxIO *xf = (mcxIO*) kv->val
      ;  close(fileno(xf->fp))
   ;  }
   }
#endif


void yamOutputClose
(  const char*  s
)
   {  mcxTing* fname = mcxTingNew(s)
   ;  mcxKV* kv = mcxHashSearch(fname, wrtTable_g, MCX_DATUM_FIND)
   ;  if (kv)
      {  mcxIO* xf = (mcxIO*) kv->val
      ;  fflush(xf->fp)
      ;  mcxIOclose(xf)
   ;  }
      /* keep the object around; repeated \writeto should append
       * (this might be settable someday)
      */
   ;  mcxTingFree(&fname)
;  }


sink* sinkNew
(  mcxIO* xf
)
   {  sink* sd=  mcxAlloc(sizeof(sink), EXIT_ON_FAIL)
   ;  sd->fd         =  xf ? filterNew(xf->fp) : NULL
   ;  sd->xmlStack   =  ting_stack_new(10)
   ;  sd->envStack   =  ting_stack_new(10)
   ;  sd->fname      =  mcxTingStr(xf->fn)
   ;  return sd
;  }


mcxIO* yamOutputNew
(  const char*  s
)
   {  mcxIO *xf            =  NULL
   ;  mcxTing *fname       =  mcxTingNew(s)
   ;  mcxKV *kv

   ;  if (fname->len > 1024)
      {  yamErr
         (  "yamOutputNew"
         ,  "output file name expansion too <%d> long"
         ,  fname->len
         )
      ;  mcxTingFree(&fname)
      ;  return NULL
   ;  }

      kv = mcxHashSearch(fname, wrtTable_g, MCX_DATUM_FIND)

   ;  if (!kv)
      {  xf = mcxIOnew(fname->str, "w")
      ;  kv = mcxHashSearch(fname, wrtTable_g, MCX_DATUM_INSERT)
      ;  kv->val = xf
   ;  }
      else
      {  xf = (mcxIO*) kv->val
      ;  if (!xf->fp)            /* used by writeto wrapper */
         mcxIOrenew(xf, NULL, "a")
      ;  mcxTingFree(&fname)
   ;  }

      if (!xf->fp && mcxIOopen(xf, RETURN_ON_FAIL) != STATUS_OK)
      {  yamErr
         (  "yamOutputNew"
         ,  "can not open file <%s> for writing\n"
         ,  s
         )
      ;  if (!(kv = mcxHashSearch(xf->fn, wrtTable_g, MCX_DATUM_DELETE)))
         {  yamErr
            (  "yamOutputNew panic"
            ,  "can not find IO object for stream <%s>"
            ,  xf->fn->str
            )
         ;  return NULL
      ;  }
         fname = ((mcxTing*) (kv->key))
      ;  mcxTingFree(&fname)
         /* might have xf->usr (in renewal case) should free it as well
          * (check though)
         */
      ;  mcxIOfree(&xf)
      ;  return NULL
   ;  }

      if (!xf->usr)
      xf->usr = sinkNew(xf)
   ;  else
      ((sink*) xf->usr)->fd->fp = xf->fp
            /* this case is relevant for "a"(ppend) renewal (only),
             * as the fp will have changed
            */

   ;  return xf
;  }


void yamIOfree_v
(  void*    xfpp
)
   {  mcxIO* xf =  *((mcxIO**) xfpp)
   ;  sinkFree((sink*) xf->usr)
   ;  if (1)
      mcxIOfree(&xf)
;  }

void mod_sink_exit
(  void
)
   {  mcxHashFree(&wrtTable_g, mcxTingFree_v, yamIOfree_v)
   ;  mcxLinkFree(&(sinkStack_g->sp), NULL)
;  }

void sinkStackPush
(  sink* sd
)
   {  mcxLink* lk = mcxLinkAfter(sinkStack_g->sp, sd)
   ;  sinkStack_g->sp = lk
;  }


void sinkStackPop
(  void
)
   {  mcxLink* lk = sinkStack_g->sp->prev
   ;  mcxLinkDelete(sinkStack_g->sp)
   ;  sinkStack_g->sp = lk
;  }


mcxLink* sinkStackBase
(  void
)
   {  mcxLink* lk = sinkStack_g->sp
   ;  while (lk->prev)
      lk = lk->prev
   ;  return lk
;  }


void sinkStackSetDefault
(  sink*   sd
)
   {  mcxLink* base = sinkStackBase()
   ;  if (base->next)
      mcxLinkDelete(base->next)     /* remove previous default */
   ;  mcxLinkAfter(base, sd)
   ;  if (!base->next->next)        /* we deleted stack pointer */
      sinkStack_g->sp = base->next
;  }


yamStack* currentXmlStack
(  void
)
   {  return ((sink*) sinkStack_g->sp->val)->xmlStack
;  }


yamStack* currentEnvStack
(  void
)
   {  return ((sink*) sinkStack_g->sp->val)->envStack
;  }


sink* sinkGetDefault
(  void
)
   {  return sd_g
;  }

void sinkSetDefault
(  sink*   sd
,  int     fltidx
)
   {  sinkStackSetDefault(sd)       /* for xml/env wellformedness */

   ;  filterSetDefault(sd->fd, fltidx)
   ;  sd_g = sd
   ;  yamKeySet("__fnout__", sd->fname)
;  }



mcxTing* sinkGetDefaultName
(  void
)
   {  if (sd_g)
      return mcxTingNew(sd_g->fname)
   ;  else
         yamErr("filter PBD", "request for default file name: absent!")
      ,  mcxExit(1)
   ;  return NULL
;  }

