/***************************************************************************
    begin       : Mon Mar 01 2004
    copyright   : (C) 2018 by Martin Preuss
    email       : martin@libchipcard.de

 ***************************************************************************
 *          Please see toplevel file COPYING for license details           *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "sepa_p.h"

#include "aqbanking/i18n_l.h"

#include <aqbanking/banking.h>
#include <aqbanking/banking_be.h>
#include <aqbanking/types/balance.h>

#include <gwenhywfar/debug.h>
#include <gwenhywfar/misc.h>
#include <gwenhywfar/gui.h>
#include <gwenhywfar/inherit.h>

#include <ctype.h>



GWEN_LIST_FUNCTIONS(AH_IMEXPORTER_SEPA_PMTINF, AH_ImExporter_Sepa_PmtInf)
GWEN_INHERIT(AB_IMEXPORTER, AH_IMEXPORTER_SEPA);



static AH_IMEXPORTER_SEPA_PMTINF *AH_ImExporter_Sepa_PmtInf_new()
{
  AH_IMEXPORTER_SEPA_PMTINF *pmtinf;

  GWEN_NEW_OBJECT(AH_IMEXPORTER_SEPA_PMTINF, pmtinf)
  GWEN_LIST_INIT(AH_IMEXPORTER_SEPA_PMTINF, pmtinf)
  pmtinf->value=AB_Value_new();
  pmtinf->transactions=AB_Transaction_List2_new();

  return pmtinf;
}



static void AH_ImExporter_Sepa_PmtInf_free(AH_IMEXPORTER_SEPA_PMTINF *pmtinf)
{
  if (pmtinf) {
    free(pmtinf->ctrlsum);
    AB_Value_free(pmtinf->value);
    AB_Transaction_List2_free(pmtinf->transactions);
    GWEN_LIST_FINI(AH_IMEXPORTER_SEPA_PMTINF, pmtinf)
    GWEN_FREE_OBJECT(pmtinf)
  }
}



AB_IMEXPORTER *AB_ImExporterSEPA_new(AB_BANKING *ab)
{
  AB_IMEXPORTER *ie;
  AH_IMEXPORTER_SEPA *ieh;

  ie=AB_ImExporter_new(ab, "sepa");
  GWEN_NEW_OBJECT(AH_IMEXPORTER_SEPA, ieh);
  GWEN_INHERIT_SETDATA(AB_IMEXPORTER, AH_IMEXPORTER_SEPA, ie, ieh,
                       AH_ImExporterSEPA_FreeData);
  AB_ImExporter_SetImportFn(ie, AH_ImExporterSEPA_Import);
  AB_ImExporter_SetExportFn(ie, AH_ImExporterSEPA_Export);
  AB_ImExporter_SetCheckFileFn(ie, AH_ImExporterSEPA_CheckFile);
  return ie;
}



void GWENHYWFAR_CB AH_ImExporterSEPA_FreeData(void *bp, void *p)
{
  AH_IMEXPORTER_SEPA *ieh;

  ieh=(AH_IMEXPORTER_SEPA *)p;
  GWEN_FREE_OBJECT(ieh);
}



int AH_ImExporterSEPA_Import(AB_IMEXPORTER *ie,
                             AB_IMEXPORTER_CONTEXT *ctx,
                             GWEN_SYNCIO *sio,
                             GWEN_DB_NODE *params)
{
  AH_IMEXPORTER_SEPA *ieh;

  assert(ie);
  ieh=GWEN_INHERIT_GETDATA(AB_IMEXPORTER, AH_IMEXPORTER_SEPA, ie);
  assert(ieh);


  return GWEN_ERROR_NOT_SUPPORTED;
}



static int AH_ImExporterSEPA_Export_Pain_Setup(AB_IMEXPORTER *ie,
                                               AB_IMEXPORTER_CONTEXT *ctx,
                                               GWEN_XMLNODE *painNode,
                                               uint32_t doctype[],
                                               AH_IMEXPORTER_SEPA_PMTINF_LIST **pList)
{
  GWEN_XMLNODE *n;
  AB_IMEXPORTER_ACCOUNTINFO *ai;
  AB_TRANSACTION *t;
  AH_IMEXPORTER_SEPA_PMTINF_LIST *pl;
  AH_IMEXPORTER_SEPA_PMTINF *pmtinf;
  int tcount=0;
  AB_VALUE *v;
  GWEN_BUFFER *tbuf;
  char *ctrlsum;

  ai=AB_ImExporterContext_GetFirstAccountInfo(ctx);
  if (ai==0) {
    DBG_ERROR(AQBANKING_LOGDOMAIN, "No account info");
    return GWEN_ERROR_NO_DATA;
  }
  else if (AB_ImExporterAccountInfo_List_Next(ai)) {
    DBG_ERROR(AQBANKING_LOGDOMAIN,
              "Account info for more than one local account");
    return GWEN_ERROR_NOT_SUPPORTED;
  }

  t=AB_ImExporterAccountInfo_GetFirstTransaction(ai, 0, 0);
  if (!t) {
    DBG_ERROR(AQBANKING_LOGDOMAIN, "No transactions in ImExporter context");
    return GWEN_ERROR_NO_DATA;
  }

  /* collect matching transactions for storage in a shared PmtInf block */
  pl=AH_ImExporter_Sepa_PmtInf_List_new();
  pmtinf=AH_ImExporter_Sepa_PmtInf_new();
  AH_ImExporter_Sepa_PmtInf_List_Add(pmtinf, pl);
  while (t) {
    const GWEN_DATE *da;
    int day, month, year;
    uint32_t transDate;
    const char *name=NULL, *iban=NULL, *bic=NULL, *cdtrSchmeId=NULL;
    AB_TRANSACTION_SEQUENCE sequenceType=AB_Transaction_SequenceUnknown;
    const char *s;
    const AB_VALUE *tv;

    tcount++;
    da=AB_Transaction_GetDate(t);
    if (da) {
      year=GWEN_Date_GetYear(da);
      month=GWEN_Date_GetMonth(da)-1;
      day=GWEN_Date_GetDay(da);
      transDate=(year<<16)+(month<<8)+(day);
    }
    else
      transDate=0;
    s=AB_ImExporterAccountInfo_GetOwner(ai);
    if (!s || !*s) {
      name=AB_Transaction_GetLocalName(t);
      if (!name || !*name) {
        DBG_ERROR(AQBANKING_LOGDOMAIN,
                  "Missing local name in transaction %d", tcount);
        AH_ImExporter_Sepa_PmtInf_List_free(pl);
        return GWEN_ERROR_BAD_DATA;
      }
    }
    s=AB_ImExporterAccountInfo_GetIban(ai);
    if (!s || !*s) {
      iban=AB_Transaction_GetLocalIban(t);
      if (!iban || !*iban) {
        DBG_ERROR(AQBANKING_LOGDOMAIN,
                  "Missing local IBAN in transaction %d", tcount);
        AH_ImExporter_Sepa_PmtInf_List_free(pl);
        return GWEN_ERROR_BAD_DATA;
      }
    }
    s=AB_ImExporterAccountInfo_GetBic(ai);
    if (!s || !*s) {
#if 0
      bic=AB_Transaction_GetLocalBic(t);
      if (!bic || !*bic) {
        DBG_ERROR(AQBANKING_LOGDOMAIN,
                  "Missing local BIC in transaction %d", tcount);
        AH_ImExporter_Sepa_PmtInf_List_free(pl);
        return GWEN_ERROR_BAD_DATA;
      }
#endif
    }
    if (doctype[0]==8) {
      sequenceType=AB_Transaction_GetSequence(t);
      if (sequenceType==AB_Transaction_SequenceUnknown) {
        DBG_ERROR(AQBANKING_LOGDOMAIN,
                  "Missing sequence type in transaction %d", tcount);
        AH_ImExporter_Sepa_PmtInf_List_free(pl);
        return GWEN_ERROR_BAD_DATA;
      }
      cdtrSchmeId=AB_Transaction_GetCreditorSchemeId(t);
      if (!cdtrSchmeId || !*cdtrSchmeId) {
        DBG_ERROR(AQBANKING_LOGDOMAIN,
                  "Missing creditor scheme id in transaction %d", tcount);
        AH_ImExporter_Sepa_PmtInf_List_free(pl);
        return GWEN_ERROR_BAD_DATA;
      }
    }

    if (pmtinf->tcount) {
      /* specify list of match criteria in one place */
#define TRANSACTION_DOES_NOT_MATCH          \
      (transDate!=pmtinf->transDate ||          \
       (name && strcmp(name, pmtinf->localName)) ||     \
       (iban && strcmp(iban, pmtinf->localIban)) ||     \
       (bic && strcmp(bic, pmtinf->localBic)) ||      \
       (doctype[0]==8 &&            \
  (sequenceType!=pmtinf->sequenceType ||        \
   (cdtrSchmeId && strcmp(cdtrSchmeId, pmtinf->creditorSchemeId)))))

      /* match against current PmtInf block */
      if (TRANSACTION_DOES_NOT_MATCH) {
        /* search for a fitting PmtInf block */
        pmtinf=AH_ImExporter_Sepa_PmtInf_List_First(pl);
        while (pmtinf && TRANSACTION_DOES_NOT_MATCH)
          pmtinf=AH_ImExporter_Sepa_PmtInf_List_Next(pmtinf);
#undef TRANSACTION_DOES_NOT_MATCH

        if (!pmtinf) {
          pmtinf=AH_ImExporter_Sepa_PmtInf_new();
          AH_ImExporter_Sepa_PmtInf_List_Add(pmtinf, pl);
        }
      }
    }

    if (!pmtinf->tcount) {
      /* initialise match data for this PmtInf block */
      pmtinf->localName = name ? name : AB_ImExporterAccountInfo_GetOwner(ai);
      pmtinf->localIban = iban ? iban : AB_ImExporterAccountInfo_GetIban(ai);
      pmtinf->localBic  = bic  ? bic  : AB_ImExporterAccountInfo_GetBic(ai);
      pmtinf->date=da;               /* transaction date (AB_Transaction_GetDate()) */
      pmtinf->transDate=transDate;   /* transaction date short representation ((year<<16)+(month<<8)+(day)) */
      if (doctype[0]==8) {
        pmtinf->sequenceType=sequenceType;
        pmtinf->creditorSchemeId=cdtrSchmeId;
      }
    }

    AB_Transaction_List2_PushBack(pmtinf->transactions, t);
    pmtinf->tcount++;
    tv=AB_Transaction_GetValue(t);
    if (tv==NULL) {
      DBG_ERROR(AQBANKING_LOGDOMAIN,
                "Missing value in transaction %d", tcount);
      AH_ImExporter_Sepa_PmtInf_List_free(pl);
      return GWEN_ERROR_BAD_DATA;
    }
    AB_Value_AddValue(pmtinf->value, tv);

    t=AB_Transaction_List_Next(t);
  }

  /* construct CtrlSum for PmtInf blocks and GrpHdr */
  v=AB_Value_new();
  tbuf=GWEN_Buffer_new(0, 64, 0, 1);
  pmtinf=AH_ImExporter_Sepa_PmtInf_List_First(pl);
  while (pmtinf) {
    AB_Value_toHumanReadableString(pmtinf->value, tbuf, 2, 0);
    pmtinf->ctrlsum=strdup(GWEN_Buffer_GetStart(tbuf));
    assert(pmtinf->ctrlsum);
    GWEN_Buffer_Reset(tbuf);
    AB_Value_AddValue(v, pmtinf->value);
    pmtinf=AH_ImExporter_Sepa_PmtInf_List_Next(pmtinf);
  }

  AB_Value_toHumanReadableString(v, tbuf, 2, 0);
  ctrlsum=strdup(GWEN_Buffer_GetStart(tbuf));
  assert(ctrlsum);
  GWEN_Buffer_free(tbuf);
  AB_Value_free(v);

  /* create GrpHdr */
  n=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, "GrpHdr");
  if (n) {
    GWEN_TIME *ti;
    uint32_t uid;
    char numbuf[32];
    GWEN_XMLNODE *nn;

    GWEN_XMLNode_AddChild(painNode, n);
    ti=GWEN_CurrentTime();

    tbuf=GWEN_Buffer_new(0, 64, 0, 1);

    /* generate MsgId */
    uid=AB_Banking_GetNamedUniqueId(AB_ImExporter_GetBanking(ie), "sepamsg", 1);
    GWEN_Time_toUtcString(ti, "YYYYMMDD-hh:mm:ss-", tbuf);
    snprintf(numbuf, sizeof(numbuf)-1, "%08x", uid);
    GWEN_Buffer_AppendString(tbuf, numbuf);
    GWEN_XMLNode_SetCharValue(n, "MsgId", GWEN_Buffer_GetStart(tbuf));
    GWEN_Buffer_Reset(tbuf);

    /* generate CreDtTm */
    GWEN_Time_toUtcString(ti, "YYYY-MM-DDThh:mm:ssZ", tbuf);
    GWEN_XMLNode_SetCharValue(n, "CreDtTm", GWEN_Buffer_GetStart(tbuf));
    GWEN_Time_free(ti);
    GWEN_Buffer_free(tbuf);

    /* store NbOfTxs */
    GWEN_XMLNode_SetIntValue(n, "NbOfTxs", tcount);
#if 0
    /* store CtrlSum */
    GWEN_XMLNode_SetCharValue(n, "CtrlSum", ctrlsum);
#endif

    /* special treatment for pain.001.001.02 and pain.008.001.01 */
    if (doctype[1]==1 && ((doctype[0]==1 && doctype[2]==2) ||
                          (doctype[0]==8 && doctype[2]==1)))
      GWEN_XMLNode_SetCharValue(n, "Grpg", "GRPD");

    nn=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, "InitgPty");
    if (nn) {
      GWEN_XMLNode_AddChild(n, nn);
      pmtinf=AH_ImExporter_Sepa_PmtInf_List_First(pl);
      AH_ImExporterSEPA_XmlSetCharValueEscaped(nn, "Nm", pmtinf->localName);
    }
  }
  free(ctrlsum);

  *pList=pl;
  return 0;
}



int AH_ImExporterSEPA_Export(AB_IMEXPORTER *ie,
                             AB_IMEXPORTER_CONTEXT *ctx,
                             GWEN_SYNCIO *sio,
                             GWEN_DB_NODE *params)
{
  AH_IMEXPORTER_SEPA *ieh;
  GWEN_XMLNODE *root;
  GWEN_XMLNODE *documentNode;
  GWEN_XMLNODE *topNode;
  GWEN_XMLNODE *n;
  uint32_t doctype[]= {0, 0, 0};
  const char *s;
  int rv;

  assert(ie);
  ieh=GWEN_INHERIT_GETDATA(AB_IMEXPORTER, AH_IMEXPORTER_SEPA, ie);
  assert(ieh);

  s=GWEN_DB_GetCharValue(params, "type", 0, 0);
  if (s) {
    int i, j;
    const char *p;
    char *tail;

    /* Parse strings of the form xxx.yyy.zz */
    p=tail=(char *)s;
    for (i=0; i<3 && *tail; i++) {
      j=strtol(p, &tail, 10);
      if (!isspace(*p) &&
          ((*tail=='.' && tail-p==3) || (*tail=='\0' && tail-p==2)) &&
          j>0)
        doctype[i]=j;
      else
        break;
      p=tail+1;
    }
    if (i<3)
      /* Parsing the "type" option failed, record it for later reference */
      doctype[0]=0;
  }

  root=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, "root");
  n=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, "?xml");
  if (n) {
    GWEN_XMLNode_AddHeader(root, n);
    GWEN_XMLNode_SetProperty(n, "version", "1.0");
    GWEN_XMLNode_SetProperty(n, "encoding", "UTF-8");
  }

  documentNode=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, "Document");
  s=GWEN_DB_GetCharValue(params, "xmlns", 0, 0);
  if (!s || !*s) {
    DBG_ERROR(AQBANKING_LOGDOMAIN,
              "xmlns not specified in profile \"%s\"",
              GWEN_DB_GetCharValue(params, "name", 0, 0));
    GWEN_XMLNode_free(root);
    return GWEN_ERROR_INVALID;
  }
  GWEN_XMLNode_SetProperty(documentNode, "xmlns", s);
  GWEN_XMLNode_AddChild(root, documentNode);

  switch (doctype[0]) {
  case 1:
    if (doctype[1]>1 || doctype[2]>2)
      s="CstmrCdtTrfInitn";
    else
      s=strstr(s, "pain");
    topNode=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, s);
    GWEN_XMLNode_AddChild(documentNode, topNode);
    rv=AH_ImExporterSEPA_Export_Pain_001(ie, ctx, topNode, doctype, params);
    break;
  case 8:
    if (!(doctype[1]==1 && doctype[2]==1))
      s="CstmrDrctDbtInitn";
    else
      s=strstr(s, "pain");
    topNode=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, s);
    GWEN_XMLNode_AddChild(documentNode, topNode);
    rv=AH_ImExporterSEPA_Export_Pain_008(ie, ctx, topNode, doctype, params);
    break;
  default:
    DBG_ERROR(AQBANKING_LOGDOMAIN, "Unknown SEPA type \"%s\"",
              GWEN_DB_GetCharValue(params, "type", 0, 0));
    GWEN_XMLNode_free(root);
    return GWEN_ERROR_INVALID;
  }

  if (rv==0) {
    GWEN_XML_CONTEXT *xmlctx;

    xmlctx=GWEN_XmlCtxStore_new(root,
                                GWEN_XML_FLAGS_INDENT |
                                GWEN_XML_FLAGS_SIMPLE |
                                GWEN_XML_FLAGS_HANDLE_HEADERS);

    rv=GWEN_XMLNode_WriteToStream(root, xmlctx, sio);
    if (rv) {
      DBG_INFO(AQBANKING_LOGDOMAIN, "here (%d)", rv);
    }
    GWEN_XmlCtx_free(xmlctx);
    GWEN_XMLNode_free(root);
  }

  /* TODO */
  return rv;
}



int AH_ImExporterSEPA_CheckFile(AB_IMEXPORTER *ie, const char *fname)
{
  AH_IMEXPORTER_SEPA *ieh;

  assert(ie);
  ieh=GWEN_INHERIT_GETDATA(AB_IMEXPORTER, AH_IMEXPORTER_SEPA, ie);
  assert(ieh);

#if 0
  return AB_ERROR_INDIFFERENT;
#else
  /* TODO */
  return GWEN_ERROR_NOT_IMPLEMENTED;
#endif
}



int AH_ImExporterSEPA_XmlSetCharValueEscaped(GWEN_XMLNODE *n, const char *varName, const char *value)
{
  if (value && *value) {
    GWEN_BUFFER *dbuf;
    int rv;

    dbuf=GWEN_Buffer_new(0, 256, 0, 1);
    rv=GWEN_Text_EscapeXmlToBuffer(value, dbuf);
    if (rv<0) {
      DBG_INFO(AQBANKING_LOGDOMAIN, "here (%d)", rv);
      GWEN_Buffer_free(dbuf);
      return rv;
    }

    GWEN_XMLNode_SetCharValue(n, varName, GWEN_Buffer_GetStart(dbuf));
    GWEN_Buffer_free(dbuf);
  }
  return 0;
}






#include "sepa_pain_001.c"
#include "sepa_pain_008.c"


