/*
 * Copyright (C) 1995 Ian Jackson <ian@chiark.greenend.org.uk>
 * Copyright (C) 2001 Wichert Akkerman <wakkerma@debian.org>
 * Copyright (C) 2008 Florian Weimer <fw@deneb.enyo.de>
 *
 * This 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 2,
 * or (at your option) any later version.
 *
 * This is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with dpkg; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdlib.h>
#include <string.h>
#include <alloca.h>

#include "vercmp.h"
#include "error.h"

const struct versionrevision *parseversion(const char *string) {
  char *hyphen, *colon, *eepochcolon;
  const char *end, *ptr;
  unsigned long epoch;
  struct versionrevision *rversion = xmalloc(sizeof(*rversion));

  if (!*string) error_exit(7, "version string is empty\n");

  /* trim leading and trailing space */
  while (*string && (*string == ' ' || *string == '\t') ) string++;
  /* string now points to the first non-whitespace char */
  end = string;
  /* find either the end of the string, or a whitespace char */
  while (*end && *end != ' ' && *end != '\t' ) end++;
  /* check for extra chars after trailing space */
  ptr = end;
  while (*ptr && ( *ptr == ' ' || *ptr == '\t' ) ) ptr++;
  if (*ptr) error_exit(7, "version string has embedded space: %s\n", string);

  colon= strchr(string,':');
  if (colon) {
    epoch= strtoul(string,&eepochcolon,10);
    if (colon != eepochcolon) error_exit(7, "epoch in version is not number\n");
    if (!*++colon) error_exit(7, "nothing after colon in version number\n");
    string= colon;
    rversion->epoch= epoch;
  } else {
    rversion->epoch= 0;
  }
  rversion->version= xstrndup(string,end-string);
  hyphen= strrchr(rversion->version,'-');
  if (hyphen) *hyphen++= 0;
  rversion->revision= hyphen ? hyphen : "";

  return rversion;
}


/* Reimplementation of the standard ctype.h is* functions. Since gettext
 * has overloaded the meaning of LC_CTYPE we can't use that to force C
 * locale, so use these cis* functions instead.
 */
static int cisdigit(int c) {
  return (c>='0') && (c<='9');
}

int cisalpha(int c) {
  return ((c>='a') && (c<='z')) || ((c>='A') && (c<='Z'));
}

int
cisspace(int c)
{
        return (c == '\n' || c == '\t' || c == ' ');
}

/* assume ascii; warning: evaluates x multiple times! */
#define order(x) ((x) == '~' ? -1 \
                : cisdigit((x)) ? 0 \
                : !(x) ? 0 \
                : cisalpha((x)) ? (x) \
                : (x) + 256)

static int verrevcmp(const char *val, const char *ref) {
  if (!val) val= "";
  if (!ref) ref= "";

  while (*val || *ref) {
    int first_diff= 0;

    while ( (*val && !cisdigit(*val)) || (*ref && !cisdigit(*ref)) ) {
      int vc= order(*val), rc= order(*ref);
      if (vc != rc) return vc - rc;
      val++; ref++;
    }

    while ( *val == '0' ) val++;
    while ( *ref == '0' ) ref++;
    while (cisdigit(*val) && cisdigit(*ref)) {
      if (!first_diff) first_diff= *val - *ref;
      val++; ref++;
    }
    if (cisdigit(*val)) return 1;
    if (cisdigit(*ref)) return -1;
    if (first_diff) return first_diff;
  }
  return 0;
}

int versioncompare(const struct versionrevision *version,
                   const struct versionrevision *refversion) {
  int r;

  if (version->epoch > refversion->epoch) return 1;
  if (version->epoch < refversion->epoch) return -1;
  r= verrevcmp(version->version,refversion->version);  if (r) return r;
  return verrevcmp(version->revision,refversion->revision);
}

int versionsatisfied3(const struct versionrevision *it,
                      const struct versionrevision *ref,
                      enum depverrel verrel) {
  int r;
  if (verrel == dvr_none) return 1;
  r= versioncompare(it,ref);
  switch (verrel) {
  case dvr_earlierequal:   return r <= 0;
  case dvr_laterequal:     return r >= 0;
  case dvr_earlierstrict:  return r < 0;
  case dvr_laterstrict:    return r > 0;
  case dvr_exact:          return r == 0;
  default:                 error_exit(7, "unknown verrel\n");
  }
  return 0;
}

const char *formatversion(const struct versionrevision *v)
{
  char epoch[32];
  if (v->epoch)
    snprintf(epoch, sizeof(epoch), "%lu:", v->epoch);
  else
    epoch[0] = 0;
  const char *rev;
  if (v->revision && *v->revision)
    rev = xstrcat("-", v->revision);
  else
    rev = "";
  return xstrcat(epoch, xstrcat(v->version, rev));
}

const char *parsedependency(const char *string, struct pkg_version *pv, enum depverrel *verrel)
{
  const char *p = string;
  pv->name = NULL;
  pv->version = NULL;

  for (;;) {
    while (cisspace(*p)) ++p;

    /* Extract package name. */
    {
      const char *namestart = p;
      while (*p && !cisspace(*p) && *p != '(' && *p != ',' && *p != '|') {
        p++;
      }

      unsigned len = p - namestart;
      if (len)
        pv->name = nsymbol(namestart, len);
      else
        pv->name = NULL;
    }

    /* skip whitespace after packagename */
    while (cisspace(*p)) p++;
    if (*p == '(') {                  /* if we have a versioned relation */
      char c1, c2;
      const char *versionstart;
      size_t versionlength;
      char *tmpversion;

      p++; while (cisspace(*p)) p++;
      c1= *p;
      if (c1 == '<' || c1 == '>') {
        c2= *++p;
        *verrel= (c1 == '<') ? dvrf_earlier : dvrf_later;
        if (c2 == '=') {
          *verrel |= (dvrf_orequal | dvrf_builtup);
          p++;
        } else if (c2 == c1) {
          *verrel |= (dvrf_strict | dvrf_builtup);
          p++;
        } else if (c2 == '<' || c2 == '>') {
          *verrel= dvr_none;
        } else {
          *verrel |= (dvrf_orequal | dvrf_builtup);
        }
      } else if (c1 == '=') {
        *verrel= dvr_exact;
        p++;
      } else {
        *verrel= dvr_exact;
      }

      /* skip spaces between the relation and the version */
      while (cisspace(*p)) p++;

      versionstart = p;
      while (*p && *p != ')' && *p != '(') {
        if (cisspace(*p)) break;
        p++;
      }
      versionlength= p - versionstart;
      while (cisspace(*p)) p++;
      if (*p == '(') error_exit(7, "reference to `%.255s': "
                                "version contains `%c'\n", pv->name, ')');
      else if (*p != ')') error_exit(7, "reference to `%.255s': "
                                     "version contains `%c'\n", pv->name, ' ');
      else if (*p == 0) error_exit(7, "`reference to `%.255s': "
                                   "version unterminated\n", pv->name);
      tmpversion = alloca(versionlength + 1);
      memcpy(tmpversion, versionstart, versionlength);
      tmpversion[versionlength] = 0;
      pv->version = parseversion(tmpversion);
      p++; while (cisspace(*p)) p++;
    } else {
      *verrel= dvr_none;
      pv->version = NULL;
    }
    if (!*p || *p == ',') break;
    if (pv->name)
      break;
  }

  return p;
}
