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

/*
 * linkedpkg.c
 *
 * Linked packages are "pseudo" packages that are bound to real packages but
 * contain different information (name/summary/description). They are normally
 * somehow generated from the real packages, either when the repositories are
 * created or automatically from the packages by looking at the provides.
 *
 * We currently support:
 *
 * application:
 *   created from AppStream appdata xml in the repository (which is generated
 *   from files in /usr/share/appdata)
 *
 * product:
 *   created from product data in the repository (which is generated from files
 *   in /etc/products.d). In the future we may switch to using product()
 *   provides of packages.
 *
 * pattern:
 *   created from pattern() provides of packages.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>

#include "pool.h"
#include "repo.h"
#include "solver.h"
#include "evr.h"
#include "bitmap.h"
#include "linkedpkg.h"

#ifdef ENABLE_LINKED_PKGS

static void
find_application_link(Pool *pool, Solvable *s, Id *reqidp, Queue *qr, Id *prvidp, Queue *qp)
{
  Id req = 0;
  Id prv = 0;
  Id p, pp;
  Id pkgname = 0, appdataid = 0;

  /* find appdata requires */
  if (s->requires)
    {
      Id *reqp = s->repo->idarraydata + s->requires;
      while ((req = *reqp++) != 0)            /* go through all requires */
	{
	  if (ISRELDEP(req))
	    continue;
	  if (!strncmp("appdata(", pool_id2str(pool, req), 8))
	    appdataid = req;
	  else
	    pkgname = req;
	}
    }
  req = appdataid ? appdataid : pkgname;
  if (!req)
    return;
  /* find application-appdata provides */
  if (s->provides)
    {
      Id *prvp = s->repo->idarraydata + s->provides;
      const char *reqs = pool_id2str(pool, req);
      const char *prvs;
      while ((prv = *prvp++) != 0)            /* go through all provides */
	{
	  if (ISRELDEP(prv))
	    continue;
	  prvs = pool_id2str(pool, prv);
	  if (strncmp("application-appdata(", prvs, 20))
	    continue;
	  if (appdataid)
	    {
	      if (!strcmp(prvs + 12, reqs))
		break;
	    }
	  else
	    {
	      int reqsl = strlen(reqs);
	      if (!strncmp(prvs + 20, reqs, reqsl) && !strcmp(prvs + 20 + reqsl, ")"))
		break;
	    }
	}
    }
  if (!prv)
    return;	/* huh, no provides found? */
  /* now link em */
  FOR_PROVIDES(p, pp, req)
    if (pool->solvables[p].repo == s->repo)
      if (!pkgname || pool->solvables[p].name == pkgname)
        queue_push(qr, p);
  if (!qr->count && pkgname && appdataid)
    {
      /* huh, no matching package? try without pkgname filter */
      FOR_PROVIDES(p, pp, req)
	if (pool->solvables[p].repo == s->repo)
          queue_push(qr, p);
    }
  if (qp)
    {
      FOR_PROVIDES(p, pp, prv)
	if (pool->solvables[p].repo == s->repo)
	  queue_push(qp, p);
    }
  if (reqidp)
    *reqidp = req;
  if (prvidp)
    *prvidp = prv;
}

static void
find_product_link(Pool *pool, Solvable *s, Id *reqidp, Queue *qr, Id *prvidp, Queue *qp)
{
  Id p, pp, namerelid;
  char *str;
  unsigned int sbt = 0;

  /* search for project requires */
  namerelid = 0;
  if (s->requires)
    {
      Id req, *reqp = s->repo->idarraydata + s->requires;
      const char *nn = pool_id2str(pool, s->name);
      int nnl = strlen(nn);
      while ((req = *reqp++) != 0)            /* go through all requires */
	if (ISRELDEP(req))
	  {
	    const char *rn;
	    Reldep *rd = GETRELDEP(pool, req);
	    if (rd->flags != REL_EQ || rd->evr != s->evr)
	      continue;
	    rn = pool_id2str(pool, rd->name);
	    if (!strncmp(rn, "product(", 8) && !strncmp(rn + 8, nn + 8, nnl - 8) && !strcmp( rn + nnl, ")"))
	      {
		namerelid = req;
		break;
	      }
	  }
    }
  if (!namerelid)
    {
      /* too bad. construct from scratch */
      str = pool_tmpjoin(pool, pool_id2str(pool, s->name), ")", 0);
      str[7] = '(';
      namerelid = pool_rel2id(pool, pool_str2id(pool, str, 1), s->evr, REL_EQ, 1);
    }
  FOR_PROVIDES(p, pp, namerelid)
    {
      Solvable *ps = pool->solvables + p;
      if (ps->repo != s->repo || ps->arch != s->arch)
	continue;
      queue_push(qr, p);
    }
  if (qr->count > 1)
    {
      /* multiple providers. try buildtime filter */
      sbt = solvable_lookup_num(s, SOLVABLE_BUILDTIME, 0);
      if (sbt)
	{
	  unsigned int bt;
	  int i, j;
	  int filterqp = 1;
	  for (i = j = 0; i < qr->count; i++)
	    {
	      bt = solvable_lookup_num(pool->solvables + qr->elements[i], SOLVABLE_BUILDTIME, 0);
	      if (!bt)
		filterqp = 0;	/* can't filter */
	      if (!bt || bt == sbt)
		qr->elements[j++] = qr->elements[i];
	    }
	  if (j)
	    qr->count = j;
	  if (!j || !filterqp)
	    sbt = 0;	/* filter failed */
	}
    }
  if (!qr->count && s->repo == pool->installed)
    {
      /* oh no! Look up reference file */
      Dataiterator di;
      const char *refbasename = solvable_lookup_str(s, PRODUCT_REFERENCEFILE);
      if (refbasename)
	{
	  dataiterator_init(&di, pool, s->repo, 0, SOLVABLE_FILELIST, refbasename, SEARCH_STRING);
	  while (dataiterator_step(&di))
	    {
	      if (di.key->type != REPOKEY_TYPE_DIRSTRARRAY)
		continue;
	      if (strcmp(repodata_dir2str(di.data, di.kv.id, 0), "/etc/products.d") != 0)
		continue;
	      queue_push(qr, di.solvid);
	    }
	  dataiterator_free(&di);
	  if (qp)
	    {
	      dataiterator_init(&di, pool, s->repo, 0, PRODUCT_REFERENCEFILE, refbasename, SEARCH_STRING);
	      while (dataiterator_step(&di))
		queue_push(qp, di.solvid);
	      dataiterator_free(&di);
	    }
	}
    }
  else if (qp)
    {
      /* find qp */
      FOR_PROVIDES(p, pp, s->name)
	{
	  Solvable *ps = pool->solvables + p;
	  if (s->name != ps->name || ps->repo != s->repo || ps->arch != s->arch || s->evr != ps->evr)
	    continue;
	  if (sbt && solvable_lookup_num(ps, SOLVABLE_BUILDTIME, 0) != sbt)
	    continue;
	  queue_push(qp, p);
	}
    }
  if (reqidp)
    *reqidp = namerelid;
  if (prvidp)
    *prvidp = solvable_selfprovidedep(s);
}

static void
find_pattern_link(Pool *pool, Solvable *s, Id *reqidp, Queue *qr, Id *prvidp, Queue *qp)
{
  Id p, pp, *pr, apevr = 0, aprel = 0;

  /* check if autopattern */
  if (!s->provides)
    return;
  for (pr = s->repo->idarraydata + s->provides; (p = *pr++) != 0; )
    if (ISRELDEP(p))
      {
	Reldep *rd = GETRELDEP(pool, p);
	if (rd->flags == REL_EQ && !strcmp(pool_id2str(pool, rd->name), "autopattern()"))
	  {
	    aprel = p;
	    apevr = rd->evr;
	    break;
	  }
      }
  if (!apevr)
    return;
  FOR_PROVIDES(p, pp, apevr)
    {
      Solvable *s2 = pool->solvables + p;
      if (s2->repo == s->repo && s2->name == apevr && s2->evr == s->evr && s2->vendor == s->vendor)
        queue_push(qr, p);
    }
  if (qp)
    {
      FOR_PROVIDES(p, pp, aprel)
	{
	  Solvable *s2 = pool->solvables + p;
	  if (s2->repo == s->repo && s2->evr == s->evr && s2->vendor == s->vendor)
	    queue_push(qp, p);
	}
    }
  if (reqidp)
    *reqidp = apevr;
  if (prvidp)
    *prvidp = aprel;
}

/* the following function is used in solvable_lookup_str_base to do
 * translated lookups on the product/pattern packages
 */
Id
find_autopackage_name(Pool *pool, Solvable *s)
{
  const char *autoprv = 0;
  const char *n = pool_id2str(pool, s->name);
  Id prv, *prvp;

  if (*n == 'p')
    {
      if (!strncmp("pattern:", n, 8))
	autoprv = "autopattern()";
      if (!strncmp("product:", n, 8))
	autoprv = "autoproduct()";
    }
  if (!autoprv)
    return 0;
  for (prvp = s->repo->idarraydata + s->provides; (prv = *prvp++) != 0; )
    if (ISRELDEP(prv))
      {
        Reldep *rd = GETRELDEP(pool, prv);
        if (rd->flags == REL_EQ && !strcmp(pool_id2str(pool, rd->name), autoprv))
          return rd->evr;
      }
  return 0;
}

void
find_package_link(Pool *pool, Solvable *s, Id *reqidp, Queue *qr, Id *prvidp, Queue *qp)
{
  const char *name = pool_id2str(pool, s->name);
  if (name[0] == 'a' && !strncmp("application:", name, 12))
    find_application_link(pool, s, reqidp, qr, prvidp, qp);
  else if (name[0] == 'p' && !strncmp("pattern:", name, 7))
    find_pattern_link(pool, s, reqidp, qr, prvidp, qp);
  else if (name[0] == 'p' && !strncmp("product:", name, 8))
    find_product_link(pool, s, reqidp, qr, prvidp, qp);
}

static int
name_min_max(Pool *pool, Solvable *s, Id *namep, Id *minp, Id *maxp)
{
  Queue q;
  Id qbuf[4];
  Id name, min, max;
  int i;

  queue_init_buffer(&q, qbuf, sizeof(qbuf)/sizeof(*qbuf));
  find_package_link(pool, s, 0, &q, 0, 0);
  if (!q.count)
    {
      queue_free(&q);
      return 0;
    }
  s = pool->solvables + q.elements[0];
  name = s->name;
  min = max = s->evr;
  for (i = 1; i < q.count; i++)
    {
      s = pool->solvables + q.elements[i];
      if (s->name != name)
	{
          queue_free(&q);
	  return 0;
	}
      if (s->evr == min || s->evr == max)
	continue;
      if (pool_evrcmp(pool, min, s->evr, EVRCMP_COMPARE) >= 0)
	min = s->evr;
      else if (min == max || pool_evrcmp(pool, max, s->evr, EVRCMP_COMPARE) <= 0)
	max = s->evr;
    }
  queue_free(&q);
  *namep = name;
  *minp = min;
  *maxp = max;
  return 1;
}

int
pool_link_evrcmp(Pool *pool, Solvable *s1, Solvable *s2)
{
  Id name1, evrmin1, evrmax1;
  Id name2, evrmin2, evrmax2;

  if (s1->name != s2->name)
    return 0;	/* can't compare */
  if (!name_min_max(pool, s1, &name1, &evrmin1, &evrmax1))
    return 0;
  if (!name_min_max(pool, s2, &name2, &evrmin2, &evrmax2))
    return 0;
  /* compare linked names */
  if (name1 != name2)
    return 0;
  if (evrmin1 == evrmin2 && evrmax1 == evrmax2)
    return 0;
  /* now compare evr intervals */
  if (evrmin1 == evrmax1 && evrmin2 == evrmax2)
    return pool_evrcmp(pool, evrmin1, evrmax2, EVRCMP_COMPARE);
  if (evrmin1 != evrmax2 && pool_evrcmp(pool, evrmin1, evrmax2, EVRCMP_COMPARE) > 0)
    return 1;
  if (evrmax1 != evrmin2 && pool_evrcmp(pool, evrmax1, evrmin2, EVRCMP_COMPARE) < 0)
    return -1;
  return 0;
}

void
extend_updatemap_to_buddies(Solver *solv)
{
  Pool *pool = solv->pool;
  Repo *installed = solv->installed;
  Solvable *s;
  int p, ip;

  if (!installed)
    return;
  if (!solv->updatemap.size || !solv->instbuddy)
    return;
  FOR_REPO_SOLVABLES(installed, p, s)
    {
      if (!MAPTST(&solv->updatemap, p - installed->start))
	continue;
      if ((ip = solv->instbuddy[p - installed->start]) <= 1)
	continue;
      if (!has_package_link(pool, s))	/* only look at pseudo -> real relations */
	continue;
      if (ip < installed->start || ip >= installed->end || pool->solvables[ip].repo != installed)
	continue;			/* just in case... */
      MAPSET(&solv->updatemap, ip - installed->start);
    }
}

#endif
