/* eLectrix - a pdf viewer
 * Copyright (C) 2010, 2011 Martin Linder <mali2297@users.sf.net>
 * 
 * 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, see <http://www.gnu.org/licenses/gpl-2.0.html>.
 */
#include <libspectre/spectre.h>
#include "e6x-common.h"
#include "e6x-ps-document.h"

#define E6X_PS_DOCUMENT_GET_PRIVATE(o) \
   (G_TYPE_INSTANCE_GET_PRIVATE ((o), E6X_TYPE_PS_DOCUMENT, E6xPsDocumentPrivate))

struct _E6xPsDocumentPrivate
{
  SpectreDocument *spectredoc;
};

static const gchar *default_info[] = 
{
  "format", N_("Format"), "",
  "title", N_("Title"), "",
  "for", N_("For"), "",
  "creator", N_("Creator"), "",
  "creation-date", N_("Creation date"), "",
  NULL
};

/* Standard GObject methods */
G_DEFINE_TYPE (E6xPsDocument, e6x_ps_document, E6X_TYPE_DOCUMENT)
static void e6x_ps_document_finalize (GObject *object);
static void e6x_ps_document_dispose (GObject *object);

/* Implementations of E6xDocument virtual methods */
static gboolean e6x_ps_document_reload (E6xDocument *doc,
                                        GError **error);
static gboolean e6x_ps_document_save_copy (E6xDocument *doc,
                                           const gchar *filename,
                                           GError **error);
static void e6x_ps_document_get_page_size (E6xDocument *doc,
                                           gdouble *width,
                                           gdouble *height);
static cairo_surface_t *e6x_ps_document_render_page (E6xDocument *doc);


GObject *
e6x_ps_document_new (const gchar *filename,
                     GError **error)
{
  GObject *object = g_object_new (E6X_TYPE_PS_DOCUMENT, NULL);
  E6xDocument *doc = E6X_DOCUMENT (object);
  
  doc->filename = g_strdup (filename);
  gboolean is_success = e6x_document_reload (doc, error);
  
  if (G_UNLIKELY (!is_success))
  {
    g_object_unref (object);
    object = NULL;
  }
  
  return object;
}


static void 
e6x_ps_document_class_init (E6xPsDocumentClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  E6xDocumentClass *doc_class = E6X_DOCUMENT_CLASS (klass);

  g_type_class_add_private (klass, sizeof (E6xPsDocumentPrivate));

  object_class->dispose = e6x_ps_document_dispose;
  object_class->finalize = e6x_ps_document_finalize;
  
  doc_class->reload = &e6x_ps_document_reload;
  doc_class->save_copy = &e6x_ps_document_save_copy;
  doc_class->get_page_size = &e6x_ps_document_get_page_size;
  doc_class->render_page = &e6x_ps_document_render_page;
}


static void 
e6x_ps_document_init (E6xPsDocument *doc)
{
  E6xPsDocumentPrivate *priv = E6X_PS_DOCUMENT_GET_PRIVATE (doc);
  
  priv->spectredoc = NULL;
  doc->priv = priv;
}

static void 
e6x_ps_document_finalize (GObject *object)
{
  G_OBJECT_CLASS (e6x_ps_document_parent_class)->finalize (object);
}


static void 
e6x_ps_document_dispose (GObject *object)
{
  E6xPsDocumentPrivate *priv = E6X_PS_DOCUMENT (object)->priv;

  if (priv->spectredoc)
  {
    spectre_document_free (priv->spectredoc);
    priv->spectredoc = NULL;
  }

  G_OBJECT_CLASS (e6x_ps_document_parent_class)->dispose (object);
}


static gboolean 
e6x_ps_document_reload (E6xDocument *doc,
                        GError **error)
{
  E6xPsDocumentPrivate *priv = E6X_PS_DOCUMENT (doc)->priv;
  SpectreStatus status = 0;
  const gchar *string = NULL;
  
  if (doc->info)
  {
    g_strfreev (doc->info);
    doc->info = NULL;
  }
  if (priv->spectredoc)
  {
    spectre_document_free (priv->spectredoc);
    priv->spectredoc = NULL;
  }
  if (G_UNLIKELY (!doc->filename))
  {
    return FALSE;
  }

  priv->spectredoc = spectre_document_new ();
  spectre_document_load (priv->spectredoc, doc->filename);
  status = spectre_document_status (priv->spectredoc);
  
  if (G_UNLIKELY (status))
  {
    g_set_error_literal (error,
                         G_FILE_ERROR,
                         G_FILE_ERROR_FAILED,
                         spectre_status_to_string (status));
    spectre_document_free (priv->spectredoc);
    priv->spectredoc = NULL;
    g_free (doc->filename);
    doc->filename = NULL;
    return FALSE;
  }
  
  doc->n_pages = spectre_document_get_n_pages (priv->spectredoc);
  doc->page_no = CLAMP (doc->page_no, 1, doc->n_pages);
  doc->info = g_strdupv ((gchar **) default_info);
  
  string = spectre_document_get_format (priv->spectredoc);
  if (string)
  {
    g_free (doc->info[2]);
    doc->info[2] = g_strdup (string);
  }
  
  string = spectre_document_get_title (priv->spectredoc);
  if (string)
  {
    g_free (doc->info[5]);
    doc->info[5] = g_strdup (string);
  }
  
  string = spectre_document_get_for (priv->spectredoc);
  if (string)
  {
    g_free (doc->info[8]);
    doc->info[8] = g_strdup (string);
  }
  
  string = spectre_document_get_creator (priv->spectredoc);
  if (string)
  {
    g_free (doc->info[11]);
    doc->info[11] = g_strdup (string);
  }

  string = spectre_document_get_creation_date (priv->spectredoc);
  if (string)
  {
    g_free (doc->info[14]);
    doc->info[14] = g_strdup (string);
  }

  if (*(doc->info[5]) != '\0')
  {
    doc->title = g_strdup (doc->info[5]);
  }
  else
  {
    doc->title = g_path_get_basename (doc->filename);
  }

  return TRUE;
}


static 
gboolean e6x_ps_document_save_copy (E6xDocument *doc,
                                    const gchar *filename,
                                    GError **error)
{
  E6xPsDocumentPrivate *priv = E6X_PS_DOCUMENT (doc)->priv;
  SpectreStatus status = 0;
  
  spectre_document_save (priv->spectredoc, filename);
  
  status = spectre_document_status (priv->spectredoc);
  if (G_UNLIKELY (status != 0))
  {
    g_set_error_literal (error,
                         G_FILE_ERROR,
                         G_FILE_ERROR_FAILED,
                         spectre_status_to_string (status));
    return FALSE;
  }
  
  return TRUE;
}


static void 
e6x_ps_document_get_page_size (E6xDocument *doc,
                               gdouble *width,
                               gdouble *height)
{
  E6xPsDocumentPrivate *priv = E6X_PS_DOCUMENT_GET_PRIVATE (doc);
  SpectrePage *page = NULL;
  gint ps_width = 0, ps_height = 0;

  g_return_if_fail (priv->spectredoc);
  
  page = spectre_document_get_page (priv->spectredoc, doc->page_no - 1);
  g_return_if_fail (page != NULL);

  spectre_page_get_size (page, &ps_width, &ps_height);
  *width = (gdouble) (ps_width + 0.5);
  *height = (gdouble) (ps_height + 0.5);

  spectre_page_free (page);
}


static cairo_surface_t *
e6x_ps_document_render_page (E6xDocument *doc)
{
  E6xPsDocumentPrivate *priv = E6X_PS_DOCUMENT (doc)->priv;
  SpectrePage *page;
  SpectreStatus status;
  SpectreRenderContext *context;
  gint ps_width, ps_height, surface_width, surface_height, row_length;
  gdouble page_width = 0.0, page_height = 0.0;
  guchar *data = NULL;
  cairo_surface_t *surface;
  static const cairo_user_data_key_t key;
    
  g_return_val_if_fail (priv->spectredoc, NULL);
  
  page = spectre_document_get_page (priv->spectredoc, doc->page_no - 1);
  spectre_page_get_size (page, &ps_width, &ps_height);
  page_width = (gdouble) (ps_width + 0.5);
  page_height = (gdouble) (ps_height + 0.5);
  
  context = spectre_render_context_new ();
  spectre_render_context_set_scale (context, doc->scale, doc->scale);
  spectre_render_context_set_rotation (context, doc->angle);
  spectre_page_render (page, context, &data, &row_length);
  status = spectre_page_status (page);
  spectre_render_context_free (context);
  spectre_page_free (page);
  
  if (G_UNLIKELY (!data))
    return NULL;
  
  if (G_UNLIKELY (status))
  {
    g_print ("%s\n", spectre_status_to_string (status));
    g_free (data);
    
    return NULL;
  }
  
  if (doc->angle == 90 || doc->angle == 270)
  {
    surface_width = page_height * doc->scale;
    surface_height = page_width * doc->scale;
  } 
  else
  {
    surface_width = page_width * doc->scale;
    surface_height = page_height * doc->scale;
  }
  
  surface = cairo_image_surface_create_for_data (data,
                                                 CAIRO_FORMAT_RGB24,
                                                 surface_width, 
                                                 surface_height,
                                                 row_length);
  cairo_surface_set_user_data (surface, &key,
                               data, (cairo_destroy_func_t) g_free);

  return surface;
}

