/*! @file

    @id $Id$
*/
//       1         2         3         4         5         6         7         8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890

#ifndef __Tokentool_HXX__
#define __Tokentool_HXX__

#include <QMainWindow>
#include <QMessageBox>
#include <QProgressBar>
#include <QDateTime>
#include <QSslCertificate>
#include <ui_tokentool.hxx>
#include <memory>

#include <QDebug>

#include <cryptoki.hxx>

#if QT_VERSION >= 0x050000
namespace QSsl {
  typedef AlternativeNameEntryType AlternateNameEntryType;
}
#endif

inline QString qs(const std::string& s) {
  return QString::fromStdString(s);
}

template<std::string::size_type SIZE>
    QString qs(const cryptoki::FixString<SIZE>& s) {
  return QString::fromStdString(s);
}

class Progress: public QProgressBar {
  public:
    Progress() {
      reset();
    }
    Progress& steps(int incr) {
      setMaximum(maximum()+incr);
      setVisible(value()<maximum());
      return *this;
    }
    Progress& operator++() {
      setValue(value()+1);
      setVisible(value()<maximum());
      if (value()>=maximum()) {
        setMaximum(0);
        setValue(0);
      }
      return *this;
    }
    Progress& reset() {
      setMinimum(0);
      setMaximum(0);
      setValue(0);
      setVisible(false);
      return *this;
    }

};

class tokentool: public QMainWindow, protected Ui::tokentool {
    Q_OBJECT;
  public:
    tokentool(std::string lib="libcvP11.so", QWidget* p=0):
        QMainWindow(p), _slot(0), _progress(new Progress) {
      try {
        setupUi(this);
        statusBar()->addPermanentWidget(_progress, 2);
        show();
        _progress->steps(3);
        _cryptoki = std::auto_ptr<cryptoki::Library>(new cryptoki::Library(lib));
        ++*_progress;
        _certificates->addAction(actionDeleteSelectedCertificate);
        libraryInfo(lib);
        ++*_progress;
        on_actionRescan_triggered();
        ++*_progress;
      } catch (...) {
        QMessageBox::critical(this, tr("Initialization Failed"),
                              tr("SmardCard Initialization Failed"));
      }
    }
  private Q_SLOTS:
    void on_actionRescan_triggered() {
      qDebug()<<__PRETTY_FUNCTION__;
      static bool lock(false);
      if (lock) return;
      lock = true;
      try {
        _slot = 0;
        _slots->clear();
        _slotList = _cryptoki->slotList(false);
        _progress->steps(_slotList.size());
        for (cryptoki::SlotList::iterator it(_slotList.begin());
             it!=_slotList.end(); ++it) {
          qDebug()<<"Add Slot";
          QAction* a(_slots->addAction(qs(it->slotinfo().slotDescription)));
          a->setCheckable(true);
          a->setData((qulonglong)&*it);
          assert(connect(a, SIGNAL(triggered(bool)), SLOT(chosen(bool))));
          if (!_slot) a->trigger();
          ++*_progress;
        }
        qDebug()<<"DONE";
      } catch (...) {} // ignore
      lock = false;
      on__certificates_itemSelectionChanged();
    }
    void on_actionDeleteSelectedCertificate_triggered() {
      QList<QTreeWidgetItem*> items(_certificates->selectedItems());
      if (items.size()>0) deleteCert(items[0]);
    }
    void on__certificates_itemSelectionChanged() {
      actionDeleteSelectedCertificate
        ->setEnabled(_certificates->selectedItems().size()
                     &&!_pin->text().isEmpty());
    }
    void on__pin_textChanged(const QString&) {
      on__certificates_itemSelectionChanged();
    }
    void chosen(bool) {
      _pin->clear();
      QAction* a(qobject_cast<QAction*>(sender()));
      a->setChecked(true);
      QMenu* m(qobject_cast<QMenu*>(a->parent()));
      QObjectList actions(m->children());
      for (QObjectList::iterator it(actions.begin());it!=actions.end(); ++it)
        if (qobject_cast<QAction*>(*it)!=a)
          qobject_cast<QAction*>(*it)->setChecked(false);
      _slot = (cryptoki::Slot*)a->data().toLongLong();
      setup();
    }
    void setup() {
      try {
        _progress->steps(3);
        slotInfo();
        ++*_progress;
        tokenInfo();
        ++*_progress;
        certificates();
        ++*_progress;
        objects();
      } catch (...) {
        _progress->reset();
        on_actionRescan_triggered();
      }
    }
    void libraryInfo(std::string lib) {
      _soname->setText(qs(lib));
      cryptoki::Info inf(_cryptoki->info());
      _libraryCryptokiVersion->setText(QString("%1.%2")
                                       .arg(inf.cryptokiVersion.major)
                                       .arg(inf.cryptokiVersion.minor));
      _libraryManufacturerID->setText(qs(inf.manufacturerID));
      _libraryDescription->setText(qs(inf.libraryDescription));
      _libraryVersion->setText(QString("%1.%2")
                                .arg(inf.libraryVersion.major)
                                .arg(inf.libraryVersion.minor));
    }
    void slotInfo() {
      try {
        if (!_slot) return;
        _hardware->setTabEnabled(1, true);
        cryptoki::SlotInfo slotInfo(_slot->slotinfo());
        _slotDescription->setText(qs(slotInfo.slotDescription));
        _slotManufacturerID->setText(qs(slotInfo.manufacturerID));
        _slotHardwareVersion->setText(QString("%1.%2")
                                      .arg(slotInfo.hardwareVersion.major)
                                      .arg(slotInfo.hardwareVersion.minor));
        _slotFirmwareVersion->setText(QString("%1.%2")
                                      .arg(slotInfo.firmwareVersion.major)
                                      .arg(slotInfo.firmwareVersion.minor));
        _tokenPresent->setEnabled(slotInfo.flags&CKF_TOKEN_PRESENT);
        _removableDevice->setEnabled(slotInfo.flags&CKF_REMOVABLE_DEVICE);
        _hwSlot->setEnabled(slotInfo.flags&CKF_HW_SLOT);
      } catch (...) {
        _hardware->setTabEnabled(1, false);
      }
    }
    void tokenInfo() {
      try {
        if (!_slot) return;
        _hardware->setTabEnabled(2, true);
        _hardware->setTabEnabled(3, true);
        cryptoki::TokenInfo tokenInfo(_slot->tokeninfo());
        _tokenLabel->setText(qs(tokenInfo.label));
        _tokenManufacturerID->setText(qs(tokenInfo.manufacturerID));
        _tokenModel->setText(qs(tokenInfo.model));
        _tokenSerialNumber->setText(qs(tokenInfo.serialNumber));
        _tokenSessionCount->setText(QString("%2/%1")
                                    .arg(qs(cryptoki::string
                                            (tokenInfo.maxSessionCount)))
                                    .arg(qs(cryptoki::string
                                            (tokenInfo.sessionCount))));
        _tokenRWSessionCount->setText(QString("%2/%1")
                                      .arg(qs(cryptoki::string
                                              (tokenInfo.maxRwSessionCount)))
                                      .arg(qs(cryptoki::string
                                              (tokenInfo.rwSessionCount))));
        _tokenPinLen->setText(QString("%2-%1")
                              .arg(tokenInfo.maxPinLen)
                              .arg(tokenInfo.minPinLen));
        _tokenPublicMemory->setText(QString("%2/%1")
                                    .arg(qs(cryptoki::string
                                            (tokenInfo.totalPublicMemory)))
                                    .arg(qs(cryptoki::string
                                            (tokenInfo.freePublicMemory))));
        _tokenPrivateMemory->setText(QString("%2/%1")
                                     .arg(qs(cryptoki::string
                                             (tokenInfo.totalPrivateMemory)))
                                     .arg(qs(cryptoki::string
                                             (tokenInfo.freePrivateMemory))));
        _tokenHardwareVersion->setText(QString("%1.%2")
                                       .arg(tokenInfo.hardwareVersion.major)
                                       .arg(tokenInfo.hardwareVersion.minor));
        _tokenFirmwareVersion->setText(QString("%1.%2")
                                       .arg(tokenInfo.firmwareVersion.major)
                                       .arg(tokenInfo.firmwareVersion.minor));
        _tokenUTCTime->setText(qs(tokenInfo.utcTime));
        _tokenUTCTime->setVisible(tokenInfo.flags&CKF_CLOCK_ON_TOKEN);
        _rng->setEnabled(tokenInfo.flags&CKF_RNG);
        _writeProtected->setEnabled(tokenInfo.flags&CKF_WRITE_PROTECTED);
        _loginRequired->setEnabled(tokenInfo.flags&CKF_LOGIN_REQUIRED);
        _userPinInitialized->setEnabled
          (tokenInfo.flags&CKF_USER_PIN_INITIALIZED);
        _restoreKeyNotNeeded->setEnabled
          (tokenInfo.flags&CKF_RESTORE_KEY_NOT_NEEDED);
        _clockOnToken->setEnabled(tokenInfo.flags&CKF_CLOCK_ON_TOKEN);
        _protectedAuthenticationPath->setEnabled
          (tokenInfo.flags&CKF_PROTECTED_AUTHENTICATION_PATH);
        _dualCryptoOperations->setEnabled
          (tokenInfo.flags&CKF_DUAL_CRYPTO_OPERATIONS);
        _tokenInitialized->setEnabled(tokenInfo.flags&CKF_TOKEN_INITIALIZED);
        _secondaryAuthentication->setEnabled
          (tokenInfo.flags&CKF_SECONDARY_AUTHENTICATION);
        _userPinCountLow->setEnabled(tokenInfo.flags&CKF_USER_PIN_COUNT_LOW);
        _userPinFinalTry->setEnabled(tokenInfo.flags&CKF_USER_PIN_FINAL_TRY);
        _userPinLocked->setEnabled(tokenInfo.flags&CKF_USER_PIN_LOCKED);
        _userPinToBeChanged->setEnabled
          (tokenInfo.flags&CKF_USER_PIN_TO_BE_CHANGED);
        _soPinCountLow->setEnabled(tokenInfo.flags&CKF_SO_PIN_COUNT_LOW);
        _soPinFinalTry->setEnabled(tokenInfo.flags&CKF_SO_PIN_FINAL_TRY);
        _soPinLocked->setEnabled(tokenInfo.flags&CKF_SO_PIN_LOCKED);
        _soPinToBeChanged->setEnabled(tokenInfo.flags&CKF_SO_PIN_TO_BE_CHANGED);
      } catch (...) {
        _hardware->setTabEnabled(2, false);
        _hardware->setTabEnabled(3, false);
      }
    }
    void certificates() {
      try {
        _certificates->clear();
        if (!_slot) return;
        _certificates->setEnabled(true);
        cryptoki::Session session(*_slot);
        cryptoki::ObjectList certs
          (session.find(cryptoki::Attribute(CKA_CLASS)
                        .from<CK_OBJECT_CLASS>(CKO_CERTIFICATE)));
        _progress->steps(certs.size());
        for (cryptoki::ObjectList::iterator cert(certs.begin());
             cert!=certs.end(); ++cert) {
          std::string data(cert->attribute(CKA_VALUE).value);
          QByteArray der(QByteArray(data.data(), data.size()));
          QSslCertificate c(der, QSsl::Der);
          addCertificate(cert->attribute(CKA_LABEL).value,
                         crypto::hex(cert->attribute(CKA_ID).value), c);
          ++*_progress;
        }
      } catch (...) {
        _certificates->setEnabled(false);
      }
    }
    void objects() {
      try {
        _objects->clear();
        if (!_slot) return;
        _objects->setEnabled(true);
        cryptoki::Session session(*_slot);
        cryptoki::ObjectList objs(session.find());
        _progress->steps(objs.size());
        for (cryptoki::ObjectList::iterator obj(objs.begin());
             obj!=objs.end(); ++obj) {
          _objects->addTopLevelItem
            (new QTreeWidgetItem
             (QStringList()
              <<qs(obj->attribute(CKA_LABEL).value)
              <<qs(crypto::hex(obj->attribute(CKA_ID).value))));
          ++*_progress;
        }
        _objects->resizeColumnToContents(0);
        _objects->resizeColumnToContents(1);
      } catch (...) {
        _objects->setEnabled(false);
      }
    }
    void addCertificate(const std::string& label, const std::string& id,
                        const QSslCertificate& cert) {
      if (cert.isNull()) return;
      QTreeWidgetItem* c(new QTreeWidgetItem(QStringList()<<qs(label)));
      c->setData(0, Qt::UserRole, qs(id));
      _certificates->addTopLevelItem(c);
      c->addChild
        ((new QTreeWidgetItem
          (QStringList()<<tr("Valid Since")
           <<cert.effectiveDate().toString(Qt::SystemLocaleLongDate))));
      c->addChild
        ((new QTreeWidgetItem
          (QStringList()<<tr("Valid Until")
           <<cert.expiryDate().toString(Qt::SystemLocaleLongDate))));
      c->addChild
        ((new QTreeWidgetItem
          (QStringList()<<tr("Certificate Serial Number")
           <<cert.serialNumber())));
      QTreeWidgetItem *it(0);
      c->addChild
        ((it = new QTreeWidgetItem(QStringList()<<tr("Subject Info")<<"")));
      for (QSslCertificate::SubjectInfo
             si(QSslCertificate::StateOrProvinceName);
           si>=QSslCertificate::Organization;
           si=(QSslCertificate::SubjectInfo)((int)si-1))
        if (!cert.subjectInfo(si).isEmpty()) {
          it->addChild
            ((new QTreeWidgetItem(subjectInfo(si)
                                  <<utfConv(cert.subjectInfo(si)))));
        }
      it->setExpanded(true);
      QMultiMap<QSsl::AlternateNameEntryType, QString>
#if QT_VERSION <0x050000
        asns(cert.alternateSubjectNames())
#else
        asns(cert.subjectAlternativeNames())
#endif
        ;
      for (QMultiMap<QSsl::AlternateNameEntryType, QString>::iterator
             asn(asns.begin()); asn!=asns.end(); ++asn)
        it->addChild
          ((new QTreeWidgetItem
            (QStringList()<<alternateName(asn.key())<<asn.value())));
      c->addChild
        ((it = new QTreeWidgetItem(QStringList()<<tr("Issuer Info")<<"")));
      for (QSslCertificate::SubjectInfo
             si(QSslCertificate::StateOrProvinceName);
           si>=QSslCertificate::Organization;
           si=(QSslCertificate::SubjectInfo)((int)si-1))
        if (!cert.issuerInfo(si).isEmpty())
          it->addChild
            ((new QTreeWidgetItem(subjectInfo(si)
                                  <<utfConv(cert.issuerInfo(si)))));
      it->setExpanded(true);
      _certificates->resizeColumnToContents(0);
      _certificates->resizeColumnToContents(1);
    }
  private:
    void deleteCert(QTreeWidgetItem* item) {
      if (!_slot) return;
      if (_pin->text().isEmpty()) {
        QMessageBox::critical(this, tr("PIN required"), tr("Please enter PIN"));
        return;
      }
      if (item->parent()) return deleteCert(item->parent());
      QByteArray qid(QByteArray::fromHex(item->data(0, Qt::UserRole)
                                         .toByteArray()));
      std::string id(qid.data(), qid.size());
      cryptoki::Session session(*_slot, true);
      try {
        session.login(_pin->text().toStdString());
      } catch (...) {
        QMessageBox::critical(this, tr("Login Failed"), tr("Wrong PIN"));
        return;
      }
      cryptoki::ObjectList objs
        (session.find(cryptoki::AttributeList()
                      <<cryptoki::Attribute(CKA_CLASS)
                      .from<CK_OBJECT_CLASS>(CKO_CERTIFICATE)
                      <<cryptoki::Attribute(CKA_ID, id)));
      for (cryptoki::ObjectList::iterator obj(objs.begin());
           obj!=objs.end(); ++obj) try {
          obj->destroy();
        } catch (const std::exception& e) {
          QMessageBox::critical(this, tr("Delete Failed"),
                                tr("Certificate Deletion Failes"));
        }
      setup();
    }
    QString alternateName(QSsl::AlternateNameEntryType an) {
      switch (an) {
        case QSsl::EmailEntry: return tr("E-Mail");
        case QSsl::DnsEntry: return tr("URL");
      }
      return tr("error", "unknown certificate subject alternate name");
    }
    QStringList subjectInfo(QSslCertificate::SubjectInfo si) {
      QStringList res;
      switch (si) {
        case QSslCertificate::Organization:
          res<<tr("Organization"); break;
        case QSslCertificate::CommonName:
          res<<tr("Common Name"); break;
        case QSslCertificate::LocalityName:
          res<<tr("Locality"); break;
        case QSslCertificate::OrganizationalUnitName:
          res<<tr("Organizational Unit"); break;
        case QSslCertificate::CountryName:
          res<<tr("Country"); break;
        case QSslCertificate::StateOrProvinceName:
          res<<tr("State or Province"); break;
        default:
          res<<tr("error", "unknown certificate subject info"); break;
      }
      return res;
    }
    QString utfConv(const QString& txt) {
      QByteArray value(txt.toLatin1());
      for (int i(-1); (i=value.indexOf("\\x"))!=-1 && i+3<value.size();)
        value.replace(i, 4, QByteArray::fromHex(value.mid(i+2, 2)));
      return QString::fromUtf8(value.data(), value.size());
    }
    QStringList utfConv(const QStringList& txts) {
      QStringList res;
      Q_FOREACH(QString txt, txts) {
        QByteArray value(txt.toLatin1());
        for (int i(-1); (i=value.indexOf("\\x"))!=-1 && i+3<value.size();) {
          value.replace(i, 4, QByteArray::fromHex(value.mid(i+2, 2)));
          res<<QString::fromUtf8(value.data(), value.size());
        }
      }
      return res;
    }
  private:
    std::auto_ptr<cryptoki::Library> _cryptoki;
    cryptoki::SlotList _slotList;
    cryptoki::Slot* _slot;
    Progress* _progress;
};

#endif
