/*
 * Copyright (c) 2007, Novell Inc.
 *
 * This program is licensed under the BSD license, read LICENSE.BSD
 * for further information
 */

/*
 * repo_helix.c
 *
 * Parse 'helix' XML representation
 * and create 'repo'
 *
 * A bit of history: "Helix Code" was the name of the company that
 * wrote Red Carpet. The company was later renamed to Ximian.
 * The Red Carpet solver was merged into the ZYPP project, the
 * library used both by ZENworks and YaST for package management.
 * Red Carpet came with solver testcases in its own repository
 * format, the 'helix' format.
 *
 */

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "queue.h"
#include "solv_xmlparser.h"
#include "repo_helix.h"
#include "evr.h"


/* XML parser states */

enum state {
  STATE_START,
  STATE_CHANNEL,
  STATE_SUBCHANNEL,
  STATE_PACKAGE,
  STATE_NAME,
  STATE_VENDOR,
  STATE_BUILDTIME,
  STATE_HISTORY,
  STATE_UPDATE,
  STATE_EPOCH,
  STATE_VERSION,
  STATE_RELEASE,
  STATE_ARCH,
  STATE_PROVIDES,
  STATE_PROVIDESENTRY,
  STATE_REQUIRES,
  STATE_REQUIRESENTRY,
  STATE_PREREQUIRES,
  STATE_PREREQUIRESENTRY,
  STATE_OBSOLETES,
  STATE_OBSOLETESENTRY,
  STATE_CONFLICTS,
  STATE_CONFLICTSENTRY,
  STATE_RECOMMENDS,
  STATE_RECOMMENDSENTRY,
  STATE_SUPPLEMENTS,
  STATE_SUPPLEMENTSENTRY,
  STATE_SUGGESTS,
  STATE_SUGGESTSENTRY,
  STATE_ENHANCES,
  STATE_ENHANCESENTRY,
  STATE_FRESHENS,
  STATE_FRESHENSENTRY,

  STATE_SELECTTION,
  STATE_PATTERN,
  STATE_ATOM,
  STATE_PATCH,
  STATE_PRODUCT,

  NUMSTATES
};

static struct solv_xmlparser_element stateswitches[] = {
  { STATE_START,       "channel",         STATE_CHANNEL, 0 },
  { STATE_CHANNEL,     "subchannel",      STATE_SUBCHANNEL, 0 },
  { STATE_SUBCHANNEL,  "package",         STATE_PACKAGE, 0 },
  { STATE_SUBCHANNEL,  "srcpackage",      STATE_PACKAGE, 0 },
  { STATE_SUBCHANNEL,  "selection",       STATE_PACKAGE, 0 },
  { STATE_SUBCHANNEL,  "pattern",         STATE_PACKAGE, 0 },
  { STATE_SUBCHANNEL,  "atom",            STATE_PACKAGE, 0 },
  { STATE_SUBCHANNEL,  "patch",           STATE_PACKAGE, 0 },
  { STATE_SUBCHANNEL,  "product",         STATE_PACKAGE, 0 },
  { STATE_SUBCHANNEL,  "application",     STATE_PACKAGE, 0 },
  { STATE_PACKAGE,     "name",            STATE_NAME, 1 },
  { STATE_PACKAGE,     "vendor",          STATE_VENDOR, 1 },
  { STATE_PACKAGE,     "buildtime",       STATE_BUILDTIME, 1 },
  { STATE_PACKAGE,     "epoch",           STATE_EPOCH, 1 },
  { STATE_PACKAGE,     "version",         STATE_VERSION, 1 },
  { STATE_PACKAGE,     "release",         STATE_RELEASE, 1 },
  { STATE_PACKAGE,     "arch",            STATE_ARCH, 1 },
  { STATE_PACKAGE,     "history",         STATE_HISTORY, 0 },
  { STATE_PACKAGE,     "provides",        STATE_PROVIDES, 0 },
  { STATE_PACKAGE,     "requires",        STATE_REQUIRES, 0 },
  { STATE_PACKAGE,     "prerequires",     STATE_PREREQUIRES, 0 },
  { STATE_PACKAGE,     "obsoletes",       STATE_OBSOLETES , 0 },
  { STATE_PACKAGE,     "conflicts",       STATE_CONFLICTS , 0 },
  { STATE_PACKAGE,     "recommends" ,     STATE_RECOMMENDS , 0 },
  { STATE_PACKAGE,     "supplements",     STATE_SUPPLEMENTS, 0 },
  { STATE_PACKAGE,     "suggests",        STATE_SUGGESTS, 0 },
  { STATE_PACKAGE,     "enhances",        STATE_ENHANCES, 0 },
  { STATE_PACKAGE,     "freshens",        STATE_FRESHENS, 0 },
  { STATE_PACKAGE,     "deps",            STATE_PACKAGE, 0 },	/* ignore deps element */

  { STATE_HISTORY,     "update",          STATE_UPDATE, 0 },
  { STATE_UPDATE,      "epoch",           STATE_EPOCH, 1 },
  { STATE_UPDATE,      "version",         STATE_VERSION, 1 },
  { STATE_UPDATE,      "release",         STATE_RELEASE, 1 },
  { STATE_UPDATE,      "arch",            STATE_ARCH, 1 },

  { STATE_PROVIDES,    "dep",             STATE_PROVIDESENTRY, 0 },
  { STATE_REQUIRES,    "dep",             STATE_REQUIRESENTRY, 0 },
  { STATE_PREREQUIRES, "dep",             STATE_PREREQUIRESENTRY, 0 },
  { STATE_OBSOLETES,   "dep",             STATE_OBSOLETESENTRY, 0 },
  { STATE_CONFLICTS,   "dep",             STATE_CONFLICTSENTRY, 0 },
  { STATE_RECOMMENDS,  "dep",             STATE_RECOMMENDSENTRY, 0 },
  { STATE_SUPPLEMENTS, "dep",             STATE_SUPPLEMENTSENTRY, 0 },
  { STATE_SUGGESTS,    "dep",             STATE_SUGGESTSENTRY, 0 },
  { STATE_ENHANCES,    "dep",             STATE_ENHANCESENTRY, 0 },
  { STATE_FRESHENS,    "dep",             STATE_FRESHENSENTRY, 0 },
  { NUMSTATES }

};

/*
 * parser data
 */

struct parsedata {
  int ret;
  /* repo data */
  Pool *pool;		/* current pool */
  Repo *repo;		/* current repo */
  Repodata *data;       /* current repo data */
  Solvable *solvable;	/* current solvable */
  Offset freshens;	/* current freshens vector */

  /* package data */
  int  srcpackage;	/* is srcpackage element */
  int  epoch;		/* epoch (as offset into evrspace) */
  int  version;		/* version (as offset into evrspace) */
  int  release;		/* release (as offset into evrspace) */
  char *evrspace;	/* buffer for evr */
  int  aevrspace;	/* actual buffer space */
  int  levrspace;	/* actual evr length */
  char *kind;

  struct solv_xmlparser xmlp;
};


/*------------------------------------------------------------------*/
/* E:V-R handling */

/* create Id from epoch:version-release */

static Id
evr2id(Pool *pool, struct parsedata *pd, const char *e, const char *v, const char *r)
{
  char *c, *space;
  int l;

  /* treat explitcit 0 as NULL */
  if (e && (!*e || !strcmp(e, "0")))
    e = 0;

  if (v && !e)
    {
      const char *v2;
      /* scan version for ":" */
      for (v2 = v; *v2 >= '0' && *v2 <= '9'; v2++)	/* skip leading digits */
        ;
      /* if version contains ":", set epoch to "0" */
      if (v2 > v && *v2 == ':')
	e = "0";
    }

  /* compute length of Id string */
  l = 1;  /* for the \0 */
  if (e)
    l += strlen(e) + 1;  /* e: */
  if (v)
    l += strlen(v);      /* v */
  if (r)
    l += strlen(r) + 1;  /* -r */

  /* get content space */
  c = space = solv_xmlparser_contentspace(&pd->xmlp, l);

  /* copy e-v-r */
  if (e)
    {
      strcpy(c, e);
      c += strlen(c);
      *c++ = ':';
    }
  if (v)
    {
      strcpy(c, v);
      c += strlen(c);
    }
  if (r)
    {
      *c++ = '-';
      strcpy(c, r);
      c += strlen(c);
    }
  *c = 0;
  /* if nothing inserted, return Id 0 */
  if (!*space)
    return 0;
#if 0
  fprintf(stderr, "evr: %s\n", space);
#endif
  /* intern and create */
  return pool_str2id(pool, space, 1);
}


/* create e:v-r from attributes
 * atts is array of name,value pairs, NULL at end
 *   even index into atts is name
 *   odd index is value
 */
static Id
evr_atts2id(Pool *pool, struct parsedata *pd, const char **atts)
{
  const char *e, *v, *r;
  e = v = r = 0;
  for (; *atts; atts += 2)
    {
      if (!strcmp(*atts, "epoch"))
	e = atts[1];
      else if (!strcmp(*atts, "version"))
	v = atts[1];
      else if (!strcmp(*atts, "release"))
	r = atts[1];
    }
  return evr2id(pool, pd, e, v, r);
}

/*------------------------------------------------------------------*/
/* rel operator handling */

struct flagtab {
  char *from;
  int to;
};

static struct flagtab flagtab[] = {
  { ">",  REL_GT },
  { "=",  REL_EQ },
  { ">=", REL_GT|REL_EQ },
  { "<",  REL_LT },
  { "!=", REL_GT|REL_LT },
  { "<=", REL_LT|REL_EQ },
  { "(any)", REL_LT|REL_EQ|REL_GT },
  { "==", REL_EQ },
  { "gt", REL_GT },
  { "eq", REL_EQ },
  { "ge", REL_GT|REL_EQ },
  { "lt", REL_LT },
  { "ne", REL_GT|REL_LT },
  { "le", REL_LT|REL_EQ },
  { "gte", REL_GT|REL_EQ },
  { "lte", REL_LT|REL_EQ },
  { "GT", REL_GT },
  { "EQ", REL_EQ },
  { "GE", REL_GT|REL_EQ },
  { "LT", REL_LT },
  { "NE", REL_GT|REL_LT },
  { "LE", REL_LT|REL_EQ }
};

/*
 * process new dependency from parser
 *  olddeps = already collected deps, this defines the 'kind' of dep
 *  atts = array of name,value attributes of dep
 *  isreq == 1 if its a requires
 */

static unsigned int
adddep(Pool *pool, struct parsedata *pd, unsigned int olddeps, const char **atts, Id marker)
{
  Id id, name;
  const char *n, *f, *k;
  const char **a;

  n = f = k = NULL;

  /* loop over name,value pairs */
  for (a = atts; *a; a += 2)
    {
      if (!strcmp(*a, "name"))
	n = a[1];
      if (!strcmp(*a, "kind"))
	k = a[1];
      else if (!strcmp(*a, "op"))
	f = a[1];
      else if (marker && !strcmp(*a, "pre") && a[1][0] == '1')
        marker = SOLVABLE_PREREQMARKER;
    }
  if (!n)			       /* quit if no name found */
    return olddeps;

  /* kind, name */
  if (k && !strcmp(k, "package"))
    k = NULL;			       /* package is default */

  if (k)			       /* if kind!=package, intern <kind>:<name> */
    {
      int l = strlen(k) + 1 + strlen(n) + 1;
      char *space = solv_xmlparser_contentspace(&pd->xmlp, l);
      sprintf(space, "%s:%s", k, n);
      name = pool_str2id(pool, space, 1);
    }
  else
    {
      name = pool_str2id(pool, n, 1);       /* package: just intern <name> */
    }

  if (f)			       /* operator ? */
    {
      /* intern e:v-r */
      Id evr = evr_atts2id(pool, pd, atts);
      /* parser operator to flags */
      int flags;
      for (flags = 0; flags < sizeof(flagtab)/sizeof(*flagtab); flags++)
	if (!strcmp(f, flagtab[flags].from))
	  {
	    flags = flagtab[flags].to;
	    break;
	  }
      if (flags > 7)
	flags = 0;
      /* intern rel */
      id = pool_rel2id(pool, name, evr, flags, 1);
    }
  else
    id = name;			       /* no operator */

  /* add new dependency to repo */
  return repo_addid_dep(pd->repo, olddeps, id, marker);
}


/*----------------------------------------------------------------*/

static void
startElement(struct solv_xmlparser *xmlp, int state, const char *name, const char **atts)
{
  struct parsedata *pd = xmlp->userdata;
  Pool *pool = pd->pool;
  Solvable *s = pd->solvable;

  switch (state)
    {

    case STATE_NAME:
      if (pd->kind)		       /* if kind is set (non package) */
        {
          strcpy(xmlp->content, pd->kind);
          xmlp->lcontent = strlen(xmlp->content);
	  xmlp->content[xmlp->lcontent++] = ':';   /* prefix name with '<kind>:' */
	  xmlp->content[xmlp->lcontent] = 0;
	}
      break;

    case STATE_PACKAGE:		       /* solvable name */
      pd->solvable = pool_id2solvable(pool, repo_add_solvable(pd->repo));
      pd->srcpackage = 0;
      pd->kind = NULL;		       /* default is (src)package */
      if (!strcmp(name, "selection"))
        pd->kind = "selection";
      else if (!strcmp(name, "pattern"))
        pd->kind = "pattern";
      else if (!strcmp(name, "atom"))
        pd->kind = "atom";
      else if (!strcmp(name, "product"))
        pd->kind = "product";
      else if (!strcmp(name, "patch"))
        pd->kind = "patch";
      else if (!strcmp(name, "application"))
        pd->kind = "application";
      else if (!strcmp(name, "srcpackage"))
	pd->srcpackage = 1;
      pd->levrspace = 1;
      pd->epoch = 0;
      pd->version = 0;
      pd->release = 0;
      pd->freshens = 0;
#if 0
      fprintf(stderr, "package #%d\n", s - pool->solvables);
#endif
      break;

    case STATE_UPDATE:
      pd->levrspace = 1;
      pd->epoch = 0;
      pd->version = 0;
      pd->release = 0;
      break;

    case STATE_PROVIDES:	       /* start of provides */
      s->provides = 0;
      break;
    case STATE_PROVIDESENTRY:	       /* entry within provides */
      s->provides = adddep(pool, pd, s->provides, atts, 0);
      break;
    case STATE_REQUIRESENTRY:
      s->requires = adddep(pool, pd, s->requires, atts, -SOLVABLE_PREREQMARKER);
      break;
    case STATE_PREREQUIRESENTRY:
      s->requires = adddep(pool, pd, s->requires, atts, SOLVABLE_PREREQMARKER);
      break;
    case STATE_OBSOLETES:
      s->obsoletes = 0;
      break;
    case STATE_OBSOLETESENTRY:
      s->obsoletes = adddep(pool, pd, s->obsoletes, atts, 0);
      break;
    case STATE_CONFLICTS:
      s->conflicts = 0;
      break;
    case STATE_CONFLICTSENTRY:
      s->conflicts = adddep(pool, pd, s->conflicts, atts, 0);
      break;
    case STATE_RECOMMENDS:
      s->recommends = 0;
      break;
    case STATE_RECOMMENDSENTRY:
      s->recommends = adddep(pool, pd, s->recommends, atts, 0);
      break;
    case STATE_SUPPLEMENTS:
      s->supplements= 0;
      break;
    case STATE_SUPPLEMENTSENTRY:
      s->supplements = adddep(pool, pd, s->supplements, atts, 0);
      break;
    case STATE_SUGGESTS:
      s->suggests = 0;
      break;
    case STATE_SUGGESTSENTRY:
      s->suggests = adddep(pool, pd, s->suggests, atts, 0);
      break;
    case STATE_ENHANCES:
      s->enhances = 0;
      break;
    case STATE_ENHANCESENTRY:
      s->enhances = adddep(pool, pd, s->enhances, atts, 0);
      break;
    case STATE_FRESHENS:
      pd->freshens = 0;
      break;
    case STATE_FRESHENSENTRY:
      pd->freshens = adddep(pool, pd, pd->freshens, atts, 0);
      break;
    default:
      break;
    }
}

static const char *
findKernelFlavor(struct parsedata *pd, Solvable *s)
{
  Pool *pool = pd->pool;
  Id pid, *pidp;

  if (s->provides)
    {
      pidp = pd->repo->idarraydata + s->provides;
      while ((pid = *pidp++) != 0)
	{
	  Reldep *prd;
	  const char *depname;

	  if (!ISRELDEP(pid))
	    continue;               /* wrong provides name */
	  prd = GETRELDEP(pool, pid);
	  depname = pool_id2str(pool, prd->name);
	  if (!strncmp(depname, "kernel-", 7))
	    return depname + 7;
	}
    }

  if (s->requires)
    {
      pidp = pd->repo->idarraydata + s->requires;
      while ((pid = *pidp++) != 0)
	{
	  const char *depname;

	  if (!ISRELDEP(pid))
	    {
	      depname = pool_id2str(pool, pid);
	    }
	  else
	    {
	      Reldep *prd = GETRELDEP(pool, pid);
	      depname = pool_id2str(pool, prd->name);
	    }
	  if (!strncmp(depname, "kernel-", 7))
	    return depname + 7;
	}
    }

  return 0;
}


static void
endElement(struct solv_xmlparser *xmlp, int state, char *content)
{
  struct parsedata *pd = xmlp->userdata;
  Pool *pool = pd->pool;
  Solvable *s = pd->solvable;
  Id evr;
  unsigned int t = 0;
  const char *flavor;

  switch (state)
    {

    case STATE_PACKAGE:		       /* package complete */
      if (pd->srcpackage && s->arch != ARCH_SRC && s->arch != ARCH_NOSRC)
	s->arch = ARCH_SRC;
      if (!s->arch)                    /* default to "noarch" */
	s->arch = ARCH_NOARCH;

      if (!s->evr && pd->version)      /* set solvable evr */
        s->evr = evr2id(pool, pd,
                        pd->epoch   ? pd->evrspace + pd->epoch   : 0,
                        pd->version ? pd->evrspace + pd->version : 0,
                        pd->release ? pd->evrspace + pd->release : 0);
      /* ensure self-provides */
      if (s->name && s->arch != ARCH_SRC && s->arch != ARCH_NOSRC)
        s->provides = repo_addid_dep(pd->repo, s->provides, pool_rel2id(pool, s->name, s->evr, REL_EQ, 1), 0);
      repo_rewrite_suse_deps(s, pd->freshens);
      pd->freshens = 0;

      /* see bugzilla bnc#190163 */
      flavor = findKernelFlavor(pd, s);
      if (flavor)
	{
	  char *cflavor = solv_strdup(flavor);	/* make pointer safe */

	  Id npr;
	  Id pid;

	  /* this is either a kernel package or a kmp */
	  if (s->provides)
	    {
	      Offset prov = s->provides;
	      npr = 0;
	      while ((pid = pd->repo->idarraydata[prov++]) != 0)
		{
		  const char *depname = 0;
		  Reldep *prd = 0;

		  if (ISRELDEP(pid))
		    {
		      prd = GETRELDEP(pool, pid);
		      depname = pool_id2str(pool, prd->name);
		    }
		  else
		    {
		      depname = pool_id2str(pool, pid);
		    }


		  if (!strncmp(depname, "kernel(", 7) && !strchr(depname, ':'))
		    {
		      char newdep[100];
		      snprintf(newdep, sizeof(newdep), "kernel(%s:%s", cflavor, depname + 7);
		      pid = pool_str2id(pool, newdep, 1);
		      if (prd)
			pid = pool_rel2id(pool, pid, prd->evr, prd->flags, 1);
		    }

		  npr = repo_addid_dep(pd->repo, npr, pid, 0);
		}
	      s->provides = npr;
	    }
#if 1

	  if (s->requires)
	    {
	      Offset reqs = s->requires;
	      npr = 0;
	      while ((pid = pd->repo->idarraydata[reqs++]) != 0)
		{
		  const char *depname = 0;
		  Reldep *prd = 0;

		  if (ISRELDEP(pid))
		    {
		      prd = GETRELDEP(pool, pid);
		      depname = pool_id2str(pool, prd->name);
		    }
		  else
		    {
		      depname = pool_id2str(pool, pid);
		    }

		  if (!strncmp(depname, "kernel(", 7) && !strchr(depname, ':'))
		    {
		      char newdep[100];
		      snprintf(newdep, sizeof(newdep), "kernel(%s:%s", cflavor, depname + 7);
		      pid = pool_str2id(pool, newdep, 1);
		      if (prd)
			pid = pool_rel2id(pool, pid, prd->evr, prd->flags, 1);
		    }
		  npr = repo_addid_dep(pd->repo, npr, pid, 0);
		}
	      s->requires = npr;
	    }
#endif
	  free(cflavor);
	}
      break;
    case STATE_NAME:
      s->name = pool_str2id(pool, content, 1);
      break;
    case STATE_VENDOR:
      s->vendor = pool_str2id(pool, content, 1);
      break;
    case STATE_BUILDTIME:
      t = atoi(content);
      if (t)
	repodata_set_num(pd->data, s - pool->solvables, SOLVABLE_BUILDTIME, t);
      break;	
    case STATE_UPDATE:		       /* new version, keeping all other metadata */
      evr = evr2id(pool, pd,
                   pd->epoch   ? pd->evrspace + pd->epoch   : 0,
                   pd->version ? pd->evrspace + pd->version : 0,
                   pd->release ? pd->evrspace + pd->release : 0);
      pd->levrspace = 1;
      pd->epoch = 0;
      pd->version = 0;
      pd->release = 0;
      /* use highest evr */
      if (!s->evr || pool_evrcmp(pool, s->evr, evr, EVRCMP_COMPARE) <= 0)
	s->evr = evr;
      break;
    case STATE_EPOCH:
    case STATE_VERSION:
    case STATE_RELEASE:
      /* ensure buffer space */
      if (xmlp->lcontent + 1 + pd->levrspace > pd->aevrspace)
	{
	  pd->aevrspace = xmlp->lcontent + 1 + pd->levrspace + 256;
	  pd->evrspace = (char *)realloc(pd->evrspace, pd->aevrspace);
	}
      memcpy(pd->evrspace + pd->levrspace, xmlp->content, xmlp->lcontent + 1);
      if (state == STATE_EPOCH)
	pd->epoch = pd->levrspace;
      else if (state == STATE_VERSION)
	pd->version = pd->levrspace;
      else
	pd->release = pd->levrspace;
      pd->levrspace += xmlp->lcontent + 1;
      break;
    case STATE_ARCH:
      s->arch = pool_str2id(pool, content, 1);
      break;
    default:
      break;
    }
}

/*-------------------------------------------------------------------*/

/*
 * read 'helix' type xml from fp
 * add packages to pool/repo
 *
 */

int
repo_add_helix(Repo *repo, FILE *fp, int flags)
{
  Pool *pool = repo->pool;
  struct parsedata pd;
  Repodata *data;
  unsigned int now;

  now = solv_timems(0);
  data = repo_add_repodata(repo, flags);

  /* prepare parsedata */
  memset(&pd, 0, sizeof(pd));
  pd.pool = pool;
  pd.repo = repo;
  pd.data = data;

  pd.evrspace = (char *)solv_malloc(256);
  pd.aevrspace = 256;
  pd.levrspace = 1;

  solv_xmlparser_init(&pd.xmlp, stateswitches, &pd, startElement, endElement);
  if (solv_xmlparser_parse(&pd.xmlp, fp) != SOLV_XMLPARSER_OK)
    pd.ret = pool_error(pd.pool, -1, "repo_helix: %s at line %u", pd.xmlp.errstr, pd.xmlp.line);
  solv_xmlparser_free(&pd.xmlp);

  solv_free(pd.evrspace);

  if (!(flags & REPO_NO_INTERNALIZE))
    repodata_internalize(data);
  POOL_DEBUG(SOLV_DEBUG_STATS, "repo_add_helix took %d ms\n", solv_timems(now));
  POOL_DEBUG(SOLV_DEBUG_STATS, "repo size: %d solvables\n", repo->nsolvables);
  POOL_DEBUG(SOLV_DEBUG_STATS, "repo memory used: %d K incore, %d K idarray\n", repodata_memused(data)/1024, repo->idarraysize / (int)(1024/sizeof(Id)));
  return pd.ret;
}
