/*
 * Copyright 2001-2007 Jorge Arellano Cid <jcid@dillo.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 */

/**
 * @file
 * Concomitant control chain (CCC).
 * Theory and code by Jorge Arellano Cid
 */

#include "msg.h"
#include "chain.h"
#include "../dlib/dlib.h"

#define VERBOSE 0

/**
 * Show debugging info
 */
#if VERBOSE
static void Chain_debug_msg(char *FuncStr, int Op, int Branch, int Dir,
                            ChainLink *Info)
{
   const char *StrOps[] = {"", "OpStart", "OpSend",
                            "OpStop", "OpEnd", "OpAbort"};
   MSG("%-*s: %-*s [%d%s] Info=%p Flags=%d\n",
       12, FuncStr, 7, StrOps[Op], Branch, (Dir == 1) ? "F" : "B",
       Info, Info ? Info->Flags : -1);
}
#else
static void Chain_debug_msg(char *FuncStr, int Op, int Branch, int Dir,
                            ChainLink *Info) { }
#endif
/**
 * Create and initialize a new chain-link
 */
ChainLink *a_Chain_new(void)
{
   return dNew0(ChainLink, 1);
}

/**
 * Create a new link from module A to module B.
 * @param Direction tells whether to make a forward or backward link.
 *  - The link from 'A' to 'B' has @p Direction direction.
 *  - The main flow of information names the FWD direction.
 * @param AtoB_branch branch on which 'B' receives communications from 'A'
 * @param BtoA_branch branch on which 'A' receives communications from 'B'
 */
ChainLink *a_Chain_link_new(ChainLink *AInfo, ChainFunction_t AFunc,
                            int Direction, ChainFunction_t BFunc,
                            int AtoB_branch, int BtoA_branch)
{
   ChainLink *NewLink = a_Chain_new();
   ChainLink *OldLink = AInfo;

   if (Direction == BCK) {
      NewLink->Fcb       = AFunc;
      NewLink->FcbInfo   = AInfo;
      NewLink->FcbBranch = BtoA_branch;
      OldLink->Bcb       = BFunc;
      OldLink->BcbInfo   = NewLink;
      OldLink->BcbBranch = AtoB_branch;

   } else { /* FWD */
      NewLink->Bcb       = AFunc;
      NewLink->BcbInfo   = AInfo;
      NewLink->BcbBranch = BtoA_branch;
      OldLink->Fcb       = BFunc;
      OldLink->FcbInfo   = NewLink;
      OldLink->FcbBranch = AtoB_branch;
   }

   return NewLink;
}

/**
 * Unlink a previously used link.
 * @param Direction tells whether to unlink the forward or backward link.
 */
void a_Chain_unlink(ChainLink *Info, int Direction)
{
   if (Direction == FWD) {
      Info->Fcb = NULL;
      Info->FcbInfo = NULL;
      Info->FcbBranch = 0;
   } else {      /* BCK */
      Info->Bcb = NULL;
      Info->BcbInfo = NULL;
      Info->BcbBranch = 0;
   }
}

/**
 * Issue the forward callback of the 'Info' link
 * @return 1 if OK, 0 if not operative.
 */
int a_Chain_fcb(int Op, ChainLink *Info, void *Data1, void *Data2)
{
   int ret = 0;

   if (Info->Flags & (CCC_Ended + CCC_Aborted)) {
      /* CCC is not operative */
   } else if (Info->Fcb) {
      /* flag the caller */
      if (Op == OpEnd)
         Info->Flags |= CCC_Ended;
      else if (Op == OpAbort)
         Info->Flags |= CCC_Aborted;

      Info->Fcb(Op, Info->FcbBranch, FWD, Info->FcbInfo, Data1, Data2);
      ret = 1;
   }
   return ret;
}

/**
 * Issue the backward callback of the 'Info' link
 * @return 1 if OK, 0 if not operative.
 */
int a_Chain_bcb(int Op, ChainLink *Info, void *Data1, void *Data2)
{
   int ret = 0;

   if (Info->Flags & (CCC_Ended + CCC_Aborted)) {
      /* CCC is not operative */
   } else if (Info->Bcb) {
      /* flag the caller */
      if (Op == OpEnd)
         Info->Flags |= CCC_Ended;
      else if (Op == OpAbort)
         Info->Flags |= CCC_Aborted;

      Info->Bcb(Op, Info->BcbBranch, BCK, Info->BcbInfo, Data1, Data2);
      ret = 1;
   }
   return ret;
}

/**
 * Issue the backward callback of the 'Info' link and then the
 * forward callback (used for OpAbort and OpStop).
 * @return 1 if OK, 0 if not operative.
 */
int a_Chain_bfcb(int Op, ChainLink *Info, void *Data1, void *Data2)
{
   int ret;

   ret = a_Chain_bcb(Op, Info, Data1, Data2);
   if (ret == 1) {
      /* we need to clear the flag to reuse this 'Info' ChainLink */
      if (Op == OpEnd)
         Info->Flags &= ~CCC_Ended;
      else if (Op == OpAbort)
         Info->Flags &= ~CCC_Aborted;

      ret = a_Chain_fcb(Op, Info, Data1, Data2);
   }
   return ret;
}


/**
 * Allocate and initialize a new DataBuf structure
 */
DataBuf *a_Chain_dbuf_new(void *buf, int size, int code)
{
   DataBuf *dbuf = dNew(DataBuf, 1);
   dbuf->Buf = buf;
   dbuf->Size = size;
   dbuf->Code = code;
   return dbuf;
}

/**
 * Check whether the CCC is operative.
 * Also used to hook debug information.
 *
 * @return 1 if ready to use, 0 if not operative.
 */
int a_Chain_check(char *FuncStr, int Op, int Branch, int Dir,
                  ChainLink *Info)
{
   int ret = 0;

   /* Show status information */
   Chain_debug_msg(FuncStr, Op, Branch, Dir, Info);

   if (Info->Flags & (CCC_Ended + CCC_Aborted)) {
      /* CCC is not operative */
      MSG_WARN("CCC: call on already finished chain. Flags=%s%s\n",
               Info->Flags & CCC_Ended ? "CCC_Ended " : "",
               Info->Flags & CCC_Aborted ? "CCC_Aborted" : "");
   } else {
      ret = 1;
   }
   return ret;
}

