/*
 * grn_news.c: manipulating .newsrc file's "sets" (or sequences)
 *
 * $Id: grn_news.c,v 1.23 2000/04/28 12:32:31 sc Exp $
 */
/* Copyright (C) 1999-2000  Sergey Chernikov (sc@ivvs.ul.ru)
 *
 * Authors: Sergey Chernikov <sc@ivvs.ul.ru>
 *
 * This program 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 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
 */

#include "grn_consts.h"
#include <stdio.h>
#include <unistd.h>
#include <gnome.h>
#include "nntp.h"
#include "grn_news.h"
#include "grn_vars.h"
#include "grn_types.h"
#include "grn_util.h"
#include "grn_config.h"


GHashTable *newsrc_ht = NULL, *ng_desc_ht = NULL;


static void cp_ng_hash(gpointer key, gpointer data, GHashTable *ht)
{
  gchar *new_key = g_strdup(key);
  gchar *new_data = g_strdup(data);
  g_hash_table_insert(ht, new_key, new_data);
}
static void write_ng_hash(gpointer key, gchar *value, FILE *f)
{
  fprintf(f, "%s: %s\n", (gchar *) key, (value != NULL) ? value : "");
}

void write_newsrc(const gchar *dstname, const gchar *srcname, GHashTable *ng_ht)
{
  gchar *backup_newsrc = grn_subst_tilde(grn_prefs.paths[1]);
  FILE *fsrc, *fdst;
  GHashTable *ht;
  gchar *sn, *dn;
  g_return_if_fail(dstname != NULL);
  if (! ng_ht)  return;
  
  ht = g_hash_table_new(g_str_hash, g_str_equal);
  g_hash_table_foreach(ng_ht, (GHFunc) cp_ng_hash, ht);

  sn = grn_subst_tilde(srcname);  dn = grn_subst_tilde(dstname);
  if (str_check(sn))  rename(sn, backup_newsrc);
  fdst = fopen(dn, "wt");
  if (fdst)
  {
    fsrc = fopen(backup_newsrc, "rt");
    if (fsrc)
    {
      gchar *s_tmp = (gchar *) g_malloc(64*1024 + 1);

      while (! feof(fsrc))
      {
        s_tmp[0] = '\0';
        fgets(s_tmp, 64*1024, fsrc);
        if (str_check(s_tmp))
        {
          t_msgheader *mh = parse_hdr(s_tmp);
	  if (mh)
	  {
            if (str_check(mh->name))
	    {
	      gchar *key, *value;
	      if (g_hash_table_lookup_extended(ht, mh->name, (gpointer *) &key,
	                                       (gpointer *) &value))
	      {
		write_ng_hash(mh->name, value, fdst);
	        g_hash_table_remove(ht, mh->name);
	        str_free(&key);  str_free(&value);
	      }
	    }
	    str_free(&(mh->name));  str_free(&(mh->value));
	    g_free(mh);
	  }
        }
      }
      str_free(&s_tmp);
      fclose(fsrc);
    }
    g_hash_table_foreach(ht, (GHFunc) write_ng_hash, fdst);
    fclose(fdst);
  }
  str_free(&backup_newsrc);  str_free(&sn);  str_free(&dn);
  free_newsrc(ht);
}

GHashTable *read_newsrc(const gchar *fname)
{
  GHashTable *out_ht;
  gchar *s_tmp;
  FILE *f;
  gchar *fn;

  out_ht = g_hash_table_new(g_str_hash, g_str_equal);

  fn = grn_subst_tilde(fname);
  if (! g_file_exists(fn))
  {
#ifdef GRN_DEBUG
    printf("read_newsrc@grn_news.c: %s not found\n", fn);
#endif
    return out_ht;
  }
  
  if (!(f=fopen(fn, "rt")))
  {
#ifdef GRN_DEBUG
    printf("read_newsrc@grn_news.c: Can't open %s for reading\n", newsrc_fn);
#endif
    return out_ht;
  }
  
  s_tmp = (gchar *) g_malloc(64*1024);
  while (! feof(f))
  {
    s_tmp[0] = '\0';
    fgets(s_tmp, 64*1024, f);
    if (strlen(s_tmp) > 0)
    {
      t_msgheader *mh = parse_hdr(s_tmp);
      if (mh)
      {
        if (str_check(mh->name))
        {
	  if (! mh->value)  mh->value = g_strdup("");
          g_hash_table_insert(out_ht, mh->name, mh->value);
        }
        else {
          str_free(&(mh->name));  str_free(&(mh->value));
        }
        g_free(mh);
      }
    }
  }
  fclose(f);
  str_free(&s_tmp);  str_free(&fn);

  return out_ht;
}

static void free_ng_hash(gchar *key, gchar *value, gpointer data)
{
  str_free(&key);  str_free(&value);
}
void free_newsrc(GHashTable *ng_ht)
{
  g_return_if_fail(ng_ht != NULL);
  g_hash_table_foreach(ng_ht, (GHFunc) free_ng_hash, NULL);
  g_hash_table_destroy(ng_ht);
}

gboolean change_newsrc(GHashTable *ng_ht, const gchar *key, const gchar *seq)
{
  gchar *key1, *value;
  g_return_val_if_fail(ng_ht != NULL, FALSE);
  g_return_val_if_fail(key != NULL, FALSE);
  if (! seq)  return FALSE;

  if (g_hash_table_lookup_extended(ng_ht, key, (gpointer *) &key1, (gpointer *) &value))
  {
    g_hash_table_remove(ng_ht, key);
    str_free(&key1);  str_free(&value);
    g_hash_table_insert(ng_ht, g_strdup(key), g_strdup(seq));
    return TRUE;
  }
  return FALSE;
}


static gchar *parse_get_seq(gchar *seq, glong *low, glong *high)
{
  *low = strtol(seq, &seq, 10);
  if (*seq == '-')
  {
    seq++;
    *high = strtol(seq, &seq, 10);
  }
  else  *high = *low;

  while(*seq && (*seq < '0' || *seq > '9'))  seq++;
  return seq;
}

static glong parse_seq(grn_newsgroup *ng)
{
  gchar *ptr;
  glong low=0, high=0, sum=0;
  
  if (! str_check(ng->seq))
  {
    if ((ng->first <= 0) || (ng->total <= 0))  return (-1);
    return ng->total;
  }
  ptr = ng->seq;
  while (*ptr)
  {
    ptr = parse_get_seq(ptr, &low, &high);

    if ((ng->first == ng->total) && (high == ng->first) && (high > 1))  return 2;
    if ((sum <= 0) && (high >= ng->first))  sum++;
    if (high < ng->first)  high = ng->first;
    if (low < ng->first)  low = ng->first;
    if (low == high)  sum++;
    else  sum += (high - low) + 1;
  }
  return sum;
}

glong grn_news_get_unread(grn_newsgroup *ng)
{
  glong nread;
  
  nread = parse_seq(ng);
  if ((nread >= 0))
  {
    if ((ng->total == ng->first) && (ng->first == 1))  nread++;
    return (ng->total - ng->first - nread + 2);
  }
  else  return -1;
}


void fill_newsrc_ng_list(GList *ng_list, GHashTable *ng_ht)
{
  GList *l;
  g_return_if_fail(ng_ht != NULL);
  
  for (l=ng_list; l; l=l->next)
  {
    grn_newsgroup *grp = (grn_newsgroup *) l->data;
    if (grp && grp->name)
    {
      gchar *seq = g_hash_table_lookup(ng_ht, grp->name);
      if (seq)
      {
        str_free(&(grp->seq));
	grp->seq = g_strdup(seq);
	grp->nunread = grn_news_get_unread(grp);
      }
    }
  }
}

void fill_newsrc_ng_desc(GList *ng_list, GHashTable *desc_ht)
{
  GList *l;
  g_return_if_fail(desc_ht != NULL);
  
  for (l=ng_list; l; l=l->next)
  {
    grn_newsgroup *grp = (grn_newsgroup *) l->data;
    if (grp && grp->name)
    {
      gchar *desc = g_hash_table_lookup(desc_ht, grp->name);
      if (desc)
      {
        str_free(&(grp->descr));
	grp->descr = g_strdup(desc);
      }
    }
  }
}


gboolean grn_news_art_read(gchar *seq, gulong msg_id)
{
  gchar *ptr;
  glong low=0, high=0;
  
  if (! str_check(seq))  return FALSE;
  ptr = seq;
  while (*ptr)
  {
    ptr = parse_get_seq(ptr, &low, &high);
    if ((msg_id >= low) && (msg_id <= high))  return TRUE;
  }
  return FALSE;
}


static gchar *subseq_construct(gulong low, gulong high)
{
  if (low == high)  return g_strdup_printf("%ld", high);
  else  return g_strdup_printf("%ld-%ld", low, high);
}

// Doesn't free the seq on return. Returns newly allocated string.
gchar *grn_news_add_read(gchar *seq, gulong msg_id)
{
  gchar *ret=NULL, *ret1=NULL, *ptr, *ptr1, *buf, *buf1;
  glong low=0, high=0, olow=0, ohigh=0, nlow=0, nhigh=0, min=0, max=0;

  if ((str_check(seq) && (grn_news_art_read(seq, msg_id))) || (msg_id <= 0))
    return g_strdup(seq);
  ptr = seq;
  while (ptr && (*ptr))
  {
    ptr = parse_get_seq(ptr, &low, &high);
    ptr1 = parse_get_seq(ptr, &nlow, &nhigh);
    if (msg_id == (low - 1))  low = msg_id;
    if (msg_id == (high + 1))  high = msg_id;
    if (ptr && (nhigh > 1) && ((high + 1) >= nlow))
    {
      high = nhigh;  ptr = ptr1;
    }
    if (low == ohigh)  low++;
    if ((low < min) || (min == 0))  min = low;
    if (high > max)  max = high;
    buf = subseq_construct(low, high);
    if ((olow > 0) && (ohigh > 0) && (msg_id > ohigh) && (msg_id < low))
    {
      buf1 = subseq_construct(msg_id, msg_id);
      if (ret)
      {
	ret1 = ret;
	ret = g_strconcat(ret1, ",", buf1, ",", buf, NULL);
	str_free(&ret1);
      }
      else  ret = g_strconcat(buf1, ",", buf, NULL);
      str_free(&buf1);
    }
    else {
      if (ret)
      {
        ret1 = ret;
	ret = g_strconcat(ret1, ",", buf, NULL);
	str_free(&ret1);
      }
      else  ret = g_strdup(buf);
    }
    str_free(&buf);
    olow = low;  ohigh = high;
  }
  buf1 = subseq_construct(msg_id, msg_id);
  if (msg_id < min)
  {
    if (ret)
    {
      ret1 = ret;
      ret = g_strconcat(buf1, ",", ret1, NULL);
      str_free(&ret1);
    }
    else  ret = g_strdup(buf1);
  }
  else if (msg_id > max)
  {
    if (ret)
    {
      ret1 = ret;
      ret = g_strconcat(ret1, ",", buf1, NULL);
      str_free(&ret1);
    }
    else  ret = g_strdup(buf1);
  }
  str_free(&buf1);
  return ret;
}

// Doesn't free the seq on return. Returns newly allocated string.
gchar *grn_news_add_unread(gchar *seq, gulong msg_id)
{
  gchar *ret=NULL, *ret1=NULL, *ptr, *buf, *buf1, *buf2;
  glong low=0, high=0, olow=0, ohigh=0;

  if (! str_check(seq))  return g_strdup("");
  if ((! grn_news_art_read(seq, msg_id)) || (msg_id <= 0))
    return g_strdup(seq);
  ptr = seq;
  while (*ptr)
  {
    ptr = parse_get_seq(ptr, &low, &high);
    olow = low;  ohigh = high;
    if (msg_id == low)  low++;
    if (msg_id == high)  high--;

    buf = subseq_construct(low, high);
    if ((olow == ohigh) && (olow == msg_id)) ;
    else if ((msg_id > low) && (msg_id < high))
    {
      buf1 = subseq_construct(low, msg_id - 1);
      buf2 = subseq_construct(msg_id + 1, high);
      if (ret)
      {
        ret1 = ret;
	ret = g_strconcat(ret1, ",", buf1, ",", buf2, NULL);
	str_free(&ret1);
      }
      else  ret = g_strconcat(buf1, ",", buf2, NULL);
      str_free(&buf1);  str_free(&buf2);
    }
    else {
      if (ret)
      {
        ret1 = ret;
	ret = g_strconcat(ret1, ",", buf, NULL);
	str_free(&ret1);
      }
      else  ret = g_strdup(buf);
    }
    str_free(&buf);
  }
  return ret;
}

// msg_id is first or last id (for marking all unread and read, respectively).
gchar *grn_news_read_all(gulong msg_id)
{
  return subseq_construct(1, msg_id);
}

void grn_news_group_subscribe(GHashTable *ng_ht, grn_newsgroup *grp)
{
  gchar *seq, *key;
  g_return_if_fail(ng_ht != NULL);
  g_return_if_fail(grp != NULL);
  
  if (! str_check(grp->name))  return;
  seq = g_hash_table_lookup(ng_ht, grp->name);
  if (seq)  return;
  
  seq = grn_news_read_all(MAX(1, grp->first - 1));
  key = g_strdup(grp->name);
  g_hash_table_insert(ng_ht, key, seq);
}

void grn_news_group_unsubscribe(GHashTable *ng_ht, grn_newsgroup *grp)
{
  gchar *seq;
  g_return_if_fail(ng_ht != NULL);
  g_return_if_fail(grp != NULL);
  
  if (! str_check(grp->name))  return;
  seq = g_hash_table_lookup(ng_ht, grp->name);
  if (! seq)  return;
  g_hash_table_remove(ng_ht, grp->name);
}


syntax highlighted by Code2HTML, v. 0.9.1