/*
 * repo_comps.c
 *
 * Parses RedHat comps format
 *
 * Copyright (c) 2012, Novell Inc.
 *
 * This program is licensed under the BSD license, read LICENSE.BSD
 * for further information
 */

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

#include "pool.h"
#include "repo.h"
#include "util.h"
#include "solv_xmlparser.h"
#define DISABLE_SPLIT
#include "tools_util.h"
#include "repo_comps.h"

/*
 * TODO:
 *
 * what's the difference between group/category?
 *
 * maybe handle REL_COND in solver recommends handling?
 */

enum state {
  STATE_START,
  STATE_COMPS,
  STATE_GROUP,
  STATE_ID,
  STATE_NAME,
  STATE_DESCRIPTION,
  STATE_DISPLAY_ORDER,
  STATE_DEFAULT,
  STATE_LANGONLY,
  STATE_LANG_ONLY,
  STATE_USERVISIBLE,
  STATE_PACKAGELIST,
  STATE_PACKAGEREQ,
  STATE_CATEGORY,
  STATE_CID,
  STATE_CNAME,
  STATE_CDESCRIPTION,
  STATE_CDISPLAY_ORDER,
  STATE_GROUPLIST,
  STATE_GROUPID,
  STATE_ENVIRONMENT,
  STATE_OPTIONLIST,
  NUMSTATES
};

static struct solv_xmlparser_element stateswitches[] = {
  { STATE_START,       "comps",         STATE_COMPS,         0 },
  { STATE_COMPS,       "group",         STATE_GROUP,         0 },
  { STATE_COMPS,       "category",      STATE_CATEGORY,      0 },
  { STATE_COMPS,       "environment",   STATE_ENVIRONMENT,   0 },
  { STATE_GROUP,       "id",            STATE_ID,            1 },
  { STATE_GROUP,       "name",          STATE_NAME,          1 },
  { STATE_GROUP,       "description",   STATE_DESCRIPTION,   1 },
  { STATE_GROUP,       "uservisible",   STATE_USERVISIBLE,   1 },
  { STATE_GROUP,       "display_order", STATE_DISPLAY_ORDER, 1 },
  { STATE_GROUP,       "default",       STATE_DEFAULT,       1 },
  { STATE_GROUP,       "langonly",      STATE_LANGONLY,      1 },
  { STATE_GROUP,       "lang_only",     STATE_LANG_ONLY,     1 },
  { STATE_GROUP,       "packagelist",   STATE_PACKAGELIST,   0 },
  { STATE_PACKAGELIST, "packagereq",    STATE_PACKAGEREQ,    1 },
  { STATE_CATEGORY,    "id",            STATE_ID,            1 },
  { STATE_CATEGORY,    "name",          STATE_NAME,          1 },
  { STATE_CATEGORY,    "description",   STATE_DESCRIPTION,   1 },
  { STATE_CATEGORY ,   "grouplist",     STATE_GROUPLIST,     0 },
  { STATE_CATEGORY ,   "display_order", STATE_DISPLAY_ORDER, 1 },
  { STATE_GROUPLIST,   "groupid",       STATE_GROUPID,       1 },
  { STATE_ENVIRONMENT, "id",            STATE_ID,            1 },
  { STATE_ENVIRONMENT, "name",          STATE_NAME,          1 },
  { STATE_ENVIRONMENT, "description",   STATE_DESCRIPTION,   1 },
  { STATE_ENVIRONMENT, "grouplist",     STATE_GROUPLIST,     0 },
  { STATE_ENVIRONMENT, "optionlist",    STATE_OPTIONLIST,    0 },
  { STATE_ENVIRONMENT, "display_order", STATE_DISPLAY_ORDER, 1 },
  { STATE_OPTIONLIST,  "groupid",       STATE_GROUPID,       1 },
  { NUMSTATES }
};

struct parsedata {
  Pool *pool;
  Repo *repo;
  Repodata *data;
  const char *filename;
  const char *basename;

  struct solv_xmlparser xmlp;
  struct joindata jd;

  const char *tmplang;
  Id reqtype;
  Id condreq;

  Solvable *solvable;
  const char *kind;
  int isdefault;
  int isvisible;
  Id handle;
};


#define COMPS_DEFAULT_ISVISIBLE 1
#define COMPS_DEFAULT_ISDEFAULT 0

/* Return true if "true", false if "false", default_value otherwise */
static int
parse_boolean(char *content, int default_value)
{
  if (!strcmp(content, "true"))
    return 1;
  if (!strcmp(content, "false"))
    return 0;
  return default_value;
}


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;

  switch(state)
    {
    case STATE_GROUP:
    case STATE_CATEGORY:
    case STATE_ENVIRONMENT:
      s = pd->solvable = pool_id2solvable(pool, repo_add_solvable(pd->repo));
      pd->handle = s - pool->solvables;
      if (state == STATE_GROUP)
        pd->kind = "group";
      else if (state == STATE_CATEGORY)
        pd->kind = "category";
      else
        pd->kind = "environment";
      pd->isvisible = COMPS_DEFAULT_ISVISIBLE;
      pd->isdefault = COMPS_DEFAULT_ISDEFAULT;
      break;

    case STATE_NAME:
    case STATE_CNAME:
    case STATE_DESCRIPTION:
    case STATE_CDESCRIPTION:
      pd->tmplang = join_dup(&pd->jd, solv_xmlparser_find_attr("xml:lang", atts));
      break;

    case STATE_PACKAGEREQ:
      {
	const char *type = solv_xmlparser_find_attr("type", atts);
	pd->condreq = 0;
	pd->reqtype = SOLVABLE_RECOMMENDS;
	if (type && !strcmp(type, "conditional"))
	  {
	    const char *requires = solv_xmlparser_find_attr("requires", atts);
	    if (requires && *requires)
	      pd->condreq = pool_str2id(pool, requires, 1);
	  }
	else if (type && !strcmp(type, "mandatory"))
	  pd->reqtype = SOLVABLE_REQUIRES;
	else if (type && !strcmp(type, "optional"))
	  pd->reqtype = SOLVABLE_SUGGESTS;
	break;
      }

    case STATE_GROUPLIST:
      {
	pd->reqtype = SOLVABLE_REQUIRES;
	break;
      }

    case STATE_OPTIONLIST:
      {
	pd->reqtype = SOLVABLE_SUGGESTS;
	break;
      }

    default:
      break;
    }
}


static void
endElement(struct solv_xmlparser *xmlp, int state, char *content)
{
  struct parsedata *pd = xmlp->userdata;
  Solvable *s = pd->solvable;
  Id id;

  switch (state)
    {
    case STATE_GROUP:
    case STATE_CATEGORY:
    case STATE_ENVIRONMENT:
      if (!s->arch)
	s->arch = ARCH_NOARCH;
      if (!s->evr)
	s->evr = ID_EMPTY;
      if (s->name && s->arch != ARCH_SRC && s->arch != ARCH_NOSRC)
	s->provides = repo_addid_dep(pd->repo, s->provides, pool_rel2id(pd->pool, s->name, s->evr, REL_EQ, 1), 0);
      if (pd->isvisible)
        repodata_set_void(pd->data, pd->handle, SOLVABLE_ISVISIBLE);
      if (pd->isdefault)
        repodata_set_void(pd->data, pd->handle, SOLVABLE_ISDEFAULT);
      pd->solvable = 0;
      break;

    case STATE_ID:
      s->name = pool_str2id(pd->pool, join2(&pd->jd, pd->kind, ":", content), 1);
      break;

    case STATE_NAME:
      repodata_set_str(pd->data, pd->handle, pool_id2langid(pd->pool, SOLVABLE_SUMMARY, pd->tmplang, 1), content);
      break;

    case STATE_DESCRIPTION:
      repodata_set_str(pd->data, pd->handle, pool_id2langid(pd->pool, SOLVABLE_DESCRIPTION, pd->tmplang, 1), content);
      break;

    case STATE_PACKAGEREQ:
      id = pool_str2id(pd->pool, content, 1);
      if (pd->condreq)
	id = pool_rel2id(pd->pool, id, pd->condreq, REL_COND, 1);
      repo_add_idarray(pd->repo, pd->handle, pd->reqtype, id);
      break;

    case STATE_GROUPID:
      id = pool_str2id(pd->pool, join2(&pd->jd, "group", ":", content), 1);
      repo_add_idarray(pd->repo, pd->handle, pd->reqtype, id);
      break;

    case STATE_USERVISIBLE:
      pd->isvisible = parse_boolean(content, COMPS_DEFAULT_ISVISIBLE);
      break;

    case STATE_DEFAULT:
      pd->isdefault = parse_boolean(content, COMPS_DEFAULT_ISDEFAULT);
      break;

    case STATE_LANG_ONLY:
      repodata_set_str(pd->data, pd->handle, SOLVABLE_LANGONLY, content);
      break;

    case STATE_LANGONLY:
      repodata_set_str(pd->data, pd->handle, SOLVABLE_LANGONLY, content);
      break;

    case STATE_DISPLAY_ORDER:
      repodata_set_str(pd->data, pd->handle, SOLVABLE_ORDER, content);
      break;

    default:
      break;
    }
}

int
repo_add_comps(Repo *repo, FILE *fp, int flags)
{
  Repodata *data;
  struct parsedata pd;

  data = repo_add_repodata(repo, flags);

  memset(&pd, 0, sizeof(pd));
  pd.repo = repo;
  pd.pool = repo->pool;
  pd.data = data;
  solv_xmlparser_init(&pd.xmlp, stateswitches, &pd, startElement, endElement);
  if (solv_xmlparser_parse(&pd.xmlp, fp) != SOLV_XMLPARSER_OK)
    pool_debug(pd.pool, SOLV_ERROR, "repo_comps: %s at line %u:%u\n", pd.xmlp.errstr, pd.xmlp.line, pd.xmlp.column);
  solv_xmlparser_free(&pd.xmlp);
  join_freemem(&pd.jd);

  if (!(flags & REPO_NO_INTERNALIZE))
    repodata_internalize(data);
  return 0;
}

/* EOF */
