//  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 Version 2
//  as published by the Free Software Foundation.
//
//  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.

#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <list>

#include <gtkmm.h>
#include <glibmm/i18n.h>
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <X11/Xlib.h>

#include <boost/format.hpp>

#include "main.hh"
#include "paths.hh"
#include "util.hh"
#include "ui-tools.hh"

namespace
{
  gdouble
  mm_to_inch (gdouble mm)
  {
    return mm / 25.40;
  }

  bool _has_alpha = false;
  GtkStyle *_style = 0;
  Glib::RefPtr<Gdk::Colormap> _colormap;
}

namespace Bmp
{
  namespace Util
  {
    void
    window_set_busy (GtkWindow* window)
    {
        GdkCursor *cursor = gdk_cursor_new_from_name (gdk_display_get_default (), "watch");

        if (!cursor)
          cursor = gdk_cursor_new_for_display (gdk_display_get_default (), GDK_WATCH);

        gdk_window_set_cursor (GTK_WIDGET (window)->window, cursor);
    }

    void
    window_set_idle (GtkWindow* window)
    {
        gdk_window_set_cursor (GTK_WIDGET (window)->window, NULL);
    }

    GtkWidget *
    ui_manager_get_popup (GtkUIManager * ui_manager,
                          char const*    path)
    {
      GtkWidget *menu_item;
      GtkWidget *submenu;

      menu_item = gtk_ui_manager_get_widget (ui_manager, path);

      if (GTK_IS_MENU_ITEM (menu_item))
          submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu_item));
      else
          submenu = NULL;

      return submenu;
    }

    Gtk::Widget *
    get_popup (Glib::RefPtr<Gtk::UIManager> ui_manager, Glib::ustring const& menupath)
    {
      Gtk::Widget * menuitem = 0;
      menuitem = ui_manager->get_widget (menupath);
      return (menuitem ? dynamic_cast <Gtk::MenuItem *> (menuitem)->get_submenu() : 0);
    }

    void
    window_set_icon_list (Gtk::Window&       window,
                          std::string const& icon_name)
    {
      static boost::format filename_fmt ("icon_%s_%d.png");

      static const unsigned int sizes[] = { 16, 32, 48, 64, 128 };
      static const unsigned int n_sizes = G_N_ELEMENTS (sizes);

      std::string theme     = mcs->key_get<std::string> ("bmp", "icon-theme");
      std::string theme_dir = Glib::build_filename (BMP_THEME_ICON_DIR, theme);

      std::list<Glib::RefPtr<Gdk::Pixbuf> > icon_list;

      for (unsigned int i = 0; i < n_sizes; i++)
      {
          std::string filename = (filename_fmt % icon_name % sizes[i]).str ();
          std::string path     = Glib::build_filename (theme_dir, filename);

          try
            {
              Glib::RefPtr<Gdk::Pixbuf> pixbuf = Gdk::Pixbuf::create_from_file (path);

              icon_list.push_back (pixbuf);
            }
          catch (Gdk::PixbufError & cxe)
            {
            }
      }

      window.set_icon_list (icon_list);
    }

    void
    window_set_busy (Gtk::Window & window)
    {
        window.get_window()->set_cursor (Gdk::Cursor (Gdk::Display::get_default(), "watch"));
    }

    void
    window_set_idle (Gtk::Window & window)
    {
        window.get_window()->set_cursor ();
    }

    void
    color_to_rgba (Gdk::Color const& color,
                   double &          r,
                   double &          g,
                   double &          b,
                   double &          a)
    {
      r = color.get_red ()   / 65535.0;
      g = color.get_green () / 65535.0;
      b = color.get_blue ()  / 65535.0;
      a = 1.0;
    }

    double
    screen_get_resolution (Glib::RefPtr<Gdk::Screen> const& screen)
    {
      // NOTE: this assumes the width and height resolutions are
      // equal. This is only true for video modes with aspect ratios that
      // match the physical screen area ratio e.g. 1024x768 for 4:3
      // screens - descender

      return std::ceil (screen_get_y_resolution (screen));
    }

    double
    screen_get_x_resolution (Glib::RefPtr<Gdk::Screen> const& screen)
    {
      return static_cast<double> (screen->get_width ()) / mm_to_inch (screen->get_height_mm ());
    }

    double
    screen_get_y_resolution (Glib::RefPtr<Gdk::Screen> const& screen)
    {
      return static_cast<double> (screen->get_height ()) / mm_to_inch (screen->get_height_mm ());
    }

    void
    style_save (GtkWidget *widget)
    {
      _style = gtk_style_copy (widget->style);
      GtkStyle *src = widget->style;

      g_slist_foreach (_style->icon_factories, (GFunc) g_object_unref, NULL);
      g_slist_free (_style->icon_factories);
      _style->icon_factories = g_slist_copy (src->icon_factories);
      g_slist_foreach (_style->icon_factories, (GFunc) g_object_ref, NULL);

      g_object_ref (_style);
    }

    GtkStyle*
    style_get ()
    {
        return _style;
    }

    // NOTE: gtkmm has no binding for gdk_screen_get_rgba_colormap()
    Glib::RefPtr<Gdk::Colormap>
    get_rgba_colormap ()
    {
        return _colormap;
    }

    void
    check_alpha ()
    {
      _colormap = Glib::RefPtr<Gdk::Colormap>(0);

      if (mcs->key_get<bool>("bmp","force-rgba-enable"))
      {
        _has_alpha = true;
      }
      else
      {
        _has_alpha = gdk_screen_is_composited (gdk_screen_get_default() );
      }

      GdkColormap * colormap = gdk_screen_get_rgba_colormap (gdk_screen_get_default());
      if (colormap)
      {
        _colormap = Glib::wrap (colormap, true);
      }
    }

    bool
    has_alpha ()
    {
      return _has_alpha;
    }

    Cairo::RefPtr<Cairo::ImageSurface>
    cairo_image_surface_from_pixbuf (Glib::RefPtr<Gdk::Pixbuf> pixbuf)
    {
      Cairo::RefPtr< ::Cairo::ImageSurface> surface =
        Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, pixbuf->get_width(), pixbuf->get_height());

      Cairo::RefPtr< ::Cairo::Context> cr =
        Cairo::Context::create (surface);

      cr->set_operator (Cairo::OPERATOR_SOURCE);
      Gdk::Cairo::set_source_pixbuf (cr, pixbuf, 0, 0);
      cr->rectangle (0, 0, pixbuf->get_width(), pixbuf->get_height());
      cr->fill ();
      return surface;
    }

    void
    cairo_rounded_rect (Cairo::RefPtr<Cairo::Context>& cr,
                        double                         x,
                        double                         y,
                        double                         width,
                        double                         height,
                        double                         radius)
    {
      g_return_if_fail (width > 0 && height > 0 && radius >= 0);

      double edge_length_x = width - radius * 2;
      double edge_length_y = height - radius * 2;

      g_return_if_fail (edge_length_x >= 0 && edge_length_y >= 0);

      cr->move_to (x + radius, y);
      cr->rel_line_to (edge_length_x, 0);
      cr->rel_curve_to (radius, 0, radius, radius, radius, radius);
      cr->rel_line_to (0, edge_length_y);
      cr->rel_curve_to (0, radius, -radius, radius, -radius, radius);
      cr->rel_line_to (-edge_length_x, 0);
      cr->rel_curve_to (-radius, 0, -radius, -radius, -radius, -radius);
      cr->rel_line_to (0, -edge_length_y);
      cr->rel_curve_to (0, -radius, radius, -radius, radius, -radius);
    }

    Cairo::RefPtr<Cairo::ImageSurface>
    cairo_image_surface_scale (Cairo::RefPtr<Cairo::ImageSurface> source, double width, double height)
    {
      Cairo::RefPtr< ::Cairo::ImageSurface> dest = Cairo::ImageSurface::create (source->get_format(), int (width), int (height));
      Cairo::RefPtr< ::Cairo::Context> cr = Cairo::Context::create (dest);

      cr->set_operator (Cairo::OPERATOR_SOURCE); 
      cr->scale (width / source->get_width(), height / source->get_height());
      cr->set_source (source, 0., 0.);
      cr->rectangle (0., 0., source->get_width(), source->get_height());
      cr->fill ();
      return dest;
    }

    Cairo::RefPtr<Cairo::ImageSurface>
    cairo_image_surface_round (Cairo::RefPtr<Cairo::ImageSurface> source, double radius)
    {
      Cairo::RefPtr< ::Cairo::ImageSurface> dest = Cairo::ImageSurface::create (source->get_format(), source->get_width(), source->get_height()); 
      Cairo::RefPtr< ::Cairo::Context> cr = Cairo::Context::create (dest);

      cr->set_operator (Cairo::OPERATOR_SOURCE); 
      cairo_rounded_rect (cr, 0, 0, source->get_width(), source->get_height(), radius);

      cr->set_source (source, 0., 0.);
      cr->fill_preserve ();

      cr->set_source_rgba (0., 0., 0., .5);
      cr->set_line_width (.7);
      cr->stroke ();

      return dest;
    }

    void 
    cairo_image_surface_border (Cairo::RefPtr<Cairo::ImageSurface> & source, double width)
    {
      Cairo::RefPtr< ::Cairo::Context> cr = Cairo::Context::create (source);
      cr->set_operator (Cairo::OPERATOR_SOURCE); 
      cr->rectangle (0, 0, source->get_width(), source->get_height());
      cr->set_source_rgba (0., 0., 0., 1.);
      cr->set_line_width (width);
      cr->stroke ();
    }

  } // namespace Util
} // namespace Bmp
