//  BMP
//  Copyright (C) 2005-2008 BMP development.
//
//  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.
//
//  --
//
//  The BMPx project hereby grants permission for non-GPL compatible GStreamer
//  plugins to be used and distributed together with GStreamer and BMPx. This
//  permission is above and beyond the permissions granted by the GPL license
//  BMPx is covered by.

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif //HAVE_CONFIG_H

#include <cstdlib>
#include <cstring>
#include <cctype>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <vector>
#include <iostream>
#include <iterator>
#include <string>

#include <gtkmm.h>
#include <cairomm/cairomm.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <glib/gi18n.h>

#include <boost/algorithm/string.hpp>
#include <boost/regex.hpp>

#include "debug.hh"
#include "md5.h"
#include "minisoup.hh"
#include "uri.hh"
#include "util.hh"

using namespace Glib;

#define BOOST_REGEX_MATCH_EXTRA 1

namespace
{
  const struct {
      const char *exp;
      const char *fmt;
  } lastfm_regexes[] = {

     { "(\\[[^\\]]+\\])",
       "(?1)"},

     { "(\\[\\/[^\\]]+\\])",
       "(?1)"},
  };

  const struct {
      const char *exp;
      const char *fmt;
  } html_regexes[] = {

     { "(<div.*/div>)",
       "(?1)"},

     { "(<img[^>]*>)",
       "(?1)"},

     { "(<a\\s+href\\s*=\")([^\">]*?)(\".*?>)([^>]*?)(<\\s*/\\s*a>)",
       "$2\\: \"$4\""},

     { "(<\\s*p\\s*>)|(<\\s*/\\s*p\\s*>)",
       "(?1\\n)(?2\\n)" },

     { "(<br\\s*/\\s*>)",
       "(?1\\n)" },

     { "(<ul>)",
       "(?1\\n)" },

     { "(</ul>)",
       "(?1)" },

     { "(<li>)",
       "(?1\\n•\\t)" },

     { "(</li>)",
       "(?1\\n)" },

  };

  // This table of RFC822 timezones is from gmime-utils.c of the gmime API */
  const struct {
    char *name;
    int   offset;
  } tz_offsets [] = {
    { "UT", 0 },
    { "GMT", 0 },
    { "EST", -500 },        // These are all US timezones.  Bloody Yanks!!
    { "EDT", -400 },
    { "CST", -600 },
    { "CDT", -500 },
    { "MST", -700 },
    { "MDT", -600 },
    { "PST", -800 },
    { "PDT", -700 },
    { "Z", 0 },
    { "A", -100 },
    { "M", -1200 },
    { "N", 100 },
    { "Y", 1200 }
  };

  // Returns timezone offset in seconds
  // Code (C) Liferea Developers
  time_t common_parse_rfc822_tz (char *token)
  {
    int offset = 0;
    const char *inptr = token;

    if (*inptr == '+' || *inptr == '-')
    {
      offset = atoi (inptr);
    }
    else
    {
      int t;

      if (*inptr == '(') inptr++;

      for (t = 0; t < 15; t++)
      {
        if (!strncmp (inptr, tz_offsets[t].name, strlen (tz_offsets[t].name))) offset = tz_offsets[t].offset;
      }
    }
    return 60 * ((offset / 100) * 60 + (offset % 100));
  }
}

namespace Bmp
{
  namespace Util
  {

    bool
    str_has_prefix_nocase (std::string const& str,
                           std::string const& prefix)
    {
      if (str.empty () || prefix.empty ())
        return false;

      return (g_ascii_strncasecmp (str.c_str (), prefix.c_str (), prefix.length ()) == 0);
    }

    bool
    str_has_suffix_nocase (std::string const& str,
                           std::string const& suffix)
    {
      if (str.empty () || suffix.empty ())
        return false;

      return (g_ascii_strcasecmp (str.c_str () + str.length () - suffix.length (), suffix.c_str ()) == 0);
    }

    bool
    str_has_suffixes_nocase (std::string const& str,
                             char const**       suffixes)
    {
      if (str.empty () || !suffixes)
        return false;

      for (char const** suffix = suffixes; *suffix; ++suffix)
        {
          if (str_has_suffix_nocase (str, std::string (*suffix)))
            return true;
        }

      return false;
    }

    bool
    str_has_suffixes_nocase (std::string const& str,
                             StrV const&        strv)
    {
      if (str.empty () || strv.empty ())
        return false;

      for (StrV::const_iterator i = strv.begin () ; i != strv.end (); ++i)
        {
          if (str_has_suffix_nocase (str, *i))
            return true;
        }

      return false;
    }

    std::string
    stdstrjoin (StrV const& strings, std::string const& delimiter)
    {
      std::string result;
      StrV::const_iterator e = strings.end(); 
      --e;
      for (StrV::const_iterator i = strings.begin(); i != strings.end(); ++i) 
      {
        result += *i; 
      	if (i != e)
      	{
	        result += delimiter;
        }
      }
      return result;
    }

    Glib::ustring
    utf8_string_normalize (Glib::ustring const& in)
    {
      StrV uppercased;

      if (in.empty())
        return Glib::ustring();

      char ** splitted = g_strsplit_set (in.c_str(), " -", -1);
      int n = 0;

      while (splitted[n])
      {
        if (strlen (splitted[n]))
        {
          ustring o  = ustring (splitted[n]).normalize().lowercase();
          ustring ol = o.substr (1);
          ustring of = ustring (1, o[0]).uppercase();
          ustring compose = (of + ol);
          uppercased.push_back (compose);
        }
        ++n;
      }

      g_strfreev (splitted);
      ustring norm = stdstrjoin (uppercased, " ");
      return norm; 
    }


    bool
    match_keys (ustring const& _h,
                ustring const& _n)
    {
        using boost::algorithm::split;
        using boost::algorithm::is_any_of;
        using boost::algorithm::find_first;

        StrV m;

        std::string n (_n.lowercase());
        std::string h (_h.lowercase());

        split( m, n, is_any_of(" ") );

        for (StrV::const_iterator i = m.begin (); i != m.end (); ++i)
        {
	        if (!find_first (h, (*i)))
	          return false;
        }

        return true;
    }

    std::string
    hex_string (void const* data,
                std::size_t len)
    {
      static const char hexchars[] = "0123456789abcdef";

      guint8 const* byte_array = static_cast<guint8 const*> (data);

      std::string str;
      str.reserve (len * 2);

      for(unsigned int i = 0; i < len; i++)
        {
          str.push_back (hexchars[byte_array[i] >> 4]);
          str.push_back (hexchars[byte_array[i] & 0x0f]);
        }

      return str;
    }

    std::string
    md5_hex_string (void const* data,
                    std::size_t len)
    {
        md5_state_t md5state;
        md5_byte_t  md5pword[16];

        md5_init (&md5state);
        md5_append (&md5state, static_cast<md5_byte_t const*> (data), static_cast<int> (len));
        md5_finish (&md5state, md5pword);

        return hex_string (md5pword, sizeof (md5pword));
    }

    std::string
    get_timestr (time_t t,
                 int    gmt)
    {
      struct tm *tm;
      static char buf[30];

      tm = gmt ? gmtime(&t) : localtime(&t);
      snprintf(buf, sizeof(buf), "%d-%.2d-%.2d %.2d:%.2d:%.2d",
          tm->tm_year + 1900,
          tm->tm_mon + 1,
          tm->tm_mday,
          tm->tm_hour,
          tm->tm_min,
          tm->tm_sec);
      return std::string(buf);
    }

    // Last.fm metadata markup sanitizer
    ustring
    sanitize_lastfm (ustring const& in)
    {
      std::string out = in;
      try {
        boost::regex e1;
        for (unsigned int n = 0; n < G_N_ELEMENTS(lastfm_regexes); ++n)
        {
          e1.assign (lastfm_regexes[n].exp);
          out = boost::regex_replace (out, e1, lastfm_regexes[n].fmt, boost::match_default | boost::format_all | boost::match_extra);
        }
       }
      catch (boost::regex_error & cxe)
      {
        g_warning ("%s: Error during Last.FM Markup sanitize: %s", G_STRLOC, cxe.what());
      }
      return out; 
    }

    // HTML Sanitizer based on regex example code Copyright (c) 1998-2002 John Maddock
    ustring
    sanitize_html (ustring const& in)
    {
      std::string out = in;
      try {
        boost::regex e1;
        for (unsigned int n = 0; n < G_N_ELEMENTS(html_regexes); ++n)
        {
          e1.assign(html_regexes[n].exp);
          out = boost::regex_replace(out, e1, html_regexes[n].fmt, boost::match_default | boost::format_all | boost::match_extra);
        }

       }
      catch (boost::regex_error & cxe)
      { 
        g_warning ("%s: Error during HTML Markup sanitize: %s", G_STRLOC, cxe.what());
      }

      return out; 
    }

    // Converts a RFC822 time string to a time_t value
    // Code (C) Liferea Developers
    time_t parseRFC822Date (const char * date)
    {
      struct tm	tm;
      time_t t, t2;
      char 	 *oldlocale;
      char	 *pos;
      gboolean	success = FALSE;

      memset (&tm, 0, sizeof(struct tm));

      // We expect at least something like "03 Dec 12 01:38:34"
      // and don't require a day of week or the timezone.
      //
      // The most specific format we expect:  "Fri, 03 Dec 12 01:38:34 CET"

      // Skip day of week
      if (NULL != (pos = g_utf8_strchr(date, -1, ','))) date = ++pos;

      // We expect English month names, so we set the locale
      oldlocale = g_strdup (setlocale (LC_TIME, NULL));
      setlocale (LC_TIME, "C");

      // Standard format with seconds and 4 digit year
      if (0 != (pos = strptime((const char *)date, " %d %b %Y %T", &tm)))
        success = TRUE;
      // Non-standard format without seconds and 4 digit year
      else if (0 != (pos = strptime((const char *)date, " %d %b %Y %H:%M", &tm)))
        success = TRUE;
      // Non-standard format with seconds and 2 digit year
      else if (0 != (pos = strptime((const char *)date, " %d %b %y %T", &tm)))
        success = TRUE;
      // Non-standard format without seconds 2 digit year
      else if (0 != (pos = strptime((const char *)date, " %d %b %y %H:%M", &tm)))
        success = TRUE;

      while (pos != 0 && *pos != '\0' && isspace((int)*pos)) pos++; // skip whitespaces before timezone

      if (0 != oldlocale)
      {
        setlocale (LC_TIME, oldlocale);	// Reset Locale
        g_free (oldlocale);
      }

      if (TRUE == success)
      {
        if((time_t)(-1) != (t = mktime(&tm)))
        {

          //
          // GMT time, with no daylight savings time
          // correction. (Usually, there is no daylight savings
          // time since the input is GMT.)
          //

          t = t - common_parse_rfc822_tz (pos);
          t2 = mktime (gmtime(&t));
          t = t - (t2 - t);
          return t;
        }
        else
        {
          g_warning ("Internal error! time conversion error! mktime failed!\n");
        }
      }

      return 0;
    }

    std::string
    get_date_string (std::string const& in)
    {
      int y = 0, m = 0, d = 0;

      const char * z (in.c_str());

      if (strlen (z) == 4)
        sscanf (z, "%04d", &y);
      else
      if (strlen (z) == 6)
        sscanf (z, "%04d%02d", &y, &m);
      else
      if (strlen (z) == 7)
        sscanf (z, "%04d-%02d", &y, &m);
      else
      if (strlen (z) == 8)
        sscanf (z, "%04d%02d%02d", &y, &m, &d);
      else
      if ((strlen (z) == 10) ||  (strlen (z) == 19))
        sscanf (z, "%04d-%02d-%02d", &y, &m, &d);

      char result[512];

      struct tm * _tm = g_new0 (struct tm, 1);

      if (y) _tm->tm_year = (y - 1900);
      if (m) _tm->tm_mon  = m - 1;
      if (d) _tm->tm_mday = d;

      if (y && m && d)
      {
        g_free (_tm);
        strftime (result, 511, "%d %B %Y", _tm);
        return locale_to_utf8 (std::string (result));
      }
      else
      if (y && m)
      {
        g_free (_tm);
        strftime (result, 511, "%B %Y", _tm);
        return locale_to_utf8 (std::string (result));
      }
      else
      if (y)
      {
        g_free (_tm);
        strftime (result, 511, "%Y", _tm);
        return locale_to_utf8 (std::string (result));
      }
      return std::string();
    }

    std::string
    get_time ()
    {
      struct tm *tm;
      static char buf[30];

      time_t t = time (NULL);
      tm = gmtime (&t);
      snprintf(buf, sizeof(buf), "%d-%.2d-%.2dT%.2d:%.2d:%.2d",
          tm->tm_year + 1900,
          tm->tm_mon + 1,
          tm->tm_mday,
          tm->tm_hour,
          tm->tm_min,
          tm->tm_sec);
      return std::string(buf);
    }

    RefPtr<Gdk::Pixbuf>
    get_image_from_uri (ustring const& uri)
    {
      RefPtr<Gdk::Pixbuf> image = RefPtr<Gdk::Pixbuf>(0);

      try{

        URI u (uri);
        if (u.get_protocol() == URI::PROTOCOL_HTTP)
        {
          Soup::RequestSyncRefP request = Soup::RequestSync::create (ustring (u));
          guint code = request->run (); 

          if (code == 200)
          try{
                RefPtr<Gdk::PixbufLoader> loader = Gdk::PixbufLoader::create ();
                loader->write (reinterpret_cast<const guint8*>(request->get_data_raw()), request->get_data_size());
                loader->close ();
                image = loader->get_pixbuf();
            }
          catch (Gdk::PixbufError & cxe)
            {
                debug("utility","%s: Gdk::PixbufError: %s", G_STRLOC, cxe.what().c_str());
            }
        }
        else if (u.get_protocol() == URI::PROTOCOL_FILE)
        {
          image = Gdk::Pixbuf::create_from_file (filename_from_uri (uri));
        }
      }
      catch (Bmp::URI::ParseError & cxe)
      { 
          debug("utility","%s: URI Parse Error: %s", G_STRLOC, cxe.what());
      }

      return image;
    }

    void
    free_gvalue_ptr (GValue* value)
    {
        g_value_unset (value);
        g_free (value);
    }

    void
    draw_cairo_image (Cairo::RefPtr<Cairo::Context> const&      cr,
                      Cairo::RefPtr<Cairo::ImageSurface> const& image,
                      double                                    x,
                      double                                    y,
                      double                                    alpha)
    {
        cr->save ();

        cr->translate (x, y);

        cr->set_operator (Cairo::OPERATOR_ATOP);
        cr->set_source (image, 0.0, 0.0);
        cr->rectangle (0.0, 0.0, image->get_width (), image->get_height ());
        cr->clip ();
        cr->paint_with_alpha (alpha);

        cr->restore ();
    }
  }
}

