/*
 * Copyright (c) 2005 Jean-François Wauthy <pollux@xfce.org>
 * 
 * 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 Library 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.
 */


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

#include <gmodule.h>

#include <cups/ipp.h>
#include <cups/ppd.h>
#include <cups/cups.h>
#include <cups/language.h>

#include <libxfcegui4/libxfcegui4.h>
#include <libxfce4util/libxfce4util.h>

#include <libxfprint/libxfprint.h>


/* prototypes */
static void action_set_default_printer_cb (GtkAction *action, PrinterListWindow *dlg);

/* globals */
G_MODULE_EXPORT const gchar name[] = "CUPS";
G_MODULE_EXPORT const gchar description[] = N_("CUPS printing system support for Xfprint");
G_MODULE_EXPORT const gchar version[] = VERSION;
G_MODULE_EXPORT const gchar author[] = "Jean-François Wauthy";
G_MODULE_EXPORT const gchar homepage[] = "http://www.xfce.org";

static GtkActionEntry printer_list_action_entries[] = {
  {"set-default-printer", GTK_STOCK_PRINT, N_("Set as default printer"), NULL,
   N_("Set as default CUPS printer"), G_CALLBACK (action_set_default_printer_cb),},
  {"printer-properties", GTK_STOCK_PROPERTIES, N_("Properties"), NULL, 
   N_("Show printer properties"), NULL,},
};


/*******************************************/
/* CUPS requests                           */
/* (widely inspired from the libgnomecups) */
/*******************************************/
static const char *
cups_password_cb (const char *prompt)
{
  int response;
  static gchar *password = NULL;
  gchar *username = NULL;

  GtkWidget *dialog;
  GtkWidget *label_username;
  GtkWidget *label_password;
  GtkWidget *entry_username;
  GtkWidget *entry_password;
  GtkWidget *hbox;

  g_free (password);

  /* show dialog prompting for username and password */
  dialog =
    gtk_dialog_new_with_buttons (_("Password"), NULL,
                                 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
                                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
  hbox = gtk_hbox_new (TRUE, 5);
  label_username = gtk_label_new (_("User :"));
  entry_username = gtk_entry_new_with_max_length (255);
  gtk_box_pack_start (GTK_BOX (hbox), label_username, FALSE, FALSE, 5);
  gtk_box_pack_start (GTK_BOX (hbox), entry_username, FALSE, FALSE, 5);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox, TRUE, FALSE, 5);

  hbox = gtk_hbox_new (TRUE, 5);
  label_password = gtk_label_new (_("Password :"));
  entry_password = gtk_entry_new_with_max_length (255);
  gtk_entry_set_visibility (GTK_ENTRY (entry_password), FALSE);
  gtk_box_pack_start (GTK_BOX (hbox), label_password, FALSE, FALSE, 5);
  gtk_box_pack_start (GTK_BOX (hbox), entry_password, FALSE, FALSE, 5);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox, TRUE, FALSE, 5);

  gtk_widget_show_all (dialog);

  while (TRUE) {
    gtk_entry_set_text (GTK_ENTRY (entry_username), g_get_user_name ());
    gtk_widget_grab_focus (entry_password);
    response = gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_widget_hide (dialog);

    if (response == GTK_RESPONSE_ACCEPT) {
      /* user entered a password */
      username = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry_username)));
      if (strlen (username) > 0) {
        /* and an username */
        password = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry_password)));
        cupsSetUser (username);

        g_free (username);
        return password;
      }
      else {
        g_free (username);
        xfce_err (_("You have to provide an username !"));
      }

    }
    else {
      /* cancel pressed or window closed */
      return NULL;
    }
  }
}

static ipp_t *
cups_request_new (int operation_id)
{
  ipp_t *request;
  cups_lang_t *language;

  language = cupsLangDefault ();
  request = ippNew ();
  request->request.op.operation_id = operation_id;
  request->request.op.request_id = 1;

  ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, "attributes-charset", NULL, "utf-8");

  ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, "attributes-natural-language", NULL, language->language);
  cupsLangFree (language);

  return request;
}

static ipp_t *
cups_request_new_for_printer (int operation_id, const gchar * printer)
{
  ipp_t *request;
  gchar *server = NULL;
  gchar *printer_uri = NULL;

  g_return_val_if_fail (printer, NULL);

  server = g_strdup (cupsServer ());
  if ((strlen (server) > 0) && server[0] == '/') {
    g_free (server);
    server = g_strdup ("localhost");
  }

  printer_uri = g_strdup_printf ("ipp://%s/printers/%s", server, printer);
  request = cups_request_new (operation_id);

  ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, printer_uri);

  g_free (printer_uri);
  g_free (server);

  return request;
}

static ipp_t *
cups_request_execute (ipp_t * request, const gchar * path)
{
  http_t *HTTP = NULL;
  ipp_status_t status;
  gchar *server = NULL;

  cupsSetPasswordCB (cups_password_cb);

  server = g_strdup (cupsServer ());
  if ((strlen (server) > 0) && server[0] == '/') {
    g_free (server);
    server = g_strdup ("localhost");
  }

  HTTP = httpConnectEncrypt (server, ippPort (), cupsEncryption ());

  if (!HTTP) {
    ippDelete (request);
    g_warning ("Unable to connect CUPS server");
    return NULL;
  }

  g_free (server);

  request = cupsDoRequest (HTTP, request, path);
  httpClose (HTTP);
  status = cupsLastError ();

  if (!request) {
    g_warning ("CUPS server couldn't execute request");
    return NULL;
  }

  if (status > IPP_OK_CONFLICT)
    g_warning ("failed request with error: %s", ippErrorString (status));

  return request;
}

/* Returns the printer list  */
/* the returned list must be freed */
G_MODULE_EXPORT GList *
get_printers ()
{
  GList *list = NULL;
  int i;
  cups_dest_t *dests;
  int num_dests;

  num_dests = cupsGetDests (&dests);    /* Printer names and options */

  if (num_dests > 0) {
    for (i = 0; i < num_dests; i++) {
      Printer *printer;
      ipp_t *request;
      ipp_attribute_t *attr;

      printer = g_new0 (Printer, 1);

      if (dests[i].instance)
        printer->name = g_strdup_printf ("%s/%s", dests[i].name, dests[i].instance);
      else
        printer->name = g_strdup (dests[i].name);

      list = g_list_append (list, printer);

      request = cups_request_new_for_printer (IPP_GET_PRINTER_ATTRIBUTES, dests[i].name);
      request = cups_request_execute (request, "/printers/");

      if (!request)
        continue;
      if (request->state == IPP_ERROR || request->state == IPP_IDLE) {
        ippDelete (request);
        continue;
      }

      attr = ippFindAttribute (request, "printer-info", IPP_TAG_TEXT);
      if (!attr || strlen (attr->values[0].string.text) == 0) {
        attr = ippFindAttribute (request, "printer-make-and-model", IPP_TAG_TEXT);
        if (attr)
          printer->alias = g_strdup (attr->values[0].string.text);
        else
          printer->alias = g_strdup ("");
      }
      else
        printer->alias = g_strdup (attr->values[0].string.text);

      attr = ippFindAttribute (request, "printer-type", IPP_TAG_ENUM);
      if (attr && (attr->values[0].integer & CUPS_PRINTER_CLASS))
        printer->type = PRINTER_TYPE_CLASS;
      else
        printer->type = PRINTER_TYPE_PRINTER;

      ippDelete (request);
    }
  }
  else
    g_warning ("no printer in the list, may be the CUPS server isn't running or you haven't configured any printer");

  cupsFreeDests (num_dests, dests);

  return list;
}

G_MODULE_EXPORT Printer *
get_default_printer ()
{
  /* TODO : find a way to work fine with CupsGetDefault and set_default */
  Printer *printer = NULL;
  int j;
  int num_dests = 0;
  GList *printers = get_printers ();

  cups_dest_t *dests = NULL;

  num_dests = cupsGetDests (&dests);
  for (j = 0; j < num_dests; j++)
    if (dests[j].is_default == 1)
      printer = printer_lookup_byname (printers, dests[j].name);
  cupsSetDests (num_dests, dests);
  cupsFreeDests (num_dests, dests);

  return printer;
}

G_MODULE_EXPORT gint
get_printer_state (const gchar * printer)
{
  ipp_t *request;
  gint state = PRINTER_STATE_UNKNOWN;

  request = cups_request_new_for_printer (IPP_GET_PRINTER_ATTRIBUTES, printer);
  request = cups_request_execute (request, "/printers/");

  if (request) {
    ipp_attribute_t *attr = ippFindAttribute (request, "printer-state",
                                              IPP_TAG_ENUM);
    if (attr)
      switch (attr->values[0].integer) {
      case IPP_PRINTER_IDLE:
        state = PRINTER_STATE_IDLE;
        break;
      case IPP_PRINTER_PROCESSING:
        state = PRINTER_STATE_PROCESSING;
        break;
      case IPP_PRINTER_STOPPED:
        state = PRINTER_STATE_STOPPED;
        break;
      }
  }

  ippDelete (request);

  return state;
}

G_MODULE_EXPORT gint
get_printer_jobs_count (const gchar * printer)
{
  gint num_jobs;
  cups_job_t *jobs;

  num_jobs = cupsGetJobs (&jobs, printer, 0, 0);

  cupsFreeJobs (num_jobs, jobs);

  return num_jobs;
}

/*-----------------------------*/
/* printer list customizations */
/*-----------------------------*/
G_MODULE_EXPORT void
customize_printer_list_window (PrinterListWindow *win)
{
  GtkUIManager *ui_manager; 
  GtkActionGroup *action_group;
  GError *error = NULL;
  
  ui_manager = printer_list_window_get_ui_manager (win);

  action_group = gtk_action_group_new ("printer-list-cups");
  gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
  gtk_action_group_add_actions (action_group, printer_list_action_entries,
				G_N_ELEMENTS (printer_list_action_entries), GTK_WIDGET (win));

  gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);

  if (gtk_ui_manager_add_ui_from_string (ui_manager, 
					 "<ui>"
					 "<menubar name=\"main-menu\">"
					 " <menu action=\"printer-menu\">"
					 "  <placeholder name=\"printer-menu-additions\">"
					 //					 "   <menuitem action=\"printer-properties\"/>"
					 "   <menuitem action=\"set-default-printer\"/>"
					 "  </placeholder>"
					 " </menu>"
					 "</menubar>"
					 "</ui>"
					 , -1, &error) == 0) {
    g_warning ("Unable to build the CUPS user interface correctly : %s", error->message);
    g_error_free (error);
  }
  gtk_ui_manager_ensure_update (ui_manager);
}

static void
action_set_default_printer_cb (GtkAction *action, PrinterListWindow *win)
{
  gchar *printer = NULL;
  int j;
  int num_dests = 0;
  cups_dest_t *dests = NULL;

  printer = printer_list_window_get_selected_printer (win);

  /* code from XPP */
  num_dests = cupsGetDests (&dests);
  for (j = 0; j < num_dests; j++)
    if (strcmp (dests[j].name, printer) == 0)
      dests[j].is_default = 1;
    else
      dests[j].is_default = 0;
  cupsSetDests (num_dests, dests);
  cupsFreeDests (num_dests, dests);

  g_free (printer);
}

/*-----------------------------*/
/* printer queue customization */
/*-----------------------------*/
G_MODULE_EXPORT void
customize_printer_queue_window (PrinterQueueWindow *win)
{
}

/****************/
/* Queue dialog */
/****************/

/* Return the job list */
/* the returned list must be freed */
G_MODULE_EXPORT GList *
get_jobs (const gchar * printer)
{
  int i;
  int num_jobs;
  cups_job_t *jobs;
  GList *list = NULL;

  /* Obtain job list from cups server */
  num_jobs = cupsGetJobs (&jobs, printer, 0, 0);

  for (i = 0; i < num_jobs; i++) {
    Job *job;
    gchar string_creation_time[10] = "";
    gchar string_processing_time[10] = "";
    struct tm *local_time;

    job = g_new0 (Job, 1);

    job->name = g_strdup (jobs[i].title);
    job->id = jobs[i].id;
    job->user = g_strdup (jobs[i].user);
    job->state = jobs[i].state != IPP_JOB_PENDING ? JOB_STATE_PRINTING : JOB_STATE_PENDING;
    job->size = jobs[i].size;
    job->priority = jobs[i].priority;
    local_time = localtime (&(jobs[i].creation_time));
    strftime (string_creation_time, sizeof (string_creation_time), "%H:%M:%S", local_time);
    job->creation_time = g_strdup (string_creation_time);
    if (jobs[i].state == IPP_JOB_PROCESSING) {
      local_time = localtime (&(jobs[i].processing_time));
      strftime (string_processing_time, sizeof (string_processing_time), "%H:%M:%S", local_time);
      job->processing_time = g_strdup (string_processing_time);
    }

    list = g_list_append (list, job);

  }

  cupsFreeJobs (num_jobs, jobs);

  return list;
}

/* Remove job from printer_name queue */
G_MODULE_EXPORT gboolean
remove_job (const gchar * printer, gint id)
{
  if (cupsCancelJob (printer, id) == 0) {
    xfce_err (ippErrorString (cupsLastError ()));
    return FALSE;
  }

  return TRUE;
}

/* /\* print test page *\/ */
/* static void */
/* queuedlg_print_test_page_cb (GtkWidget * widget, QueueDlg * dlg) */
/* { */
/*   gchar *testpage_file = NULL; */

/*   testpage_file = g_build_filename (CUPS_DATA_DIR, "data/testprint.ps", NULL); */
/*   DBG ("test page : %s", testpage_file); */

/*   if (cupsPrintFile (dlg->printer_name, testpage_file, _("Test page"), 0, NULL)) { */
/*     gboolean (*refresh_fct) (QueueDlg *); */

/*     refresh_fct = dlg->refresh_queue_fct; */

/*     refresh_fct (dlg); */
/*     xfce_info (_("Test page sent to printer.")); */
/*   } */
/*   else */
/*     xfce_err (_("An error occured while sending test page to printer : %s"), ippErrorString (cupsLastError ())); */

/*   g_free (testpage_file); */
/* } */

/* set current printer as default */
/* static void */
/* queuedlg_set_printer_as_default_cb (GtkWidget * widget, QueueDlg * dlg) */
/* { */
/*   //  if (set_default_printer (dlg->printer_name)) */
/*   //  xfce_info (_("Printer set as default")); */
/* } */

/* pause/resume printer */
/* static void */
/* queuedlg_pause_printer_cb (GtkWidget * widget, QueueDlg * dlg) */
/* { */
/*   ipp_t *request; */

/*   request = cups_request_new_for_printer (IPP_PAUSE_PRINTER, dlg->printer_name); */
/*   request = cups_request_execute (request, "/admin/"); */

/*   if (request && request->state >= IPP_IDLE) { */
/*     gtk_label_set_text_with_mnemonic (GTK_LABEL (GTK_BIN (widget)->child), _("_Resume printer")); */

/*     g_signal_handlers_disconnect_by_func (G_OBJECT (widget), G_CALLBACK (queuedlg_pause_printer_cb), dlg); */
/*     g_signal_connect (G_OBJECT (widget), "activate", G_CALLBACK (queuedlg_resume_printer_cb), dlg); */
/*   } */

/*   ippDelete (request); */
/* } */

/* static void */
/* queuedlg_resume_printer_cb (GtkWidget * widget, QueueDlg * dlg) */
/* { */
/*   ipp_t *request; */

/*   request = cups_request_new_for_printer (IPP_RESUME_PRINTER, dlg->printer_name); */
/*   request = cups_request_execute (request, "/admin/"); */

/*   if (request && request->state >= IPP_IDLE) { */
/*     gtk_label_set_text_with_mnemonic (GTK_LABEL (GTK_BIN (widget)->child), _("_Pause printer")); */

/*     g_signal_handlers_disconnect_by_func (G_OBJECT (widget), G_CALLBACK (queuedlg_resume_printer_cb), dlg); */
/*     g_signal_connect (G_OBJECT (widget), "activate", G_CALLBACK (queuedlg_pause_printer_cb), dlg); */
/*   } */

/*   ippDelete (request); */
/* } */

/************/
/* Printing */
/************/
G_MODULE_EXPORT gboolean
print_file (const gchar * printer, const gchar * original_name, const gchar * file, gboolean remove_file)
{
  gchar *printer_name = NULL;
  gchar *printer_instance = NULL;
  int i, length;
  int num_dests;
  cups_dest_t *dests;
  cups_dest_t *dest;
  gboolean ret = FALSE;

  g_return_val_if_fail (printer != NULL && strlen (printer) > 0, FALSE);  
  g_return_val_if_fail (file != NULL && strlen (file) > 0, FALSE);  
  g_return_val_if_fail (original_name != NULL, FALSE);

  /* extract printer name and instance name */
  length = strlen (printer);

  for (i = length; i >= 0 && printer[i] != '/'; i--);
  printer_name = g_strndup (printer, i <= 0 ? length : i);
  if (i > 0)
    printer_instance = g_strndup (&printer[i + 1], length - i - 1);
  
  num_dests = cupsGetDests (&dests);
  dest = cupsGetDest (printer_name, printer_instance, num_dests, dests);

  if (cupsPrintFile (printer_name, file, original_name, dest->num_options, dest->options)) {
    ret = TRUE;
  }
  else {
    xfce_err (ippErrorString (cupsLastError ()));
  }

  cupsFreeDests (num_dests, dests);
  g_free (printer_name);
  g_free (printer_instance);
  if (remove_file)
    unlink (file);

  return ret;
}
