/* cm-netkey.c  -  Widget to show information about a Netkey card.
 * Copyright (C) 2009 g10 Code GmbH
 *
 * This file is part of GPA.
 *
 * GPA is free software; you can redistribute and/or modify this part
 * of GPA under the terms of either
 *
 *   - the GNU Lesser General Public License as published by the Free
 *     Software Foundation; either version 3 of the License, or (at
 *     your option) any later version.
 *
 * or
 *
 *   - 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.
 *
 * or both in parallel, as here.
 *
 * GPA 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/>.
 */

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "gpa.h"
#include "gtktools.h"
#include "convert.h"
#include "gpa-key-details.h"

#include "cm-object.h"
#include "cm-netkey.h"




/* Identifiers for the entry fields.  */
enum
  {
    ENTRY_SERIALNO,
    ENTRY_NKS_VERSION,

    ENTRY_PIN_RETRYCOUNTER,
    ENTRY_PUK_RETRYCOUNTER,
    ENTRY_SIGG_PIN_RETRYCOUNTER,
    ENTRY_SIGG_PUK_RETRYCOUNTER,

    ENTRY_LAST
  };


/* A structure for PIN information.  */
struct pininfo_s
{
  int valid;            /* Valid information.  */
  int nullpin;          /* The NullPIN is activ.  */
  int blocked;          /* The PIN is blocked.  */
  int nopin;            /* No such PIN.  */
  int tries_left;       /* How many verification tries are left.  */
};



/* Object's class definition.  */
struct _GpaCMNetkeyClass
{
  GpaCMObjectClass parent_class;
};


/* Object definition.  */
struct _GpaCMNetkey
{
  GpaCMObject  parent_instance;

  GtkWidget *warning_frame;  /* The frame used to display warnings etc.  */

  GtkWidget *keys_frame;     /* The frame containing the keys.  */

  GtkWidget *entries[ENTRY_LAST];

  GtkWidget *change_pin_btn;      /* The button to change the PIN for NKS.  */
  GtkWidget *change_puk_btn;      /* The button to change the PUK for NKS.  */
  GtkWidget *change_sigg_pin_btn; /* The button to change the PIN for SigG. */
  GtkWidget *change_sigg_puk_btn; /* The button to change the PUK for SigG. */

  /* Information about the PINs. */
  struct pininfo_s pininfo[4];


  int  reloading;   /* Sentinel to avoid recursive reloads.  */

};

/* The parent class.  */
static GObjectClass *parent_class;



/* Local prototypes */
static void learn_keys_clicked_cb (GtkButton *widget, void *user_data);
static void gpa_cm_netkey_finalize (GObject *object);



/************************************************************
 *******************   Implementation   *********************
 ************************************************************/

/* Clears the info contained in the card widget. */
static void
clear_card_data (GpaCMNetkey *card)
{
  int idx;

  for (idx=0; idx < ENTRY_LAST; idx++)
    if (GTK_IS_LABEL (card->entries[idx]))
      gtk_label_set_text (GTK_LABEL (card->entries[idx]), "");
    else
      gtk_entry_set_text (GTK_ENTRY (card->entries[idx]), "");
}


/* Put the PIN information into the field with ENTRY_ID.  If BUTTON is
   not NULL its sensitivity is set as well. */
static void
update_entry_retry_counter (GpaCMNetkey *card, int entry_id,
                            struct pininfo_s *info, int any_isnull,
                            int is_puk, GtkWidget *button)
{
  char numbuf[50];
  const char *string;

  if (!info->valid)
    string = _("unknown");
  else if (info->nullpin)
    string = "NullPIN";
  else if (info->blocked)
    string = _("blocked");
  else if (info->nopin)
    string = "n/a";
  else
    {
      snprintf (numbuf, sizeof numbuf, "%d", info->tries_left);
      string = numbuf;
    }
  gtk_label_set_text (GTK_LABEL (card->entries[entry_id]), string);
  if (button)
    {
      gtk_button_set_label (GTK_BUTTON (button),
                            (info->valid && !info->nullpin
                             && (info->blocked || !info->tries_left))
                            ? (is_puk? _("Reset PUK") : _("Reset PIN"))
                            : (is_puk? _("Change PUK"): _("Change PIN")));
      gtk_widget_set_sensitive (button,
                                (info->valid && !any_isnull
                                 && !info->nullpin && !info->blocked
                                 && !info->nopin));
    }
}



/* Update the CHV status fields.  This function may modify STRING.  */
static void
update_entry_chv_status (GpaCMNetkey *card, int entry_id, char *string)
{
  struct {
    int info_idx;
    int is_puk;
    int entry_id;
    GtkWidget *widget;
  } tbl[] =
    {
      {  0, 0, ENTRY_PIN_RETRYCOUNTER },
      {  1, 1, ENTRY_PUK_RETRYCOUNTER  },
      {  2, 0, ENTRY_SIGG_PIN_RETRYCOUNTER },
      {  3, 1, ENTRY_SIGG_PUK_RETRYCOUNTER },
      { -1, 0 }
    };
  int idx;
  int any_isnull = 0;

  tbl[0].widget = card->change_pin_btn;
  tbl[1].widget = card->change_puk_btn;
  tbl[2].widget = card->change_sigg_pin_btn;
  tbl[3].widget = card->change_sigg_puk_btn;

  (void)entry_id; /* Not used.  */

  for (idx=0; idx < DIM (card->pininfo); idx++)
    memset (&card->pininfo[idx], 0, sizeof card->pininfo[0]);

  while (spacep (string))
    string++;
  for (idx=0; *string && idx < DIM (card->pininfo); idx++)
    {
      int value = atoi (string);
      while (*string && !spacep (string))
        string++;
      while (spacep (string))
        string++;

      card->pininfo[idx].valid = 1;
      if (value >= 0)
        card->pininfo[idx].tries_left = value;
      else if (value == -4)
        {
          card->pininfo[idx].nullpin = 1;
          any_isnull = 1;
        }
      else if (value == -3)
        card->pininfo[idx].blocked = 1;
      else if (value == -2)
        card->pininfo[idx].nopin = 1;
      else
        card->pininfo[idx].valid = 0;
    }

  for (idx=0; tbl[idx].info_idx != -1; idx++)
    if (tbl[idx].info_idx < DIM (card->pininfo))
      update_entry_retry_counter (card, tbl[idx].entry_id,
                                  &card->pininfo[tbl[idx].info_idx],
                                  any_isnull,
                                  tbl[idx].is_puk, tbl[idx].widget);

  gtk_widget_set_no_show_all (card->warning_frame, FALSE);
  if (any_isnull)
    gtk_widget_show_all (card->warning_frame);
  else
    {
      gtk_widget_hide (card->warning_frame);
      gtk_widget_set_no_show_all (card->warning_frame, TRUE);
    }
}


/* Structure form comminucation between reload_more_data and
   reload_more_data_cb.  */
struct reload_more_data_parm
{
  GpaCMNetkey *card; /* self  */
  gpgme_ctx_t ctx;   /* A prepared context for relaod_more_data_cb.  */
  int any_unknown;   /* Set if at least one key is not known.  */
};


/* Helper for relaod_more_data.  This is actually an Assuan status
   callback  */
static gpg_error_t
reload_more_data_cb (void *opaque, const char *status, const char *args)
{
  struct reload_more_data_parm *parm = opaque;
  gpgme_key_t key = NULL;
  const char *s;
  char pattern[100];
  int idx;
  gpg_error_t err;
  const char *keyid;
  int any = 0;

  g_debug ("  reload_more_data (%s=%s)", status, args);
  if (strcmp (status, "KEYPAIRINFO") )
    return 0;

  g_debug ("   start search");
  idx = 0;
  pattern[idx++] = '&';
  for (s=args; hexdigitp (s) && idx < sizeof pattern - 1; s++)
    pattern[idx++] = *s;
  pattern[idx] = 0;
  if (!spacep (s) || (s - args != 40))
    return 0;  /* Invalid formatted keygrip.  */
  while (spacep (s))
    s++;
  keyid = s;

  if (!(err=gpgme_op_keylist_start (parm->ctx, pattern, 0)))
    {
      if (!(err=gpgme_op_keylist_next (parm->ctx, &key)))
        {
          GtkWidget *vbox, *expander, *details, *hbox, *label;

          vbox = gtk_bin_get_child (GTK_BIN (parm->card->keys_frame));
          if (!vbox)
            g_debug ("Ooops, vbox missing in key frame");
          else
            {
              expander = gtk_expander_new (keyid);
              details = gpa_key_details_new ();
              gtk_container_add (GTK_CONTAINER (expander), details);
              gpa_key_details_update (details, key, 1);

              hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
              label = gtk_label_new (NULL);
              gtk_label_set_width_chars  (GTK_LABEL (label), 22);
              gtk_widget_set_halign (GTK_WIDGET (label), 0);
              gtk_widget_set_valign (GTK_WIDGET (label), 0);
              gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
              gtk_box_pack_start (GTK_BOX (hbox), expander, TRUE, TRUE, 0);
              gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);

              any = 1;
            }
          gpgme_key_unref (key);
        }
    }
  gpgme_op_keylist_end (parm->ctx);
  if (!any)
    parm->any_unknown = 1;
  g_debug ("   ready");
  return 0;
}


/* Reload more data.  This function is called from the idle handler.  */
static void
reload_more_data (GpaCMNetkey *card)
{
  gpg_error_t err, operr;
  gpgme_ctx_t gpgagent;
  GtkWidget *vbox;
  struct reload_more_data_parm parm;

  g_debug ("start reload_more_data (count=%d)", card->reloading);
  gpgagent = GPA_CM_OBJECT (card)->agent_ctx;
  g_return_if_fail (gpgagent);
  g_return_if_fail (card->keys_frame);
  g_debug ("  gpgagent=%p", gpgagent);

  /* We remove any existing children of the keys frame and then we add
     a new vbox to be filled with new widgets by the callback.  */
  vbox = gtk_bin_get_child (GTK_BIN (card->keys_frame));
  if (vbox)
    gtk_widget_destroy (vbox);
  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
  gtk_container_add (GTK_CONTAINER (card->keys_frame), vbox);

  /* Create a context for key listings.  */
  parm.any_unknown = 0;
  parm.card = card;
  err = gpgme_new (&parm.ctx);
  if (err)
    {
      /* We don't want an error window because we are run from an idle
         handler and the information is not that important.  */
      g_debug ("failed to create a context: %s", gpg_strerror (err));
      return;
    }
  gpgme_set_protocol (parm.ctx, GPGME_PROTOCOL_CMS);
  /* We include ephemeral keys in the listing.  */
  gpgme_set_keylist_mode (parm.ctx, GPGME_KEYLIST_MODE_EPHEMERAL);

  g_debug ("  parm.ctx=%p", parm.ctx);

  err = gpgme_op_assuan_transact_ext (gpgagent,
                                      "SCD LEARN --keypairinfo",
                                      NULL, NULL, NULL, NULL,
                                      reload_more_data_cb, &parm, &operr);
  g_debug ("  assuan ret=%d", err);
  if (!err)
    err = operr;

  if (err)
    g_debug ("SCD LEARN failed: %s", gpg_strerror (err));

  if (parm.any_unknown)
    {
      GtkWidget *button;

      button = gtk_button_new_with_label (_("Learn keys"));

      gtk_widget_set_halign (GTK_WIDGET (button), 0.5);
      gtk_widget_set_valign (GTK_WIDGET (button), 0);

      gpa_add_tooltip
        (button, _("For some or all of the keys available on the card, "
                   "the GnuPG crypto engine does not yet know the "
                   "corresponding certificates.\n"
                   "\n"
                   "If you click this button, GnuPG will be asked to "
                   "\"learn\" "
                   "this card and import all certificates stored on the "
                   "card into its own certificate store.  This is not done "
                   "automatically because it may take several seconds to "
                   "read all certificates from the card.\n"
                   "\n"
                   "If you are unsure what to do, just click the button."));
      gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 5);
      gtk_box_reorder_child (GTK_BOX (vbox), button, 0);

      g_signal_connect (G_OBJECT (button), "clicked",
                        G_CALLBACK (learn_keys_clicked_cb), card);
    }

  gpgme_release (parm.ctx);
  gtk_widget_show_all (card->keys_frame);
  g_debug ("end   reload_more_data (count=%d)", card->reloading);
  return;
}


/* Idle queue callback to reload more data.  */
static gboolean
reload_more_data_idle_cb (void *user_data)
{
  GpaCMNetkey *card = user_data;

  if (card->reloading)
    {
      g_debug ("already reloading (count=%d)", card->reloading);
    return TRUE; /* Just in case we are still reloading, wait for the
                    next idle slot.  */
    }

  card->reloading++;
  reload_more_data (card);
  g_object_unref (card);
  card->reloading--;

  return FALSE;  /* Remove us from the idle queue.  */
}


struct scd_getattr_parm
{
  GpaCMNetkey *card;  /* The object.  */
  const char *name;   /* Name of expected attribute.  */
  int entry_id;       /* The identifier for the entry.  */
  void (*updfnc) (GpaCMNetkey *card, int entry_id, char *string);
};


static gpg_error_t
scd_getattr_cb (void *opaque, const char *status, const char *args)
{
  struct scd_getattr_parm *parm = opaque;
  int entry_id;

/*   g_debug ("STATUS_CB: status=`%s'  args=`%s'", status, args); */

  if (!strcmp (status, parm->name) )
    {
      entry_id = parm->entry_id;

      if (entry_id < ENTRY_LAST)
        {
          char *tmp = xstrdup (args);

          percent_unescape (tmp, 1);
          if (parm->updfnc)
            parm->updfnc (parm->card, entry_id, tmp);
          else if (GTK_IS_LABEL (parm->card->entries[entry_id]))
            gtk_label_set_text
              (GTK_LABEL (parm->card->entries[entry_id]), tmp);
          else
            gtk_entry_set_text
              (GTK_ENTRY (parm->card->entries[entry_id]), tmp);
          xfree (tmp);
        }
    }

  return 0;
}


/* Use the assuan machinery to load the bulk of the OpenPGP card data.  */
static void
reload_data (GpaCMNetkey *card)
{
  static struct {
    const char *name;
    int entry_id;
    void (*updfnc) (GpaCMNetkey *card, int entry_id, char *string);
  } attrtbl[] = {
    { "SERIALNO",    ENTRY_SERIALNO },
    { "NKS-VERSION", ENTRY_NKS_VERSION },
    { "CHV-STATUS",  ENTRY_PIN_RETRYCOUNTER, update_entry_chv_status },
    { NULL }
  };
  int attridx;
  gpg_error_t err = 0;
  gpg_error_t operr;
  char command[100];
  struct scd_getattr_parm parm;
  gpgme_ctx_t gpgagent;

  gpgagent = GPA_CM_OBJECT (card)->agent_ctx;
  g_return_if_fail (gpgagent);

  card->reloading++;
  g_debug ("uped reloading counter (count=%d)", card->reloading);

  /* Show all attributes.  */
  parm.card = card;
  for (attridx=0; attrtbl[attridx].name; attridx++)
    {
      parm.name     = attrtbl[attridx].name;
      parm.entry_id = attrtbl[attridx].entry_id;
      parm.updfnc   = attrtbl[attridx].updfnc;
      snprintf (command, sizeof command, "SCD GETATTR %s", parm.name);
      err = gpgme_op_assuan_transact_ext (gpgagent,
                                          command,
                                          NULL, NULL,
                                          NULL, NULL,
                                          scd_getattr_cb, &parm, &operr);
      if (!err)
        err = operr;

      if (err && attrtbl[attridx].entry_id == ENTRY_NKS_VERSION)
        {
          /* The NKS-VERSION is only supported by GnuPG > 2.0.11
             thus we ignore the error.  */
          gtk_label_set_text
            (GTK_LABEL (card->entries[attrtbl[attridx].entry_id]),
             _("unknown"));
        }
      else if (err)
        {
          if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT)
            ; /* Lost the card.  */
          else
            {
              g_debug ("assuan command `%s' failed: %s <%s>\n",
                       command, gpg_strerror (err), gpg_strsource (err));
            }
          clear_card_data (card);
          break;
        }
    }
  if (!err)
    {
      g_object_ref (card);
      g_idle_add (reload_more_data_idle_cb, card);
    }
  card->reloading--;
  g_debug ("downed reloading counter (count=%d)", card->reloading);
}


/* A structure used to pass data to the learn_keys_gpg_status_cb.  */
struct learn_keys_gpg_status_parm
{
  GpaCMNetkey    *card;
  GtkWidget      *button;
  GtkProgressBar *pbar;
};


/* Helper for learn_keys_clicked_cb.  */
static gboolean
learn_keys_gpg_status_cb (void *opaque, char *line)
{
  struct learn_keys_gpg_status_parm *parm = opaque;

  if (!line)
    {
      /* We are finished with the command.  */
      /* Trigger a reload of the key data.  */
      g_object_ref (parm->card);
      g_idle_add (reload_more_data_idle_cb, parm->card);
      /* Cleanup.  */
      gtk_widget_destroy (parm->button);
      g_object_unref (parm->button);
      g_object_unref (parm->card);
      xfree (parm);
      return FALSE; /* (The return code does not matter here.)  */
    }

  if (!strncmp (line, "PROGRESS", 8))
    gtk_progress_bar_pulse (parm->pbar);

  return TRUE; /* Keep on running.  */
}


/* Fire up the learn command.  */
static void
learn_keys_clicked_cb (GtkButton *button, void *user_data)
{
  GpaCMNetkey *card = user_data;
  GtkWidget *widget;
  gpg_error_t err;
  struct learn_keys_gpg_status_parm *parm;

  parm = xcalloc (1, sizeof *parm);

  gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE);
  widget = gtk_bin_get_child (GTK_BIN (button));
  if (widget)
    gtk_widget_destroy (widget);

  widget = gtk_progress_bar_new ();
  gtk_container_add (GTK_CONTAINER (button), widget);
  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (widget),
                              _("Learning keys ..."));
  gtk_widget_show_all (GTK_WIDGET (button));

  g_object_ref (card);
  parm->card = card;
  g_object_ref (button);
  parm->button = GTK_WIDGET (button);
  parm->pbar = GTK_PROGRESS_BAR (widget);

  err = gpa_start_simple_gpg_command
    (learn_keys_gpg_status_cb, parm, GPGME_PROTOCOL_CMS, 1,
     "--learn-card", "-v", NULL);
  if (err)
    {
      g_debug ("error starting gpg: %s", gpg_strerror (err));
      learn_keys_gpg_status_cb (parm, NULL);
      return;
    }
}


/* Run the dialog to change the NullPIN.  */
static void
change_nullpin (GpaCMNetkey *card)
{
  gpg_error_t err, operr;
  GtkWidget *dialog;
  gpgme_ctx_t gpgagent;
  int is_sigg;
  char *string;
  int okay;

  gpgagent = GPA_CM_OBJECT (card)->agent_ctx;
  g_return_if_fail (gpgagent);

  if (card->pininfo[0].valid && card->pininfo[0].nullpin)
    is_sigg = 0;
  else if (card->pininfo[2].valid && card->pininfo[2].nullpin)
    is_sigg = 1;
  else
    return; /* Oops: No NullPIN.  */

  string = g_strdup_printf
    (_("<b>Setting the Initial PIN</b> (%s)\n\n"
       "You selected to set the initial PIN of your card.  "
       "The PIN is currently set to the NullPIN.  Setting an "
       "initial PIN is <b>required but can't be reverted</b>.\n\n"
       "Please check the documentation of your card to learn "
       "for what the NullPIN is good.\n\n"
       "If you proceed you will be asked to enter a new PIN "
       "and later to repeat that PIN.  Make sure that you "
       "will remember that PIN - it will not be possible to "
       "recover the PIN if it has been entered wrongly more "
       "than %d times.\n\n%s"),
     is_sigg? "SigG" : "NKS",
     2,
     is_sigg
     ? _("You are now setting the PIN for the SigG key used to create "
         "<b>qualified signatures</b>.  You may want to set the "
         "PIN to the same value as used for the NKS keys.")
     : _("You are now setting the PIN for the NKS keys used for standard "
         "signatures, encryption and authentication."));

  /* FIXME:  How do we figure out our GtkWindow?  */
  dialog = gtk_message_dialog_new_with_markup (NULL /*GTK_WINDOW (card)*/,
                                               GTK_DIALOG_DESTROY_WITH_PARENT,
                                               GTK_MESSAGE_INFO,
                                               GTK_BUTTONS_OK_CANCEL,
                                               NULL);
  gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), string);
  g_free (string);

  okay = (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK);
  if (okay)
    {
      err = gpgme_op_assuan_transact_ext (gpgagent,
                                          is_sigg
                                          ? "SCD PASSWD --nullpin PW1.CH.SIG"
                                          : "SCD PASSWD --nullpin PW1.CH",
                                          NULL, NULL,
                                          NULL, NULL,
                                          NULL, NULL, &operr);
      if (!err)
        err = operr;

      if (gpg_err_code (err) == GPG_ERR_CANCELED)
        okay = 0; /* No need to reload the data.  */
      else if (err)
        {
          char *message = g_strdup_printf
            (_("Error changing the NullPIN.\n"
               "(%s <%s>)"), gpg_strerror (err), gpg_strsource (err));
          gpa_window_error (message, NULL);
          xfree (message);
        }
    }
  gtk_widget_destroy (GTK_WIDGET (dialog));
  if (okay)
    reload_data (card);
}



static void
change_or_reset_pin (GpaCMNetkey *card, int info_idx)
{
  gpg_error_t err, operr;
  GtkWidget *dialog;
  gpgme_ctx_t gpgagent;
  int reset_mode;
  const char *string;
  int is_puk;
  int okay;
  const char *pwidstr;

  gpgagent = GPA_CM_OBJECT (card)->agent_ctx;
  g_return_if_fail (gpgagent);
  g_return_if_fail (info_idx < DIM (card->pininfo));

  if (!card->pininfo[info_idx].valid
      || card->pininfo[info_idx].nopin
      || card->pininfo[info_idx].nullpin)
    {
      g_debug ("oops: NullPIN or not valid");
      return;
    }
  reset_mode = (card->pininfo[info_idx].blocked
                || !card->pininfo[info_idx].tries_left);
  is_puk = !!(info_idx & 1);
  g_debug ("%s PIN at idx %d", reset_mode?"Resetting":"Changing", info_idx);
  switch (info_idx)
    {
    case 0: pwidstr = "PW1.CH"; break;
    case 1: pwidstr = "PW2.CH"; break;
    case 2: pwidstr = "PW1.CH.SIG"; break;
    case 3: pwidstr = "PW2.CH.SIG"; break;
    default: return;  /* Ooops.  */
    }

  string = (!reset_mode
            ? _("<b>Changing a PIN or PUK</b>\n"
                "\n"
                "If you proceed you will be asked to enter "
                "the current value and then to enter a new "
                "value and repeat that value at another prompt.\n"
                "\n"
                "Entering a wrong value for the current value "
                "decrements the retry counter.  If the retry counters "
                "of the PIN and the corresponding PUK are both down "
                "to zero, the keys controlled by the PIN are not anymore "
                "usable and there is no way to unblock them!")
            : is_puk
            ? _("<b>Resetting a PUK</b>\n"
                "\n"
                "Although <i>PUK</i> stands for <i>PIN Unblocking Code</i> "
                "the TCOS operating system of the NetKey card implements it "
                "as an alternative PIN and thus it is possible to use the "
                "PIN to unblock the PUK.\n"
                "\n"
                "If the PUK is blocked (the retry counter is down to zero), "
                "you may unblock it by using the non-blocked PIN.  The retry "
                "counter is then set back to its initial value.\n"
                "\n"
                "If you proceed you will be asked to enter the current "
                "value of the PIN and then to enter a new value for the "
                "blocked PUK and repeat that new value at another prompt.")
            : _("<b>Resetting a PIN</b>\n"
                "\n"
                "If the PIN is blocked (the retry counter is down to zero), "
                "you may unblock it by using the non-blocked PUK.  The retry "
                "counter is then set back to its initial value.\n"
                "\n"
                "If you proceed you will be asked to enter the current "
                "value of the PUK and then to enter a new value for the "
                "blocked PIN and repeat that new value at another prompt.")
            );

  /* FIXME:  How do we figure out our GtkWindow?  */
  dialog = gtk_message_dialog_new_with_markup (NULL /*GTK_WINDOW (card)*/,
                                               GTK_DIALOG_DESTROY_WITH_PARENT,
                                               GTK_MESSAGE_INFO,
                                               GTK_BUTTONS_OK_CANCEL,
                                               NULL);
  gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), string);
  okay = (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK);
  if (okay)
    {
      char command[100];

      snprintf (command, sizeof command, "SCD PASSWD%s %s",
                reset_mode? " --reset":"", pwidstr);
      err = gpgme_op_assuan_transact_ext (gpgagent, command,
                                          NULL, NULL, NULL, NULL, NULL, NULL,
                                          &operr);
      if (!err)
        err = operr;

      if (gpg_err_code (err) == GPG_ERR_CANCELED)
        okay = 0; /* No need to reload the data.  */
      else if (err)
        {
          char *message = g_strdup_printf
            (_("Error changing or resetting the PIN/PUK.\n"
               "(%s <%s>)"), gpg_strerror (err), gpg_strsource (err));
          gpa_window_error (message, NULL);
          xfree (message);
        }
    }
  gtk_widget_destroy (GTK_WIDGET (dialog));
  if (okay)
    reload_data (card);
}



static void
set_initial_pin_clicked_cb (GtkButton *widget, void *user_data)
{
  GpaCMNetkey *card = user_data;

  change_nullpin (card);
}


static void
change_pin_clicked_cb (void *widget, void *user_data)
{
  GpaCMNetkey *card = user_data;

  if (widget == card->change_pin_btn)
    change_or_reset_pin (card, 0);
  else if (widget == card->change_puk_btn)
    change_or_reset_pin (card, 1);
  else if (widget == card->change_sigg_pin_btn)
    change_or_reset_pin (card, 2);
  else if (widget == card->change_sigg_puk_btn)
    change_or_reset_pin (card, 3);
}


/* Helper for construct_data_widget.  Returns the label widget. */
static GtkLabel *
add_grid_row (GtkWidget *grid, int *rowidx,
               const char *labelstr, GtkWidget *widget, GtkWidget *widget2,
               int readonly)
{
  GtkWidget *label;
  int is_label = GTK_IS_LABEL (widget);

  label = gtk_label_new (labelstr);
  gtk_label_set_width_chars  (GTK_LABEL (label), 22);
  gtk_widget_set_halign (GTK_WIDGET (label), 0);
  gtk_widget_set_valign (GTK_WIDGET (label), 0.5);
  gtk_grid_attach (GTK_GRID (grid), label, 0, *rowidx, 1, 1);

  if (is_label) {
    gtk_widget_set_halign (GTK_WIDGET (widget), 0);
    gtk_widget_set_valign (GTK_WIDGET (widget), 0.5);
  }

  if (readonly)
    {
      if (!is_label && GTK_IS_ENTRY (widget))
        {
          gtk_entry_set_has_frame (GTK_ENTRY (widget), FALSE);
          gtk_editable_set_editable(GTK_EDITABLE(widget), FALSE);
        }
    }
  else
    {
      if (is_label)
        gtk_label_set_selectable (GTK_LABEL (widget), TRUE);
    }

  gtk_grid_attach (GTK_GRID(grid), widget, 1, *rowidx, 1, 1);

  if (widget2)
    gtk_grid_attach (GTK_GRID(grid), widget, 2, *rowidx, 3, 1);
  ++*rowidx;

  return GTK_LABEL (label);
}


/* This function constructs the container holding all widgets making
   up this data widget.  It is called during instance creation.  */
static void
construct_data_widget (GpaCMNetkey *card)
{
  GtkWidget *frame;
  GtkWidget *grid;
  GtkWidget *vbox;
  GtkWidget *label;
  GtkWidget *button;
  int rowidx;

  /* General frame.  */
  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
  label = gtk_label_new (_("<b>General</b>"));
  gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
  gtk_frame_set_label_widget (GTK_FRAME (frame), label);
  //table = gtk_table_new (2, 3, FALSE);
  grid = gtk_grid_new();
  gtk_container_set_border_width (GTK_CONTAINER (grid), 10);
  gtk_container_add (GTK_CONTAINER (frame), grid);
  rowidx = 0;

  card->entries[ENTRY_SERIALNO] = gtk_label_new (NULL);
  add_grid_row (grid, &rowidx, _("Serial number:"),
                 card->entries[ENTRY_SERIALNO], NULL, 0);

  card->entries[ENTRY_NKS_VERSION] = gtk_label_new (NULL);
  add_grid_row (grid, &rowidx, _("Card version:"),
                 card->entries[ENTRY_NKS_VERSION], NULL, 0);

  gtk_box_pack_start (GTK_BOX (card), frame, FALSE, TRUE, 0);


  /* Warning frame.  */
  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
  label = gtk_label_new
    (_("<b>A NullPIN is still active on this card</b>.\n"
       "You need to set a real PIN before you can make use of the card."));
  gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
  gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);

  button = gtk_button_new_with_label (_("Set initial PIN"));

  gtk_widget_set_halign (GTK_WIDGET (button), 0.5);
  gtk_widget_set_valign (GTK_WIDGET (button), 0.5);

  gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, FALSE, 0);

  gtk_container_add (GTK_CONTAINER (frame), vbox);
  gtk_box_pack_start (GTK_BOX (card), frame, FALSE, TRUE, 0);
  card->warning_frame = frame;

  g_signal_connect (G_OBJECT (button), "clicked",
                    G_CALLBACK (set_initial_pin_clicked_cb), card);


  /* Keys frame.  */
  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
  label = gtk_label_new (_("<b>Keys</b>"));
  gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
  gtk_frame_set_label_widget (GTK_FRAME (frame), label);

  /* There is only a label widget yet in the frame for now.  The real
     widgets are added while figuring out the keys of the card.  */
  label = gtk_label_new (_("scanning ..."));
  gtk_container_add (GTK_CONTAINER (frame), label);

  gtk_box_pack_start (GTK_BOX (card), frame, FALSE, TRUE, 0);
  card->keys_frame = frame;


  /* PIN frame.  */
  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
  label = gtk_label_new (_("<b>PIN</b>"));
  gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
  gtk_frame_set_label_widget (GTK_FRAME (frame), label);
  grid = gtk_grid_new ();
  gtk_container_set_border_width (GTK_CONTAINER (grid), 10);
  gtk_container_add (GTK_CONTAINER (frame), grid);
  rowidx = 0;

  card->entries[ENTRY_PIN_RETRYCOUNTER] = gtk_label_new (NULL);
  button = gtk_button_new ();
  add_grid_row (grid, &rowidx, _("PIN retry counter:"),
                 card->entries[ENTRY_PIN_RETRYCOUNTER], button, 1);
  card->change_pin_btn = button;
  g_signal_connect (G_OBJECT (button), "clicked",
                    G_CALLBACK (change_pin_clicked_cb), card);

  card->entries[ENTRY_PUK_RETRYCOUNTER] = gtk_label_new (NULL);
  button = gtk_button_new ();
  add_grid_row (grid, &rowidx, _("PUK retry counter:"),
                 card->entries[ENTRY_PUK_RETRYCOUNTER], button, 1);
  card->change_puk_btn = button;
  g_signal_connect (G_OBJECT (button), "clicked",
                    G_CALLBACK (change_pin_clicked_cb), card);

  card->entries[ENTRY_SIGG_PIN_RETRYCOUNTER] = gtk_label_new (NULL);
  button = gtk_button_new ();
  add_grid_row (grid, &rowidx, _("SigG PIN retry counter:"),
                 card->entries[ENTRY_SIGG_PIN_RETRYCOUNTER], button, 1);
  card->change_sigg_pin_btn = button;
  g_signal_connect (G_OBJECT (button), "clicked",
                    G_CALLBACK (change_pin_clicked_cb), card);

  card->entries[ENTRY_SIGG_PUK_RETRYCOUNTER] = gtk_label_new (NULL);
  button = gtk_button_new ();
  add_grid_row (grid, &rowidx, _("SigG PUK retry counter:"),
                 card->entries[ENTRY_SIGG_PUK_RETRYCOUNTER], button, 1);
  card->change_sigg_puk_btn = button;
  g_signal_connect (G_OBJECT (button), "clicked",
                    G_CALLBACK (change_pin_clicked_cb), card);

  gtk_box_pack_start (GTK_BOX (card), frame, FALSE, TRUE, 0);
}



/************************************************************
 ******************   Object Management  ********************
 ************************************************************/

static void
gpa_cm_netkey_class_init (void *class_ptr, void *class_data)
{
  GpaCMNetkeyClass *klass = class_ptr;

  parent_class = g_type_class_peek_parent (klass);

  G_OBJECT_CLASS (klass)->finalize = gpa_cm_netkey_finalize;
}


static void
gpa_cm_netkey_init (GTypeInstance *instance, void *class_ptr)
{
  GpaCMNetkey *card = GPA_CM_NETKEY (instance);

  construct_data_widget (card);

}


static void
gpa_cm_netkey_finalize (GObject *object)
{
/*   GpaCMNetkey *card = GPA_CM_NETKEY (object); */

  parent_class->finalize (object);
}


/* Construct the class.  */
GType
gpa_cm_netkey_get_type (void)
{
  static GType this_type = 0;

  if (!this_type)
    {
      static const GTypeInfo this_info =
	{
	  sizeof (GpaCMNetkeyClass),
	  (GBaseInitFunc) NULL,
	  (GBaseFinalizeFunc) NULL,
	  gpa_cm_netkey_class_init,
	  (GClassFinalizeFunc) NULL,
	  NULL, /* class_data */
	  sizeof (GpaCMNetkey),
	  0,    /* n_preallocs */
	  gpa_cm_netkey_init
	};

      this_type = g_type_register_static (GPA_CM_OBJECT_TYPE,
                                          "GpaCMNetkey",
                                          &this_info, 0);
    }

  return this_type;
}


/************************************************************
 **********************  Public API  ************************
 ************************************************************/
GtkWidget *
gpa_cm_netkey_new ()
{
  return GTK_WIDGET (g_object_new (GPA_CM_NETKEY_TYPE, NULL));
}


/* If WIDGET is of Type GpaCMNetkey do a data reload through the
   assuan connection.  */
void
gpa_cm_netkey_reload (GtkWidget *widget, gpgme_ctx_t gpgagent)
{
  if (GPA_IS_CM_NETKEY (widget))
    {
      GPA_CM_OBJECT (widget)->agent_ctx = gpgagent;
      if (gpgagent)
        reload_data (GPA_CM_NETKEY (widget));
    }
}
