#include "News.h"

#define CFG_CAT_ADD(id, icon)                                       \
 {                                                                  \
    snprintf(buf, sizeof(buf), "%s/%s",                             \
             e_module_dir_get(news->module), icon);                 \
    cat = news_feed_category_new(id, buf);                          \
    c->feed.categories = eina_list_append(c->feed.categories, cat); \
 }
#define CFG_FEED_ADD(id, icon)                       \
 {                                                   \
    snprintf(buf, sizeof(buf), "%s/%s",              \
             e_module_dir_get(news->module), icon);  \
    feed = news_feed_new(id, buf, 1, 0, cat);        \
    cat->feeds = eina_list_append(cat->feeds, feed); \
 }

static int        _feed_activate(News_Feed *f);
static void       _feed_deactivate(News_Feed *f);
static News_Feed *_feed_find(News_Feed_Category *cat, char *name);
static News_Feed_Category *_feed_category_find(char *name);
static char      *_get_host_from_url(const char *url);
static char      *_get_file_from_url(const char *url);
static void       _cb_mouse_down(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void       _cb_mouse_out(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void       _cb_feed_open(void *data, Evas_Object *obj, const char *emission, const char *source);
static Eina_Bool        _cb_feed_server_add(void *data, int type, void *event);
static Eina_Bool        _cb_feed_server_del(void *data, int type, void *event);
static Eina_Bool        _cb_feed_server_data(void *data, int type, void *event);
static void       _cb_feed_parse(News_Feed_Document *doc, News_Parse_Error error, int changes);
static Eina_Bool        _cb_feeds_timer(void *data);
static int        _cb_sort_cats(const void *d1, const void *d2);
static int        _cb_sort_feeds(const void *d1, const void *d2);

static const News_Feed_Lang _feed_langs[] = {
   {"ca", "Catalan"},
   {"zh", "Chinese"},
   {"cs", "Czech"},
   {"nl", "Dutch"},
   {"da", "Danish"},
   {"de", "German"},
   {"en", "English"},
   {"fi", "Finnish"},
   {"fr", "French"},
   {"hu", "Hungarian"},
   {"it", "Italian"},
   {"ja", "Japanese"},
   {"ko", "Korean"},
   {"pl", "Polish"},
   {"pt", "Portuguese"},
   {"ru", "Russian"},
   {"sk", "Slovak"},
   {"sl", "Slovenian"},
   {"es", "Spanish"},
   {"sv", "Swedish"},
   {NULL, NULL}
};

/*
 * Public functions
 */

int
news_feed_init(void)
{
   News_Feed_Lang *lang;
   Eina_List *l;
   int i;

   /* create dynamic languages list from static one
      we create this list so language are accessible from everywhere */
   l = NULL;
   i = 0;
   while (_feed_langs[i].key)
     {
        lang = E_NEW(News_Feed_Lang, 1);
        lang->key = eina_stringshare_add(_feed_langs[i].key);
        lang->name = eina_stringshare_add(_feed_langs[i].name);
        l = eina_list_append(l, lang);
        i++;
     }
   news->langs = l;

   /* validate each feed and create host and file strings + attach to category */
   NEWS_FEED_FOREACH_BEG();
   if (!news_feed_edit(_feed,
                       (char *)_feed->name, _feed->name_ovrw,
                       (char *)_feed->language, _feed->language_ovrw,
                       (char *)_feed->description, _feed->description_ovrw,
                       (char *)_feed->url_home, _feed->url_home_ovrw,
                       (char *)_feed->url_feed,
                       (char *)_feed->icon, _feed->icon_ovrw,
                       _feed->important,
                       _cat, 1))
     _cat->feeds = eina_list_remove_list(_cat->feeds, _l_cats);
   NEWS_FEED_FOREACH_END();

   /* create 'feeds_visible' lists in categories */
   news_feed_lists_refresh(0);

   /* set the update timer */
   news_feed_timer_set(news->config->feed.timer_m);

   return 1;
}

void
news_feed_shutdown(void)
{
   /* destroy dynamic languages list */
   news_feed_lang_list_free(news->langs);

   /* delete the update timer */
   news_feed_timer_set(0);
}

void
news_feed_all_delete(void)
{
   News_Config *c;
   News_Feed *f;
   News_Feed_Category *cat;

   c = news->config;
   while (c->feed.categories)
     {
        cat = c->feed.categories->data;
        while (cat->feeds)
          {
             f = cat->feeds->data;
             cat->feeds = eina_list_remove_list(cat->feeds, cat->feeds);
             news_feed_free(f);
          }
        c->feed.categories = eina_list_remove_list(c->feed.categories,
                                                   c->feed.categories);
        news_feed_category_free(cat);
     }

   news_feed_lists_refresh(0);
}

void
news_feed_all_restore(void)
{
   News_Config *c;
   News_Feed_Category *cat;
   News_Feed *feed;
   char buf[4096];
   
   c = news->config;
   news_feed_all_delete();

   /* FEED_ADD uses the cat created by CAT_ADD */
   CFG_CAT_ADD( NEWS_FEED_ITEM_CAT_COMICS, NEWS_FEED_ITEM_CAT_ICON_COMICS );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_BOULETCORP, NEWS_FEED_ITEM_FEED_ICON_BOULETCORP );
   CFG_CAT_ADD( NEWS_FEED_ITEM_CAT_COMPUTERS, NEWS_FEED_ITEM_CAT_ICON_COMPUTERS );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_FRESHMEAT, NEWS_FEED_ITEM_FEED_ICON_FRESHMEAT );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_OSNEWS, NEWS_FEED_ITEM_FEED_ICON_OSNEWS );
   CFG_CAT_ADD( NEWS_FEED_ITEM_CAT_ENLIGHTENMENT, NEWS_FEED_ITEM_CAT_ICON_ENLIGHTENMENT );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_E_CVS, NEWS_FEED_ITEM_FEED_ICON_E_CVS );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_E_FR, NEWS_FEED_ITEM_FEED_ICON_E_FR );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_NEWS, NEWS_FEED_ITEM_FEED_ICON_NEWS );
   CFG_CAT_ADD( NEWS_FEED_ITEM_CAT_NEWS, NEWS_FEED_ITEM_CAT_ICON_NEWS );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_AFP_EN, NEWS_FEED_ITEM_FEED_ICON_AFP_EN );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_BBC_WORLD, NEWS_FEED_ITEM_FEED_ICON_BBC_WORLD );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_CNN, NEWS_FEED_ITEM_FEED_ICON_CNN );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_COURRIERINTERNATIONAL, NEWS_FEED_ITEM_FEED_ICON_COURRIERINTERNATIONAL );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_GOOGLE, NEWS_FEED_ITEM_FEED_ICON_GOOGLE );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_GOOGLE_FR, NEWS_FEED_ITEM_FEED_ICON_GOOGLE_FR );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_LEMONDE_UNE, NEWS_FEED_ITEM_FEED_ICON_LEMONDE );
   CFG_CAT_ADD( NEWS_FEED_ITEM_CAT_SCIENCES, NEWS_FEED_ITEM_CAT_ICON_SCIENCES );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_FUTURASCIENCES, NEWS_FEED_ITEM_FEED_ICON_FUTURASCIENCES );
   CFG_CAT_ADD( NEWS_FEED_ITEM_CAT_TEST, NEWS_FEED_ITEM_CAT_ICON_TEST );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_TEST, NEWS_FEED_ITEM_FEED_ICON_TEST );
   CFG_CAT_ADD( NEWS_FEED_ITEM_CAT_VARIOUS, NEWS_FEED_ITEM_CAT_ICON_VARIOUS );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_BARRAPUNTO, NEWS_FEED_ITEM_FEED_ICON_BARRAPUNTO );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_DIGG, NEWS_FEED_ITEM_FEED_ICON_DIGG );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_MENEAME, NEWS_FEED_ITEM_FEED_ICON_MENEAME );
   CFG_FEED_ADD(  NEWS_FEED_ITEM_FEED_SLASHDOT, NEWS_FEED_ITEM_FEED_ICON_SLASHDOT );

   news_feed_lists_refresh(0);
}

void
news_feed_lists_refresh(int sort)
{
   Eina_List *l, *l2, *list;
   News_Feed_Category *cat;
   News_Feed *f;
   int list_free;

   /* 1. sort by name */

   if (sort && news->config->feed.sort_name)
     {
        list = news->config->feed.categories;
        list = eina_list_sort(list, eina_list_count(list), _cb_sort_cats);
        news->config->feed.categories = list;

        for (l=news->config->feed.categories; l; l=eina_list_next(l))
          {
             cat = l->data;
             list = cat->feeds;
             list = eina_list_sort(list, eina_list_count(list), _cb_sort_feeds);
             cat->feeds = list;
          }
     }

   /* 2. create "feeds_visible" in categories */

   for (l=news->config->feed.categories; l; l=eina_list_next(l))
     {
        cat = l->data;
        list = NULL;
        list_free = 0;
        if (news->config->feed.langs_all)
          {
             list = cat->feeds;
          }
        else
          {
             for (l2=cat->feeds; l2; l2=eina_list_next(l2))
               {
                  f = l2->data;
                  if (news_feed_lang_selected_is(f->language))
                    list = eina_list_append(list, f);
               }
             list_free = 1;
          }

        if (cat->feeds_visible_free && cat->feeds_visible)
          eina_list_free(cat->feeds_visible);

        cat->feeds_visible = list;
        cat->feeds_visible_free = list_free;
     }

   /* 3. refresh ui */

   news_feed_category_list_ui_refresh();
}

void
news_feed_timer_set(int time)
{
   if (news->feeds_timer)
     {
        ecore_timer_del(news->feeds_timer);
        news->feeds_timer = NULL;
     }
   if (!time) return;

   news->config->feed.timer_m = time;
   news->feeds_timer = ecore_timer_add(time * 60,
                                       _cb_feeds_timer, NULL);
}

News_Feed *
news_feed_new(char *name, int name_ovrw, char *language, int language_ovrw, char *description, int description_ovrw, char *url_home, int url_home_ovrw, char *url_feed, char *icon, int icon_ovrw, int important, News_Feed_Category *category)
{
   News_Feed *f;

   f = E_NEW(News_Feed, 1);

   if (!news_feed_edit(f,
                       name, name_ovrw,
                       language, language_ovrw,
                       description, description_ovrw,
                       url_home, url_home_ovrw,
                       url_feed,
                       icon, icon_ovrw,
                       important,
                       category, 0))
     {
        free(f);
        return NULL;
     }

   return f;
}

int
news_feed_edit(News_Feed *f, char *name, int name_ovrw, char *language, int language_ovrw, char *description, int description_ovrw, char *url_home, int url_home_ovrw, char *url_feed, char *icon, int icon_ovrw, int important, News_Feed_Category *category, int check_only)
{
   News_Feed *f2;
   char *host, *file;
   int update = 0;

   if ( !name || !name[0] )
     {
        news_util_message_error_show(D_("The <hilight>name</hilight> you entered is not correct"));
        return 0;
     }
   if (!category)
     {
        news_util_message_error_show(D_("The need to <hilight>select a category</hilight>."));
        return 0;
     }
   f2 = _feed_find(category, name);
   if ( f2 && (f != f2) )
     {
        news_util_message_error_show(D_("A feed with the <hilight>name</hilight> %s <hilight>already exists</hilight><br>"
                                        "in the %s category<br><br>"
                                        "Its not possible to have feeds with the same name<br>"
                                        "in one category."),
                                     name, category->name);
        return 0;
     }
   if (!language || !language[0])
     {
        news_util_message_error_show(D_("You need to select a <hilight>language</hilight>."));
        return 0;
     }
   if ( !url_feed || !url_feed[0] ||
        !(host = _get_host_from_url(url_feed)) ||
        !(file = _get_file_from_url(url_feed)) )
     {
        news_util_message_error_show(D_("The <hilight>url</hilight> you specified is not correct"));
        if (host) free(host);
        return 0;
     }

   if (!check_only)
     {
        if (f->name != name)
          {
             if (f->name) eina_stringshare_del(f->name);
             f->name = eina_stringshare_add(name);
          }
        f->name_ovrw = name_ovrw;

        if (f->language != language)
          {
             char lang[3];

             if (f->language) eina_stringshare_del(f->language);
             snprintf(lang, sizeof(lang), "%2s", language);
             if (language) f->language = eina_stringshare_add(lang);
          }
        f->language_ovrw = language_ovrw;

        if (f->description != description)
          {
             if (f->description) eina_stringshare_del(f->description);
             if (description) f->description = eina_stringshare_add(description);
          }
        f->description_ovrw = description_ovrw;

        if (f->url_home != url_home)
          {
             if (f->url_home) eina_stringshare_del(f->url_home);
             if (url_home) f->url_home = eina_stringshare_add(url_home);
          }
        f->url_home_ovrw = url_home_ovrw;

        if ( (f->url_feed != url_feed) &&
             (!f->url_feed || strcmp(f->url_feed, url_feed)) )
          {
             if (f->url_feed) eina_stringshare_del(f->url_feed);
             f->url_feed = eina_stringshare_add(url_feed);
             update = 1;
          }

        if (f->icon != icon)
          {
             if ( f->icon &&
                  !(icon && !strcmp(f->icon, icon)) )
               {
                  eina_stringshare_del(f->icon);
                  f->icon = NULL;
                  if (f->obj_icon)
                    {
                       evas_object_del(f->obj_icon);
                       f->obj_icon = NULL;
                    }
               }
             if (icon && !f->icon) f->icon = eina_stringshare_add(icon);
          }
        f->icon_ovrw = icon_ovrw;

        if (f->important != important)
          {
             f->important = important;
             if (f->item
                 && ( (f->item->config->view_mode == NEWS_ITEM_VIEW_MODE_FEED_IMPORTANT)
                      ||
                      ((f->item->config->view_mode == NEWS_ITEM_VIEW_MODE_FEED_IMPORTANT_UNREAD)
                       && f->doc && f->doc->unread_count)))
               news_item_refresh(f->item, 1, 0, 1);
          }

        f->category = category;

        if (f->item)
	  {
             News_Feed_Ref *ref;

             if (f->icon && !f->obj_icon)
               news_feed_obj_refresh(f, 1, 1);

             /* Feed Ref update */
             ref = news_feed_ref_find(f, f->item);
             if (ref)
               {
                  if (ref->name) eina_stringshare_del(ref->name);
                  ref->name = eina_stringshare_add(f->name);
                  if (ref->category) eina_stringshare_del(ref->category);
                  ref->category = eina_stringshare_add(f->category->name);
               }
	  }

        news_config_save();
     }
   else
     {
        f->category = category;
     }

   E_FREE(f->host);
   f->host = host;
   E_FREE(f->file);
   f->file = file;

   if (update && f->item) news_feed_update(f);

   return 1;
}

void
news_feed_free(News_Feed *f)
{
   News_Item *ni;

   ni = f->item;
   if (ni)
     {
        news_feed_detach(f, 1);
        news_item_refresh(ni, 1, 0, 0);
        news_viewer_refresh(ni->viewer);
     }
   if (f->doc) _feed_deactivate(f);

   if (f->config_dialog) news_config_dialog_feed_hide(f);
   if (f->menu) news_menu_feed_hide(f);
   if (f->obj_icon) evas_object_del(f->obj_icon);

   if (f->name) eina_stringshare_del(f->name);
   if (f->language) eina_stringshare_del(f->language);
   if (f->description) eina_stringshare_del(f->description);
   if (f->url_home) eina_stringshare_del(f->url_home);
   if (f->url_feed) eina_stringshare_del(f->url_feed);
   if (f->icon) eina_stringshare_del(f->icon);

   if (f->host) free(f->host);
   if (f->file) free(f->file);

   free(f);
}

/*
 * Attach a feed to an item
 * 
 * You can give the feed or the feed ref
 */
int
news_feed_attach(News_Feed *f, News_Feed_Ref *ref, News_Item *ni)
{
   if (!f)
     {
        News_Feed_Category *cat;
        News_Feed *f_look;
        Eina_List *l, *l2;
      
        for (l=news->config->feed.categories; l; l=eina_list_next(l))
          {
             cat = eina_list_data_get(l);
             if (!strcmp(cat->name, ref->category))
               {
                  for (l2=cat->feeds; l2; l2=eina_list_next(l2))
                    {
                       f_look = eina_list_data_get(l2);
                       if (!strcmp(f_look->name, ref->name))
                         {
                            f = f_look;
                            l = NULL;
                            break;
                         }
                    }
               }
          }
        if (!f) return 0;
     }

   if (!ref)
     {
        ref = E_NEW(News_Feed_Ref, 1);
        ref->category = eina_stringshare_add(f->category->name);
        ref->name = eina_stringshare_add(f->name);
        ref->feed = f;
        ni->config->feed_refs = eina_list_append(ni->config->feed_refs, ref);
     }
   else
     {
        ref->feed = f;
     }

   f->item = ni;
   if (f->doc)
     {
        if (f->doc->unread_count)
          news_item_unread_count_change(f->item, +1);
     }
   else
     {
        _feed_activate(f);
        news_feed_update(f);
     }

   return 1;
}

/*
 * Detach a feed from the item wich its attached to
 * 
 * If @p really is 0, only detach the feed from the item, but keep feed ref
 * If @p really is 1, remove the feed ref too
 */
void
news_feed_detach(News_Feed *f, int really)
{
   News_Item *ni;

   /* 1. delete the feed reference from items to feed */
   ni = f->item;
   if (ni)
     {
        News_Feed_Ref *ref;

        ref = news_feed_ref_find(f, ni);
        if (ref)
          {
             if (really)
               {
                  DITEM(("feed detach : delete feed ref (%s)", ref->feed->name));
                  ni->config->feed_refs = eina_list_remove(ni->config->feed_refs, ref);
                  eina_stringshare_del(ref->category);
                  eina_stringshare_del(ref->name);
                  free(ref);
               }
             else
               ref->feed = NULL;
          }
     }

   /* 2. gui items */
   if (f->obj)
     {
        evas_object_del(f->obj);
        f->obj = NULL;
     }
   if (f->obj_icon)
     {
        evas_object_del(f->obj_icon);
        f->obj_icon = NULL;
     }

   /* 3. unread count */
   if (f->doc && f->doc->unread_count)
     news_item_unread_count_change(f->item, -1);

   /* 4. misc cleanups */
   if (ni && ni->viewer)
     {
        if (ni->viewer->vfeeds.selected == f)
          ni->viewer->vfeeds.selected = NULL;
     }    

   /* 5. delete reference from feed to item */
   f->item = NULL;

   /* 6. the feed will be deactivated on next feed_update :)
      we wait until that because _detach is also called when there
      is a gadcon change (like size) */
}

int
news_feed_update(News_Feed *f)
{
   News_Feed_Document *doc;

   /* if the feed is not attached to an item
      it means that it was detached and never reattached,
      so deactivate the feed */
   if (!f->item)
     {
        _feed_deactivate(f);
        return 0;
     }

   doc = f->doc;

   if (doc->server.buffer)
     {
        free(doc->server.buffer);
        doc->server.buffer = NULL;
     }
   doc->server.buffer_size = 0;

   if (doc->server.conn)
     {
        //FIXME: segfault appears if i delete the server when connection has not been created yet (needs e patch)
        if (doc->server.waiting_reply)
          {
             //news_util_debug("Forced feed dl with waiting_replay of 1 !<br>ecore_con_server_del !<br>(%d tries)", doc->server.nb_tries);
             ecore_con_server_del(doc->server.conn);
             doc->server.waiting_reply = 0;
          }
        if (doc->server.nb_tries >= NEWS_FEED_NB_TRIES_MAX)
          return 0;
        doc->server.nb_tries++;
        doc->server.conn = NULL;
        //TODO: popup to warn that we are at at the nb_tries try =)
     }
   else
     doc->server.nb_tries = 1;

   if (news->config->proxy.enable &&
       news->config->proxy.port &&
       news->config->proxy.host && news->config->proxy.host[0])
     doc->server.conn = ecore_con_server_connect(ECORE_CON_REMOTE_SYSTEM,
						 news->config->proxy.host,
						 news->config->proxy.port,
						 doc);
   else
     doc->server.conn = ecore_con_server_connect(ECORE_CON_REMOTE_SYSTEM,
						 f->host,
						 80,
						 doc);

   if (!doc->server.conn)
     {
        //TODO: popup, if proxy, tell it
        DFEED(("Could not start connection to %s",
               f->host));
        return 0;
     }
   DFEED(("Trying to update feed %s", f->host));

   if (f->item && !f->item->loading_state)
     news_item_loadingstate_refresh(f->item);

   if (f->item && f->item->viewer &&
       (f->item->viewer->vfeeds.selected == f))
     news_viewer_feed_selected_infos_refresh(f->item->viewer);

   return 1;
}

/* 
 * Refresh the object showed in item
 */
void
news_feed_obj_refresh(News_Feed *f, int changed_content, int changed_state)
{
   News_Item *ni;
   Evas_Object *obj;

   obj = f->obj;
   ni = f->item;

   if (!ni) return;

   /* base obj */

   if (!f->obj)
     {
        obj = edje_object_add(ni->gcc->gadcon->evas);
        news_theme_edje_set(obj, NEWS_THEME_FEED);
        evas_object_event_callback_add(obj, EVAS_CALLBACK_MOUSE_DOWN,
                                       _cb_mouse_down, f);
        evas_object_event_callback_add(obj, EVAS_CALLBACK_MOUSE_OUT,
                                       _cb_mouse_out, f);
        edje_object_signal_callback_add(obj, "e,action,open", "e",
                                        _cb_feed_open, f);
        evas_object_propagate_events_set(obj, 0);
        evas_object_show(obj);
     }

   if (!f->obj || changed_state)
     {
        if (f->doc && f->doc->unread_count)
          edje_object_signal_emit(obj, "e,state,new,set", "e");
        else
          edje_object_signal_emit(obj, "e,state,new,unset", "e");
     }

   if (!f->obj || changed_content)
     {
        edje_object_part_text_set(obj, "name", f->name);
     }

   /* icon */

   if (f->obj_icon && changed_content)
     {
        evas_object_del(f->obj_icon);
        f->obj_icon = NULL;
     }

   if (!f->obj_icon && f->icon && f->icon[0])
     {
        Evas_Object *icon;
        
        DFEED(("Refresh view obj icon of feed %s : %s", f->name, f->icon));
        icon = e_icon_add(ni->gcc->gadcon->evas);
        e_icon_file_set(icon, f->icon);
        e_icon_fill_inside_set(icon, 1);
        edje_object_part_swallow(obj, "icon", icon);
        evas_object_show(icon);
        f->obj_icon = icon;
     }

   f->obj = obj;
}


News_Feed_Ref *
news_feed_ref_find(News_Feed *f, News_Item *ni)
{
   NEWS_ITEM_FEEDS_FOREACH_BEG(ni);
   if (_feed == f)
     return _ref;
   NEWS_ITEM_FEEDS_FOREACH_END();

   return NULL;
}

void
news_feed_read_set(News_Feed *feed)
{
   Eina_List *l;
   News_Feed_Article *art;

   if (!feed->doc) return;
   if (!feed->doc->unread_count) return;

   for (l=feed->doc->articles; l; l=eina_list_next(l))
     {
        art = l->data;
        if (art->unread)
          news_feed_article_unread_set(art, 0);
     }
}

void
news_feed_unread_count_change(News_Feed *feed, int nb)
{
   int was_empty = 0;

   if (!nb) return;
   if (!feed->doc->unread_count)
     was_empty = 1;
   feed->doc->unread_count += nb;
   if (was_empty || !feed->doc->unread_count)
     {
        if (nb > 0)
          news_item_unread_count_change(feed->item, 1);
        else
          news_item_unread_count_change(feed->item, -1);
        
        switch ((News_Item_View_Mode)feed->item->config->view_mode)
          {
          case NEWS_ITEM_VIEW_MODE_ONE:
             break;
          case NEWS_ITEM_VIEW_MODE_FEED:
          case NEWS_ITEM_VIEW_MODE_FEED_IMPORTANT:
             news_feed_obj_refresh(feed, 0, 1);
             break;
          case NEWS_ITEM_VIEW_MODE_FEED_UNREAD:
             news_item_refresh(feed->item, 1, 0, 1);
             break;
          case NEWS_ITEM_VIEW_MODE_FEED_IMPORTANT_UNREAD:
             if (feed->important)
               news_item_refresh(feed->item, 1, 0, 1);
             break;
          }

        if (feed->item->viewer)
          news_viewer_refresh(feed->item->viewer);
     }
   
   DFEED(("%s: UNREADcount = %d", feed->name, feed->doc->unread_count));
}

void
news_feed_list_ui_refresh(void)
{
   Eina_List *l;

   if (news->config_dialog_feeds)
     news_config_dialog_feeds_refresh_feeds();

   for (l=news->items; l; l=eina_list_next(l))
     {
        News_Item *ni;

        ni = eina_list_data_get(l);
        if (ni->config_dialog_content)
	  {
             news_config_dialog_item_content_refresh_feeds(ni);
             news_config_dialog_item_content_refresh_selected_feeds(ni);
	  }
     }
}

void
news_feed_article_del(News_Feed_Article *art)
{
   News_Feed_Document *doc;

   doc = art->doc;
   if (art->title) free(art->title);
   if (art->url) free(art->url);
   if (art->description) free(art->description);
   if (art->image) evas_object_del(art->image);

   if (doc->feed->item && doc->feed->item->viewer)
     {
        if (doc->feed->item->viewer->varticles.selected == art)
          doc->feed->item->viewer->varticles.selected = NULL;
     }

   if (doc->feed->item && art->unread)
     news_feed_unread_count_change(doc->feed, -1);

   doc->articles = eina_list_remove(doc->articles, art);

   free(art);
}

void
news_feed_article_unread_set(News_Feed_Article *art, int unread)
{
   /* check already in that state */
   if (unread == art->unread) return;

   art->unread = unread;
   if (art->doc->feed->item->viewer)
     news_viewer_article_state_refresh(art->doc->feed->item->viewer, art);

   if (unread)
     news_feed_unread_count_change(art->doc->feed, 1);
   else
     news_feed_unread_count_change(art->doc->feed, -1);
}

News_Feed_Category *
news_feed_category_new(char *name, char *icon)
{
   News_Feed_Category *cat;

   cat = E_NEW(News_Feed_Category, 1);

   if (!news_feed_category_edit(cat, name, icon))
     {
        free(cat);
        return NULL;
     }

   return cat;
}

int
news_feed_category_edit(News_Feed_Category *cat, char *name, char *icon)
{
   News_Feed_Category *cat2;

   if (!name || !name[0])
     {
        news_util_message_error_show(D_("You need to enter a <hilight>name</hilight> !"));
        return 0;
     }

   /* that name already exists ? */
   cat2 = _feed_category_find(name);
   if (cat2 && (cat2 != cat))
     {
        news_util_message_error_show(D_("The <hilight>name</hilight> you entered is <hilight>already used</hilight><br>"
                                       "by another category"));
        return 0;
     }

   if (cat->name != name)
     {
        if (cat->name) eina_stringshare_del(cat->name);
        cat->name = eina_stringshare_add(name);
     }

   if (cat->icon != icon)
     {
        if (cat->icon) eina_stringshare_del(cat->icon);
        if (icon) cat->icon = eina_stringshare_add(icon);
     }

   return 1;
}

void
news_feed_category_free(News_Feed_Category *cat)
{
   if (cat->feeds && eina_list_count(cat->feeds)) return;
   
   if (cat->name) eina_stringshare_del(cat->name);
   if (cat->icon) eina_stringshare_del(cat->icon);
   if (cat->feeds_visible_free && cat->feeds_visible)
     eina_list_free(cat->feeds_visible);
   if (cat->config_dialog) news_config_dialog_category_hide(cat);
   
   free(cat);
}

void
news_feed_category_list_ui_refresh(void)
{
   if (news->config_dialog_feeds)
     news_config_dialog_feeds_refresh_categories();
   if (news->config_dialog_feed_new)
     news_config_dialog_feed_refresh_categories(NULL);

   NEWS_FEED_FOREACH_BEG();
   if (_feed->config_dialog)
     news_config_dialog_feed_refresh_categories(_feed);
   NEWS_FEED_FOREACH_END();

   news_feed_list_ui_refresh();
}

void
news_feed_lang_list_refresh(void)
{
   if (news->config_dialog_feed_new)
     news_config_dialog_feed_refresh_langs(NULL);

   NEWS_FEED_FOREACH_BEG();
   if (_feed->config_dialog)
     news_config_dialog_feed_refresh_langs(_feed);
   NEWS_FEED_FOREACH_END();
}

void
news_feed_lang_list_free(Eina_List *list)
{
   News_Feed_Lang *lang;

   while ((lang = eina_list_data_get(list)))
     {
        if (lang->key) eina_stringshare_del(lang->key);
        if (lang->name) eina_stringshare_del(lang->name);
        list = eina_list_remove_list(list, list);
        free(lang);
     }
}

const char *
news_feed_lang_name_get(const char *key)
{
   int i;

   i = 0;
   while (_feed_langs[i].key)
     {
        if (!strncmp(_feed_langs[i].key, key, 2))
          return _feed_langs[i].name;
        i++;
     }
   return NULL;
}

int
news_feed_lang_selected_is(const char *key)
{
   News_Feed_Lang *lang;
   Eina_List *l;

   for (l=news->config->feed.langs; l; l=eina_list_next(l))
     {
        lang = l->data;
        if (!strncmp(lang->key, key, 2))
          return 1;
     }
   return 0;
}

/*
 * Private functions
 *
 */

static int
_feed_activate(News_Feed *f)
{
   News_Feed_Document *doc;

   doc = E_NEW(News_Feed_Document, 1);

   doc->feed = f;
   f->doc = doc;

   doc->ui_needrefresh = 1;

   doc->server.handler_add = ecore_event_handler_add(ECORE_CON_EVENT_SERVER_ADD,
						     _cb_feed_server_add, doc);
   doc->server.handler_del = ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DEL,
						     _cb_feed_server_del, doc);
   doc->server.handler_data = ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DATA,
                                                      _cb_feed_server_data, doc);

   doc->parse.type = NEWS_FEED_TYPE_UNKNOWN;

   return 1;
}

static void
_feed_deactivate(News_Feed *f)
{
   News_Feed_Document *doc;

   doc = f->doc;
   if (!doc) return;

   DFEED(("Feed %s deactivate", f->name));

   if (doc->parse.parser) news_parse_stop(doc);
       
   if (doc->parse.meta_article) eina_stringshare_del(doc->parse.meta_article);
   if (doc->parse.meta_channel) eina_stringshare_del(doc->parse.meta_channel);
   if (doc->parse.meta_date) eina_stringshare_del(doc->parse.meta_date);
   if (doc->parse.charset) eina_stringshare_del(doc->parse.charset);

   //FIXME: waiting ecore_con patch to be able to delete the server in all cases
   if (doc->server.conn && doc->server.waiting_reply) ecore_con_server_del(doc->server.conn);
   ecore_event_handler_del(doc->server.handler_add);
   ecore_event_handler_del(doc->server.handler_del);
   ecore_event_handler_del(doc->server.handler_data);
   if (doc->server.buffer) free(doc->server.buffer);
  
   if (doc->articles)
     {
        News_Feed_Article *article;

        while(doc->articles)
          {
             article = doc->articles->data;
             news_feed_article_del(article);
          }
     }
  
   if (doc->popw) news_popup_del(doc->popw);

   f->doc = NULL;
   free(doc);
}

static News_Feed *
_feed_find(News_Feed_Category *cat, char *name)
{
   News_Feed *f;
   Eina_List *l;

   for (l=cat->feeds; l; l=eina_list_next(l))
     {
        f = l->data;
        if (!strcmp(f->name, name))
          return f;
     }

   return NULL;
}

static News_Feed_Category *
_feed_category_find(char *name)
{
   Eina_List *l;

   for (l=news->config->feed.categories; l; l=eina_list_next(l))
     {
        News_Feed_Category *cat;

        cat = eina_list_data_get(l);
        if (!strcmp(cat->name, name))
          return cat;
     }

   return NULL;
}

static char *
_get_host_from_url(const char *url)
{
   char *host, *p;
   char buf[4096];

   strncpy(buf, url, sizeof(buf));
   if (strncmp(buf, "http://", 7))
     return NULL;
   if (*(buf+7) == '\0')
     return NULL;
   p = strchr(buf+7, '/');
   if (p) *p = '\0';
   host = strdup(buf+7);

   return host;
}

static char *
_get_file_from_url(const char *url)
{
   char *file, *p;

   p = strstr(url, "://");
   if (!p)
     return NULL;
   p += 3;
   p = strstr(p, "/");
   if (!p)
     file = strdup("/");
   else
     file = strdup(p);

   return file;
}

static void
_cb_mouse_down(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   News_Feed *f;
   Evas_Event_Mouse_Down *ev;

   f = data;
   ev = event_info;

   DFEED(("Mouse down %d", ev->button));

   switch(ev->button)
     {
        int cx, cy, cw, ch;
        
     case 3:
        if (f->menu) break;
        
        news_menu_feed_show(f);
        
        e_gadcon_canvas_zone_geometry_get(f->item->gcc->gadcon,
                                          &cx, &cy, &cw, &ch);
        e_menu_activate_mouse(f->menu,
                              e_util_zone_current_get(e_manager_current_get()),
                              cx + ev->output.x, cy + ev->output.y, 1, 1,
                              E_MENU_POP_DIRECTION_DOWN, ev->timestamp);
        evas_event_feed_mouse_up(f->item->gcc->gadcon->evas, ev->button,
                                 EVAS_BUTTON_NONE, ev->timestamp, NULL);
        break;
     }
}

static void
_cb_mouse_out(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   News_Item *ni;
   Evas_Event_Mouse_Out *ev;

   ni = data;
   ev = event_info;

   DITEM(("Mouse out"));

}

static void
_cb_feed_open(void *data, Evas_Object *obj, const char *emission, const char *source)
{
   News_Item *ni;
   News_Feed *f;

   f = data;
   ni = f->item;

   DITEM(("Cb feed %s open", f->name));

   /* if the object is not loaded yet, abort */
   if (!f->doc) return;

   switch (f->doc->feed->item->config->openmethod)
     {
     case NEWS_ITEM_OPENMETHOD_VIEWER:
        if (ni->viewer)
          {
             if (ni->viewer->vfeeds.selected == f)
               news_viewer_destroy(ni->viewer);
             else
               news_viewer_feed_select(ni->viewer, f);
          }
        else
          {
             news_viewer_create(ni);
             news_viewer_feed_select(ni->viewer, f);
          }
        break;
     case NEWS_ITEM_OPENMETHOD_BROWSER:
        if (ni->config->browser_open_home)
          news_util_browser_open(f->url_home);
        else
          news_util_browser_open(f->url_feed);
        news_feed_read_set(f);
        break;
     }
}

static Eina_Bool
_cb_feed_server_add(void *data, int type, void *event)
{
   News_Feed_Document *doc;
   Ecore_Con_Event_Server_Add *ev;
   char buf[4096];

   doc = data;
   ev = event;

   /* check if the event is our event */
   if (doc->server.conn != ev->server)
     return EINA_TRUE;

   DFEED(("Connection established after %d tries, sending request", doc->server.nb_tries));

   snprintf(buf, sizeof(buf), "GET %s HTTP/1.0\r\n", doc->feed->file);
   ecore_con_server_send(doc->server.conn, buf, strlen(buf));
   snprintf(buf, sizeof(buf), "Host: %s\r\n", doc->feed->host);
   ecore_con_server_send(doc->server.conn, buf, strlen(buf));
   snprintf(buf, sizeof(buf), "User-Agent: %s/v%d\r\n\r\n", "Enlightenment News module", news->config->version);
   ecore_con_server_send(doc->server.conn, buf, strlen(buf));

   doc->server.waiting_reply = 1;

   return EINA_TRUE;
}

static Eina_Bool
_cb_feed_server_del(void *data, int type, void *event)
{
   News_Feed_Document *doc;
   Ecore_Con_Event_Server_Del *ev;

   ev = event;
   doc = data;

   /* check if the event is our event */
   if (doc->server.conn != ev->server)
     return EINA_TRUE;

   ecore_con_server_del(doc->server.conn);
   doc->server.conn = NULL;

   DFEED(("Connection end"));

   if (!doc->server.buffer || !doc->server.buffer_size)
     {
        // TODO: error popup
        if (doc->feed->item)
          news_item_loadingstate_refresh(doc->feed->item);
        return EINA_TRUE;
     }

   doc->server.buffer_size++;
   doc->server.buffer = realloc(doc->server.buffer,
				doc->server.buffer_size);
   doc->server.buffer[doc->server.buffer_size - 1] = '\0';

   doc->server.waiting_reply = 0;

   news_parse_go(doc, _cb_feed_parse);

   return EINA_TRUE;
}

static Eina_Bool
_cb_feed_server_data(void *data, int type, void *event)
{
   News_Feed_Document *doc;
   Ecore_Con_Event_Server_Data *ev;

   ev = event;
   doc = data;

   DFEED(("Received %d octets !", ev->size));

   /* check if the event is our event */
   if (doc->server.conn != ev->server)
     return EINA_TRUE;

   doc->server.buffer = realloc(doc->server.buffer,
 				doc->server.buffer_size + ev->size);
   memcpy(doc->server.buffer + doc->server.buffer_size, ev->data, ev->size);
   doc->server.buffer_size += ev->size;

   return EINA_TRUE;
}

static void
_cb_feed_parse(News_Feed_Document *doc, News_Parse_Error error, int changes)
{
   News_Item *ni;

   ni = doc->feed->item;

   free(doc->server.buffer);
   doc->server.nb_tries = 0;
   doc->server.buffer = NULL;
   doc->server.buffer_size = 0;
   doc->parse.last_time = ecore_time_unix_get();

   //TODO with popups
   doc->parse.error = error;
   switch(error)
     {
     case NEWS_PARSE_ERROR_NO:
        break;
     case NEWS_PARSE_ERROR_BROKEN_FEED:
        break;
     case NEWS_PARSE_ERROR_TYPE_UNKNOWN:
        break;
     case NEWS_PARSE_ERROR_NOT_IMPLEMENTED:
        break;
     }

   if (changes)
     {
        //TODO: popup

        doc->ui_needrefresh = 1;

        if (ni)
          {
             if (ni->viewer)
               news_viewer_refresh(ni->viewer);
          }
     }
   else
     {
        if (ni && ni->viewer &&
            (ni->viewer->vfeeds.selected == doc->feed))
          news_viewer_feed_selected_infos_refresh(ni->viewer);
     }
}

static Eina_Bool
_cb_feeds_timer(void *data)
{
   NEWS_FEED_FOREACH_BEG();
   if (_feed->doc)
     news_feed_update(_feed);
   NEWS_FEED_FOREACH_END();

   return EINA_TRUE;
}

static int
_cb_sort_cats(const void *d1, const void *d2)
{
   const News_Feed_Category *c1, *c2;

   c1 = d1;
   c2 = d2;

   return strcmp(c1->name, c2->name);
}

static int
_cb_sort_feeds(const void *d1, const void *d2)
{
   const News_Feed *f1, *f2;

   f1 = d1;
   f2 = d2;

   return strcmp(f1->name, f2->name);
}
