/* Simple BBC News ticker client
 * (c) Darren Salt
 * GPL applies
 * $Id: ft-rdf.c,v 1.14 2007/04/25 14:11:15 ds Exp $
 */

/* System includes */

#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <swis.h>

/* Program includes */

#include "globals.h"

#include "digest.h"
#include "fetch.h"
#include "util.h"
#include "wimp.h"
#include "xml.h"


static int fetch_headings (void);
static int fetch_stories (void);
static int fetch_resources (void);
static int find_last_update (char *);
static char *find_next_item (char *);
static char *find_headline (char **);
static char *find_resource (char **);
static char *find_description (char **);
static char *find_publish_date (char **);
static char *find_url (char **);
static void xfree_old_data (void);


const struct server fetcher_bbc = {
/* Test data
  format_BBC, 9, 1, "", "BBC News", "test", 80,
  fetch_headings, "test.h.bbc",
  fetch_stories, "test.bbc",
*/
  format_BBC, 1, 0, "", "BBC News", "news.bbc.co.uk", 80,
  fetch_headings, "",
  fetch_stories, "rss/newsonline_uk_edition/front_page/rss091.xml",
  fetch_resources,
  find_last_update,
  find_next_item,
  find_headline,
  find_resource,
  find_description,
  find_publish_date,
  find_url,
  xfree_old_data
};


const struct server fetcher_rdf = {
  format_RDF, 1, 0, "", "", "", 0,
  fetch_headings,
  "",
  fetch_stories,
  "",
  fetch_resources,
  find_last_update,
  find_next_item,
  find_headline,
  find_resource,
  find_description,
  find_publish_date,
  find_url,
  xfree_old_data
};


static int
fetch_headings (void)
{
  return 1;
}


static int first_headline;

static int
find_last_update (char *unused)
{
  /* unused */ unused = unused;
  return 1;
}


static char *
find_next_item (char *ptr)
{
  if (first_headline)
  {
    first_headline = 0;
    return ptr;
  }
  return (char *)next_element (ptr);
}


static const char *
find_rdf_start (const char *prev, int channel)
{
  const char *p = find_element (stories_data, "rdf:RDF");
  if (!p)
  {
    p = find_element (stories_data, "rss");
    if (channel)
      p = find_element (first_child_element (p), "channel");
  }
  return first_child_element (p);
}


static char *
find_text_of (const char *ptr, const char *element)
{
  const char *item = find_element (ptr, element);
  if (item)
    item = get_text (first_child_element (item));
  return (char *)item;
}


static char *
find_headline (char **ptr)
{
  const char *item;

  if (first_headline)
  {
    const char *p = find_text_of (*ptr, "lastBuildDate");
    if (!p)
      p = find_text_of (*ptr, "pubDate");
    /* element present and with non-null content? */
    if (p)
    {
      static char *update = 0;
      update = realloc (update, (p ? strlen (p) : 0) + 14);
      if (update)
      {
	sprintf (update, "Last update: %s", p);
	return update;
      }
    }
    else
    {
      static char update[64];
      int time[2];
      time[0] = 3;
      _kernel_osword (14, time);
      _swix (0x4304B, _INR (0, 4), -1, time, update, sizeof (update),
	     "Last fetched: %24:%MI, %ZDY %M3 %CE%YR");
      if (*update)
        return update;
    }
  }

  item = find_element (*ptr, "item");
  if (!item)
    return 0;

  *ptr = (char *)item;
  return find_text_of (first_child_element (item), "title");
}


static char *
find_resource (char **ptr)
{
  static const char elements[][16] = {
//    "channel",
    "textinput",
    ""
  };
  const char *item = 0;
  int i = -1;

  while (elements[++i][0])
  {
    const char *p = find_element (*ptr, elements[i]);
    if (!item || (p && p < item))
      item = p;
  }
  if (!item)
    return 0;
  *ptr = (char *)item;

  return find_text_of (first_child_element (item), "title");
}


static char *
find_description (char **ptr)
{
  return (first_headline || !*ptr)
	 ? 0
	 : find_text_of (first_child_element (*ptr), "description");
}


static char *
find_publish_date (char **ptr)
{
  return (first_headline || !*ptr)
	 ? 0
	 : find_text_of (first_child_element (*ptr), "pubDate");
}


static char *
find_url (char **ptr)
{
  return (first_headline || !*ptr)
	 ? 0
	 : find_text_of (first_child_element (*ptr), "link");
}


/* Fetch the headlines text. */

static int
fetch_stories (void)
{
  const char *p, *ch;
  static const char *const tags[] = {
    /* http://web.resource.org/rss/1.0/spec */
    "rss", "rdf:RDF", "item", "channel", "textinput",
    "title", "link", "description",
    /* http://web.resource.org/rss/1.0/modules/syndication/ */
    "sy:updateBase", "sy:updatePeriod", "sy:updateFrequency",
    /* Non-standard */
    "lastBuildDate", "pubDate",
    0
  };

  fetch_stories_file (1);

  if (!stories_data || !init_stories ())
    return 0;
  if (!parse_xml (stories_data, tags))
  {
    status_error ("Parse failed: malformed RSS?");
    return 0;
  }

  first_headline = 1;

  /* Stories pointer setup (point into the fetched text) */

  p = find_rdf_start (0, 1);
  if (!p)
  {
    status_error ("Parse failed: malformed RSS?");
    return 0;
  }

  ch = find_element (p, "channel");
  if (ch)
  {
    long ord[9] = { 0 };
    const char *u_base;

    ch = first_child_element (ch);
    u_base = find_text_of (ch, "sy:updateBase");
    ticker_data.update_time = 0;

    if (opt.use_doc_update && u_base
	&& sscanf (u_base, "%4i-%2i-%2iT%2i:%2i", &ord[6], &ord[5], &ord[4],
		   &ord[3], &ord[2]) == 5)
    {
      const char *u_period = find_text_of (ch, "sy:updatePeriod");
      const char *u_freq = find_text_of (ch, "sy:updateFrequency");
      long long now_ck = 3, base = 0;
      unsigned long period = 24 * 60 * 60 * 100, freq = 1;

      long now_mt = read_monotonic_time ();
      _kernel_osword (14, (int *)&now_ck); /* read RTC */
      _swi (Territory_ConvertOrdinalsToTime, _INR (0, 2), -1, &base, ord);

      if (u_freq)
      {
	freq = atoi (u_freq);
	if (freq < 1)
	  freq = 1;
      }

      if (u_period)
      {
	if (!stricmp (u_period, "hourly"))
	  period = 60 * 60 * 100;
	else if (!stricmp (u_period, "weekly"))
	  period *= 7;
	else if (!stricmp (u_period, "monthly"))
	  period *= 30; /* should be variable */
	else if (!stricmp (u_period, "yearly"))
	  period *= 365; /* should be variable */
      }

      period /= freq;
      while (base <= now_ck - period)
	base += period;

      if (period >= (unsigned long) LONG_MAX)
	period = (unsigned long) LONG_MAX; /* limit: 32-bit signed */

      now_ck -= base;
      if (now_ck > LONG_MAX / 2)
        now_ck = LONG_MAX / 2;

      ticker_data.last_update = now_mt - (long) now_ck;
      ticker_data.update_time = (long) period;
    }
  }
  else
    ch = p;
  ticker_data.title.headline = find_text_of (ch, "title");
  if (ticker_data.title.headline)
  {
    ticker_data.title.description = find_text_of (ch, "description");
//    ticker_data.title.publish_date = find_text_of (ch, "pubDate");
    ticker_data.title.url = find_text_of (ch, "link");
  }
  else
  {
    ticker_data.title.headline = fetcher.label;
    ticker_data.title.description = 0;
    ticker_data.title.url = 0;
  }
  ticker_data.title.publish_date = 0;
  ticker_data.title.icon = 1;

  while (!is_end_of_xml (p))
    if ((p = append_story (0, (char *)p)) == 0)
    {
      free_stories ();
      return 0;
    }

  return 1;
}


static int
fetch_resources (void)
{
  const char *p = find_rdf_start (0, 0);
  if (!p)
    return 0;

  /* Do <channel> element */
  append_resource ((char *)p);

  p = find_rdf_start (0, 1);
  if (!p)
    return 0;

  /* Do elements within <channel> */
  while (!is_end_of_xml (p))
    if ((p = append_resource ((char *)p)) == 0)
      return 0;

  return 1;
}


static void
xfree_old_data (void)
{
}
