/*
 ----------------------------------------------------------------------------
 "THE BEER-WARE LICENSE" (Revision 42):
 <dkratzert@gmx.de> wrote this file. As long as you retain
 this notice you can do whatever you want with this stuff. If we meet some day,
 and you think this stuff is worth it, you can buy me a beer in return.
 Daniel Kratzert
 ----------------------------------------------------------------------------
*/

#include <QtGui>
#include "dsreditwindow.h"
#include "dsrglwindow.h"
#include "deprecation.h"

/*
   TODO:
   - create a database handler class.
*/

DSREditWindow::DSREditWindow(Molecule *mol, DSRMol *header,
                             QString dsr_db_path, QString nametag,
                             QVector<QStringList> fragmentsList, DSRGui *parent)
  : QWidget(parent)
{
  RESTRAINT_CARDS << "SIMU" << "RIGU" << "DELU" << "SAME" << "FREE" << "DFIX" << "BUMP"
                  << "HFIX" << "BIND" << "SADI" << "CHIV" << "FLAT" << "DEFS" << "ISOR"
                  << "NCSY" << "DANG" << "EADP" << "EXYZ";
  this->setParent(parent);
  m_gui = parent;
  m_mol = mol;
  m_header = header;
  mydsrdbpath = dsr_db_path;
  m_frags = fragmentsList;
  userdb = false;
  m_nametag = nametag;
  a = 0;
  b = 0;
  c = 0;
  alpha = 0;
  beta = 0;
  gamma = 0;
  oldatoms = new QString;
  oldrestraints = new QString;
  previous_atoms = new QStringList;
  first_atnames = new QStringList;
  setWindowFlags(Qt::Window);
  setWindowTitle(QString("Edit Fragment"));
  mainVLayout = new QVBoxLayout(this);
  maingrid = new QGridLayout;
  mainVLayout->addLayout(maingrid);
  makeUnitCellEdit();
  QLabel *cellabel = new QLabel("Unit Cell");
  QLabel *namelabel = new QLabel("Name:");
  nameEdit = new QLineEdit;
  QLabel *restrlabel = new QLabel("Restraints:");
  restrEdit = new QTextEdit();
  QLabel *atomslabel = new QLabel("Atoms:");
  atomsEdit = new QTextEdit;
  atomsEdit->setAcceptRichText(false);
  QLabel *resiclabel = new QLabel("Residue Class:");
  resicledit = new QLineEdit;
  resicledit->setMaximumWidth(getCharWidth(6));
  resicledit->setMinimumWidth(getCharWidth(6));
  use_selected = new QPushButton("Use selected Atoms");
  use_selected->setMaximumWidth(getCharWidth(30));
  myedgl = new DSRGlWindow(this, mol, *header, nametag);
  updateButton = new QPushButton("Update Fragment");
  addButton = new QPushButton("Add as New");
  deleteButton = new QPushButton("Delete Fragment");
  mailButton = new QPushButton("Mail Fragment home");
  clearButton = new QPushButton("Clear All");
  renameButton = new QPushButton("Enter Rename Mode");
  accept_renameButton = new QPushButton("OK");
  //accept_renameButton->setStyleSheet("QPushButton{background: rgb(124, 255, 117);}");
  abort_renameButton = new QPushButton("Abort");
  //abort_renameButton->setStyleSheet("QPushButton{background: rgb(255, 180, 180);}");
  buttonlayout = new QGridLayout;
  infoLabel = new QLabel;
  //    buttongrid          row column spanrow spancolumn:
  buttonlayout->addWidget(addButton,     0, 0);
  buttonlayout->addWidget(updateButton,  0, 1);
  buttonlayout->addWidget(deleteButton,  0, 2);
  buttonlayout->addWidget(mailButton,    1, 0);
  buttonlayout->addWidget(clearButton,   1, 1);
  buttonlayout->addWidget(renameButton,  1, 2);
  buttonlayout->addWidget(infoLabel,     2, 0, 2, 3);
  //infoLabel->setStyleSheet("border: 1px solid lightgrey");
  addButton->setDisabled(true);

    //    maingrid          row column spanrow spancolumn:
  maingrid->setRowStretch(1, 2);
  maingrid->setRowStretch(3, 2);
  maingrid->setRowStretch(4, 2);
  maingrid->setColumnStretch(0, 0);
  maingrid->setColumnStretch(1, 2);
  maingrid->setColumnStretch(2, 0);
  maingrid->setColumnStretch(3, 0);
  maingrid->setColumnStretch(4, 2);
  maingrid->addWidget(myedgl,      0, 4, 4, 3);
  maingrid->addWidget(cellabel,    0, 0, 1, 1);
  maingrid->addLayout(cellLayout,  0, 1, 1, 3);
  maingrid->addWidget(namelabel,   1, 0, 1, 1);
  maingrid->addWidget(nameEdit,    1, 1, 1, 1);
  maingrid->addWidget(resiclabel,  1, 2, 1, 1);
  maingrid->addWidget(resicledit,  1, 3, 1, 1);
  maingrid->addWidget(use_selected,2, 1, 1, 2);
  maingrid->addWidget(atomslabel,  3, 0, 1, 1);
  maingrid->addWidget(atomsEdit,   3, 1, 1, 3);
  maingrid->addWidget(restrlabel,  4, 0, 2, 1);
  maingrid->addWidget(restrEdit,   4, 1, 2, 3);
  maingrid->addLayout(buttonlayout,4, 4, 2, 3, Qt::AlignTop);

  addButton->setToolTip("Add this fragment to the database.\n"
                        "Fragments from the database supplied by DSR will\n"
                        "always be stored as a new fragment in the user database.");
  deleteButton->setToolTip("Deletes the currently opened fragment. \n"
                           "It can only delete fragments from the user database in dsr_usr_db.txt");
  nameEdit->setToolTip("Insert any chemical name or sum formula here.");
  resicledit->setToolTip("A residue class has to start with a letter \n"
                         "and can be up to four characters including numbers.");
  use_selected->setToolTip("Uses the selected atoms in ShlelXle as new atoms\n"
                           "for the atom list below.");
  atomsEdit->setToolTip("Insert atoms with following syntax:\n"
                        "Name   Atomic Number    x    y    z\n"
                        "C6     6     0.00460    2.39814    1.55732");
  updateButton->setToolTip("Updates the fragment with the current changes.");
  deleteButton->setToolTip("Erases the current fragment from the database.\n"
                           "(Only user-made fragments can be deleted)");
  mailButton->setToolTip("Sends the current fragment to the author of DSR.\n"
                         "By sending the fragment to Daniel Kratzert, you\n"
                         "allow him to distribute it with the DSR program.");
  renameButton->setToolTip("Enter rename mode to rename atoms.\n"
                           "Restraints will be updated according to the new atom names.\n"
                           "You can not add/delete atoms in rename mode.\n"
                           "Renaming is only available if no previous changes were made.");
  clearButton->setToolTip("Clear all settings to define a new fragment.");
  accept_renameButton->setToolTip("Accept the changes and return to fragment editor.");
  abort_renameButton->setToolTip("Revert all changes and return to fragment editor.");
  restrEdit->setToolTip("Insert any valid SHELXL restraint.");
  use_selected->setStyleSheet("QPushButton {"
                               "font-weight: bold; "
                              "}");
  infoLabel->setText("");
  QFont labelfont("Arial bold", 12);
  infoLabel->setFont(labelfont);
  infoLabel->setWordWrap(true); // otherwise the widget gets elongated by long text.
  myedgl->updateGL();
  // Only do this if we selected a fragment in the list!
  // Before, we have no information from the db header.
  if (!nametag.isEmpty() && !header->cell.isEmpty()) {
    setCellFromDB(header);
  }
  if (!nametag.isEmpty() && !header->comment.isEmpty()) {
    setNamefromDB(header);
  }
  if (!nametag.isEmpty() && !header->residue.isEmpty()) {
    setResidueFromDB(header);
  }
  if (!nametag.isEmpty() && !header->atoms.isEmpty()) {
    setAtomsFromDB(header);
  }
  if (!nametag.isEmpty() && !header->restr.isEmpty()) {
    setRestraintsFromDB(header);
  }
  if (!nametag.isEmpty() && !header->dbtype.isEmpty()) {
    setDBTypeFromDB(header);
  }
  if (!userdb) {
    deleteButton->setEnabled(false);
  }
  if (m_nametag.isEmpty()) {
    updateButton->setDisabled(true);
  }
  connectSignalsAndSlots();
  connect(deleteButton, SIGNAL(clicked()),
          parent->mygl, SLOT(clear_molecule()));
}

DSREditWindow::~DSREditWindow(){
}

void DSREditWindow::connectSignalsAndSlots()
//! handles all signals and slots
{
  // check and set residue class from edit field:
  connect(resicledit, SIGNAL(textChanged(QString)),
          this, SLOT(checkResiClassInput(QString)));
  // set fragname variable from edit field:
  connect(nameEdit, SIGNAL(textChanged(QString)),
          this, SLOT(setFragmentName(QString)));
  // set cell from edit fields:
  connect(cell_a, SIGNAL(textChanged(QString)),
          this, SLOT(set_a_value(QString)));
  connect(cell_b, SIGNAL(textChanged(QString)),
          this, SLOT(set_b_value(QString)));
  connect(cell_c, SIGNAL(textChanged(QString)),
          this, SLOT(set_c_value(QString)));
  connect(cell_alpha, SIGNAL(textChanged(QString)),
          this, SLOT(set_alpha_value(QString)));
  connect(cell_beta, SIGNAL(textChanged(QString)),
          this, SLOT(set_beta_value(QString)));
  connect(cell_gamma, SIGNAL(textChanged(QString)),
          this, SLOT(set_gamma_value(QString)));
  // set MyAtoms from atomsEdit field:
  connect(atomsEdit, SIGNAL(textChanged()),
          this, SLOT(setAtoms()));
  connect(restrEdit, SIGNAL(textChanged()),
          this, SLOT(setRestraints()));
  // display an error message if there is any error in the
  // format of atoms or restraints:
  connect(this, SIGNAL(line_error(QString)),
          this, SLOT(setInfoLabel(QString)));
  connect(addButton, SIGNAL(clicked()),
          this, SLOT(addFragment()));
  connect(deleteButton, SIGNAL(clicked()),
          this, SLOT(deleteCurrentFragment()));
  connect(deleteButton, SIGNAL(clicked()),
          this, SLOT(updateFraglist()));
  connect(deleteButton, SIGNAL(clicked()),  // the information in this window gets useless,
          this, SLOT(close()));             // thus it is closed.
  connect(updateButton, SIGNAL(clicked()),
          this, SLOT(updateFragment()));
  connect(use_selected, SIGNAL(clicked()),
          this, SLOT(setSelectedAtoms()));
  connect(mailButton, SIGNAL(clicked()),
          this, SLOT(mailHome()));
  connect(renameButton, SIGNAL(clicked()),
          this, SLOT(enterRenameMode()));
  connect(accept_renameButton, SIGNAL(clicked()),
          this, SLOT(leaveRenameMode()));
  connect(clearButton, SIGNAL(clicked()),
          this, SLOT(clearAll()));
}


QSize DSREditWindow::minimumSizeHint() const
{
  return QSize(700, 700);
}

QSize DSREditWindow::sizeHint() const
{
  return QSize(900, 700);
}


void DSREditWindow::clearAll(){
  cell_a->clear();
  cell_b->clear();
  cell_c->clear();
  cell_alpha->clear();
  cell_beta->clear();
  cell_gamma->clear();
  nameEdit->clear();
  resicledit->clear();
  atomsEdit->clear();
  restrEdit->clear();
  restrEdit->append("Add at least one restraint here. (And delete this text)");
  addButton->setDisabled(true);  // Otherwise, it is possible to create a damaged fragment.
  updateButton->setDisabled(true);
  myedgl->clear_molecule();
}

void DSREditWindow::enterRenameMode(){
  //! prepare window to rename atoms
  first_atnames->clear();
  foreach (QString line, atomsEdit->toPlainText().split(QRegExp("\n|\r\n|\r"))) {
    first_atnames->append(line.split(' ').at(0));
  }
  addButton->hide();
  updateButton->hide();
  mailButton->hide();
  restrEdit->setDisabled(true);
  nameEdit->setDisabled(true);
  resicledit->setDisabled(true);
  cell_a->setDisabled(true);
  cell_b->setDisabled(true);
  cell_c->setDisabled(true);
  cell_alpha->setDisabled(true);
  cell_beta->setDisabled(true);
  cell_gamma->setDisabled(true);
  renameButton->hide();
  deleteButton->hide();
  use_selected->setDisabled(true);
  buttonlayout->addWidget(accept_renameButton,  0, 0, 1, 2);
  buttonlayout->addWidget(abort_renameButton,  1, 0, 1, 2);
  accept_renameButton->show();
  abort_renameButton->show();
  atomsEdit->disconnect();
  oldatoms->clear();
  oldatoms->append(atomsEdit->toPlainText());
  oldrestraints->clear();
  oldrestraints->append(restrEdit->toPlainText());
  connect(atomsEdit, SIGNAL(textChanged()),
          this, SLOT(renameAtoms()));
  connect(abort_renameButton, SIGNAL(clicked()),
          this, SLOT(abortRenameMode()));
  restrEdit->disconnect();
  new_header = new DSRMol;
  new_header->atoms.clear();
}

void DSREditWindow::leaveRenameMode(){
  //! go out of rename mode and rename the restraints
  //rename_restraints()
  renameButton->show();
  deleteButton->show();
  restrEdit->setEnabled(true);
  nameEdit->setEnabled(true);
  resicledit->setEnabled(true);
  cell_a->setEnabled(true);
  cell_b->setEnabled(true);
  cell_c->setEnabled(true);
  cell_alpha->setEnabled(true);
  cell_beta->setEnabled(true);
  cell_gamma->setEnabled(true);
  use_selected->setEnabled(true);
  mailButton->show();
  updateButton->show();
  addButton->show();
  accept_renameButton->hide();
  abort_renameButton->hide();
  atomsEdit->disconnect();
  restrEdit->disconnect();
  // re-attach signal after rename mode
  connect(atomsEdit, SIGNAL(textChanged()),
          this, SLOT(setAtoms()));
  connect(restrEdit, SIGNAL(textChanged()),
          this, SLOT(setRestraints()));
  setAtoms();
  setRestraints();
}

void DSREditWindow::abortRenameMode(){
  //! go out of rename mode and do nothing
  //rename_restraints()
  renameButton->show();
  deleteButton->show();
  restrEdit->setEnabled(true);
  nameEdit->setEnabled(true);
  resicledit->setEnabled(true);
  cell_a->setEnabled(true);
  cell_b->setEnabled(true);
  cell_c->setEnabled(true);
  cell_alpha->setEnabled(true);
  cell_beta->setEnabled(true);
  cell_gamma->setEnabled(true);
  use_selected->setEnabled(true);
  mailButton->show();
  updateButton->show();
  addButton->show();
  accept_renameButton->hide();
  abort_renameButton->hide();
  atomsEdit->disconnect();
  restrEdit->disconnect();
  // re-attach signal after rename mode
  connect(atomsEdit, SIGNAL(textChanged()),
          this, SLOT(setAtoms()));
  connect(restrEdit, SIGNAL(textChanged()),
          this, SLOT(setRestraints()));
  atomsEdit->setText(oldatoms->toLatin1());
  restrEdit->setText(oldrestraints->toLatin1());
  myedgl->display_fragment(*m_header);
}

void DSREditWindow::updateFraglist(){
  emit updated(m_nametag);
}

void DSREditWindow::mailHome() {
  //! mail the current fragment to dkratzert@gmx.de
  //! Formating according to RFC 6068
  //! Mac and Windows like to have different mailstrings
  QString mailstring;
#if defined Q_WS_WIN32 || defined Q_OS_WIN
      mailstring = "mailto:dkratzert@gmx.de?"
                   "subject=New%20fragment%20for%20DSR"
                   "&body=Dear%20Daniel,%0D%0A"
                   "please%20add%20this%20fragment%20to%20the%20DSR%20database:%0D%0A%0D%0A"
                   +QString("%1").arg(combineDataToNewFragment().join("%0D%0A"))
                   +"%0D%0A";
#else
      mailstring = "mailto:dkratzert@gmx.de?"
                   "subject=New fragment for DSR"
                   "&body=Dear Daniel,\n"
                   "please add this fragment to the DSR database:\n\n"
                   +QString("%1").arg(combineDataToNewFragment().join("\n"))
                   +"\n";
#endif
  QDesktopServices::openUrl(QUrl(mailstring, QUrl::TolerantMode));
}

void DSREditWindow::addFragment() {
  //! combines new fragment and write db to the harddisk
  QStringList origdb;
  QStringList newdb;
  if (!validateCell()) {
      emit line_error(QString(tr("<font color=red><b>No valid unit cell!</font>")));
      return;
  }
  if (nameEdit->text().isEmpty()) {
      emit line_error(QString(tr("<font color=red><b>Please give the fragment a name.</font>")));
      return;
  }
  origdb = readFragmentDB();
  newdb = combineDataToNewFragment();
  origdb.append(newdb);
  userdb = true;
  writeFragmentDB(origdb);
  this->updateFraglist();
  this->close();
}

void DSREditWindow::updateFragment() {
  //! this method deletes the old and writes the new fragment
  QStringList origdb;
  QStringList newFragment;
  QString delfrag; // I will delete this later
  delfrag = m_nametag;
  if (!validateCell()) {
      emit line_error(QString(tr("<font color=red><b>No valid unit cell!</font>")));
      return;
  }
  if (nameEdit->text().isEmpty()) {
      emit line_error(QString(tr("<font color=red><b>Please give the fragment a name.</font>")));
      return;
  }
  origdb = readFragmentDB();
  newFragment = combineDataToNewFragment();
  origdb.append(newFragment);
  userdb = true;
  writeFragmentDB(origdb);
  deleteFragment(delfrag);
  this->updateFraglist();
  this->close();
}

QStringList DSREditWindow::combineDataToNewFragment(){
  //! combines all collected data to a fragment string for the database
  QStringList fragStringList;
  m_nametag = inventNameTag(5);
  if (resiclass.isEmpty()) {
    resiclass = inventNameTag(4);
  }
  fragStringList.append("\n<"+m_nametag+">");
  fragStringList.append("rem Name: "+fragname+"");
  fragStringList.append("rem Src: ShelXle DSR GUI");
  fragStringList.append("RESI "+resiclass);
  foreach (QStringList line, myRestraints) {
    fragStringList.append(line.join(" "));
  }
  fragStringList.append("FRAG 17 "+QString("%1 %2 %3 %4 %5 %6")
                    .arg(a).arg(b).arg(c)
                    .arg(alpha).arg(beta).arg(gamma));
  QString atline;
  foreach (QStringList line, myAtoms) {
    if (line.length() < 5 ) continue;
    atline.clear();
    atline = QString("%1 %2 %3 %4 %5")
              .arg(line.at(0), -5, ' ')
              .arg(line.at(1), -3, ' ')
              .arg(line.at(2).toDouble(), 10, 'f', 5)
              .arg(line.at(3).toDouble(), 10, 'f', 5)
              .arg(line.at(4).toDouble(), 10, 'f', 5);
    fragStringList.append(atline);
  }
  fragStringList.append("</"+m_nametag+">");
  return fragStringList;
}

bool DSREditWindow::validateCell() {
  //! validates if the cell parameter make sense
  if (a > 0 && b > 0 && c > 0) {
    if ((alpha > 0 && beta > 0 && gamma > 0) && (alpha+beta+gamma <= 360)) {
      return true;
    }
  }
  return false;
}

void DSREditWindow::setDBTypeFromDB(DSRMol *header) {
  //! set userdb false or true if "dsr_db" or "dsr_user_db"
  if (header->dbtype == QString("dsr_user_db")) {
    userdb = true;
  }
}

void DSREditWindow::setInfoLabel(QString myerror) {
  //! display the error of the error signa in infoLabel
  infoLabel->setText(myerror);
}

void DSREditWindow::setNamefromDB(DSRMol *header) {
  //! Set the name of the linedit field nameEdit to the fragment name from the database
  //! This method runs if you select a fragment and then click on the edit button.
  QString name;
  name = header->comment;
  nameEdit->setText(name);
  fragname = name;
}

void DSREditWindow::setResidueFromDB(DSRMol *header) {
  //! get residue from header and populate resiclass variable and edit field
  QString residueClass;
  residueClass = header->residue;
  resicledit->setText(residueClass);
  resiclass = residueClass;
}

void DSREditWindow::setAtomsFromDB(DSRMol *header) {
  //! Fills the atoms edit field with atom data.
  QFont font("Courier");
  atomsEdit->setLineWrapMode(QTextEdit::NoWrap);
  font.setStyleHint(QFont::TypeWriter);
  atomsEdit->setMinimumHeight(200);
  atomsEdit->setFont(font);
  QTextCursor cursor = atomsEdit->textCursor();
  QString atomEditorLine;
  foreach (QStringList atom, header->atoms) {
    myAtoms.append(atom);
    atomEditorLine.clear();
    int num = 0;
    // This relies on the .format() by DSR!:
    foreach (QString column, atom) { // format each column:
      switch (num) {
        case 0:  // name
          column = column.leftJustified(6, ' ');
          break;
        case 1:  // sfac
          column = column.leftJustified(4, ' ');
          break;
        case 2:  // x
          column = column.rightJustified(9, ' ')+"  ";
          break;
        case 3:  // y
          column = column.rightJustified(9, ' ')+"  ";
          break;
        case 4:  // z
          column = column.rightJustified(9, ' ');
          break;
        default:  // anything else, just in case...
          column = column.leftJustified(9, ' ');
          break;
      }
      atomEditorLine = atomEditorLine+column;
      num++;
    }
    atomsEdit->append(atomEditorLine);
  }
  cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
  atomsEdit->setTextCursor(cursor);
}

void DSREditWindow::setRestraintsFromDB(DSRMol *header) {
  //! Fils the restraints edit field with restraints from the db
  QFont font("Courier");
  restrEdit->setLineWrapMode(QTextEdit::NoWrap);
  font.setStyleHint(QFont::TypeWriter);
  restrEdit->setMinimumHeight(200);
  restrEdit->setFont(font);
  QTextCursor cursor = restrEdit->textCursor();
  QStringList restr;
  restr = header->restr;
  myRestraints.clear();
  for (int i=0; i<restr.size(); i++) {
    restrEdit->append(restr.at(i));
    myRestraints.append(restr.at(i).split(" ", skipEmptyParts));
  }
  cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
  restrEdit->setTextCursor(cursor);
}

void DSREditWindow::setCellFromDB(DSRMol *header) {
  // 2
  //! Sets the cell information from the dsr db.
  //! Usually a 1 1 1 90 90 90 cell should come from DSR
  //! because it exports cartesian coordinates for this GUI
  a = header->cell.at(0);
  b = header->cell.at(1);
  c = header->cell.at(2);
  alpha = header->cell.at(3);
  beta = header->cell.at(4);
  gamma = header->cell.at(5);
  cell_a->setText(QString("%1").arg(a));
  cell_b->setText(QString("%1").arg(b));
  cell_c->setText(QString("%1").arg(c));
  cell_alpha->setText(QString("%1").arg(alpha));
  cell_beta->setText(QString("%1").arg(beta));
  cell_gamma->setText(QString("%1").arg(gamma));
  cell_a->setMaximumWidth(getCharWidth(10));
  cell_a->setMinimumWidth(getCharWidth(6));
  cell_b->setMaximumWidth(getCharWidth(10));
  cell_b->setMinimumWidth(getCharWidth(6));
  cell_c->setMaximumWidth(getCharWidth(10));
  cell_c->setMinimumWidth(getCharWidth(6));
  cell_alpha->setMaximumWidth(getCharWidth(10));
  cell_alpha->setMinimumWidth(getCharWidth(6));
  cell_beta->setMaximumWidth(getCharWidth(10));
  cell_beta->setMinimumWidth(getCharWidth(6));
  cell_gamma->setMaximumWidth(getCharWidth(10));
  cell_gamma->setMinimumWidth(getCharWidth(6));
}

void DSREditWindow::checkResiClassInput(QString text) {
  //! check wether the input text to residue class field is valid.
  if (!text[0].isLetter()) {
    resicledit->clear(); // first character has to be a letter!
    resiclass.clear();
    return;
  }
  resiclass.clear();
  resiclass = text.toUpper();
  resicledit->setText(text.left(4).toUpper()); // resi class allows up to 4 characters!
}

void DSREditWindow::setFragmentName(QString name) {
  //! runs if fragment name is changed
  fragname = name;
}


void DSREditWindow::renameRestraints(QStringList new_atnames, int cursopos){
  //! renames the restraints in rename mode
  QTextCursor cursor = atomsEdit->textCursor();
  cursor.setPosition(cursopos, QTextCursor::MoveAnchor);
  QStringList origrestr;
  origrestr = restrEdit->toPlainText().split(QRegExp("\n|\r\n|\r"));
  atomsEdit->setTextCursor(cursor);
  // the previous atoms are the names before I typed a new one and renaming starts
  // I want to rename previous names with new names
  if (new_atnames.length() == previous_atoms->length()) {
    restrEdit->clear();
    foreach (QString rline, origrestr) {
      QStringList rline_list;
      rline_list = rline.split(' ');
      // go through all new atoms and replace previous names with new names in restraints list:
      int num = 0;
      foreach (QString newatom, new_atnames) {
        for(int i = 0; i < rline_list.length(); i++) {
          if (rline_list.at(i).toUpper() == previous_atoms->at(num).toUpper()) {
            rline_list[i] = newatom.toUpper().toLatin1();
          }
        }
        num++;
      }
      restrEdit->append(rline_list.join(" "));
    }
  }
}

void DSREditWindow::renameAtoms(){
  //! Takes the text from atomEdit and fills myAtoms Qvector<QStringlist>
  //! The special thing in this method is that only the Names can be changed
  //! The text after the fourth character will constantly be overwritten by
  //! the previous coordinates.
  //! It should be impossble to delete an atom.
  int cursopos = atomsEdit->textCursor().position();
  myAtoms.clear();
  atomsEdit->disconnect();
  badLine = false;
  QString error;
  QStringList line_split;
  QStringList new_atnames; // <- the new atom names
  int num = 0;
  error = QString(tr(""));
  emit line_error(error);
  int length = atomsEdit->toPlainText().split(QRegExp("\n|\r\n|\r")).length();
  if (previous_atoms->length() == 0) {
    previous_atoms->append(*first_atnames);
  }
  foreach (QString line, atomsEdit->toPlainText().split(QRegExp("\n|\r\n|\r"))) {
    if (length != previous_atoms->length()) { // make sure not to delete an atom!
      atomsEdit->undo();                      // In this case, undo last action
      connect(atomsEdit, SIGNAL(textChanged()),
              this, SLOT(renameAtoms()));
      return;
    }
    line_split = line.split(" ", skipEmptyParts);
    if (line_split.length() > 0){
      line_split[0] = line_split.at(0).left(4);
      new_atnames.append(line_split.at(0).toUpper()); // collect new atom names after after a character was changed
    } else {  // This is to make sure there is no empty line. An empty line will cause the rename mode to abort.
        this->atomsEdit->undo();
        this->abortRenameMode();
        error = QString(tr("<font color=red><b>Do not delete atoms in rename mode!</font>"));
        emit line_error(error);
        return;
    }
    num++;
    if (new_atnames.count(line_split.at(0)) >= 2) {
      // warn user about duplicates and do not rename atom:
      error = QString(tr("<font color=red><b>Attempt to create duplicate atom \"%1\" detected. "
                         "<br>Please make them unique.</font>").arg(line_split.at(0)));
      emit line_error(error);
      atomsEdit->undo();           // In this case, undo last action. Makes duplicates impossible
      connect(atomsEdit, SIGNAL(textChanged()),
              this, SLOT(renameAtoms()));
      return;
    }
    if (!line_split.at(0).contains(QRegExp("^([A-Za-z]{1,3})\\d{0,3}[A-Za-z\\'\\\"\\#]{0,2}\\d{0,1}"))) {
      error = QString(tr("<font color=red><b>Invalid atom name found.</font>"));
      emit line_error(error);
      connect(atomsEdit, SIGNAL(textChanged()),
              this, SLOT(renameAtoms()));
      return;
    }
  }
  atomsEdit->clear();
  int onum = 0;
  foreach (QString line, oldatoms->split(QRegExp("\n|\r\n|\r"))) {
    // use the new names + the previous atoms
    line = new_atnames.at(onum).leftJustified(4, ' ').left(4)+line.remove(0, 4);
    atomsEdit->append(line);
    myAtoms.append(line.split(' ', skipEmptyParts));
    onum++;
  }
  renameRestraints(new_atnames, cursopos);
  previous_atoms->clear();
  previous_atoms->append(new_atnames);
  new_header->atoms.clear();
  foreach(QStringList line, myAtoms) {
    new_header->atoms.append(line);
  }
  connect(atomsEdit, SIGNAL(textChanged()),
          this, SLOT(renameAtoms()));
  myedgl->display_fragment(*new_header);
}


void DSREditWindow::setAtoms(){
  //! takes the text from atomEdit and fills myAtoms Qvector<QStringlist>
  renameButton->setDisabled(true);
  myAtoms.clear();
  badLine = false;
  QString error;
  QStringList line_split;
  QStringList atnames;
  int num = 0;
  foreach (QString line, atomsEdit->toPlainText().split(QRegExp("\n|\r\n|\r"))) {
    line_split = line.split(" ", skipEmptyParts);
    line_split = checkAtomLine(line_split, num+1);
    myAtoms.append(line_split);
    if (line_split.length() > 0){
      atnames.append(line_split.at(0).toUpper());
      if (atnames.count(line_split.at(0).toUpper()) >= 2) {
        error = QString(tr("<font color=red><b>Duplicate atom \"%1\" found! Please make them unique.</font>").arg(line_split.at(0)));
        // Can not do undo(), because it would prevent adding duplicate names with "add selected atoms"
        // and this has to be possible because of residues:
        //atomsEdit->undo();
        emit line_error(error);
        badLine = true;
        //return;
      }
    }
    num++;
  }
  if (myAtoms.size() <= 1 && myAtoms[0].isEmpty()) {
    addButton->setDisabled(true);
    updateButton->setDisabled(true);
    error = QString(tr("<font color=red><b>Please add at least one atom to add/update the fragment.</font>"));
    emit line_error(error);
    return;
  } else {
    addButton->setEnabled(true);
    updateButton->setEnabled(true);
  }
  if (!badLine) {
    emit line_error("");
    addButton->setEnabled(true);
    updateButton->setEnabled(true);
  } else {
    updateButton->setDisabled(true);
    addButton->setDisabled(true);
    return;
  }
}

void DSREditWindow::setSelectedAtoms() {
  //! uses selected atoms for atom field and sets cell accordingly
  atomsEdit->clear();
  int numselect = m_mol->selectedatoms.size();
  QString line;
  QString atname;
  QString firstatom;
  QString lastatom;
  for (int i = 0; i < numselect; i++) {
    line.clear();
    atname.clear();
    // The two split() make sure no illegal suffices remain on the labels:
    atname = m_mol->selectedatoms.at(i).Label.split('_').at(0).split(QChar(187)).at(0);
    if (i == 0) {
      firstatom = atname;
    }
    if (i+1 == numselect) {
      lastatom = atname;
    }
    line = QString("%1 %2 %3 %4 %5")
        .arg(atname, -5)  // Atom name
        .arg(m_mol->selectedatoms.at(i).an+1, -2) // strangely ShelXle stores AN-1 here
        .arg(m_mol->selectedatoms.at(i).pos.x, 10, 'f', 5)  // X
        .arg(m_mol->selectedatoms.at(i).pos.y, 10, 'f', 5)  // Y
        .arg(m_mol->selectedatoms.at(i).pos.z, 10, 'f', 5); // Z
    atomsEdit->append(line);
  }
  // make sure at least some restraints end up in the database:
  if ((restrEdit->toPlainText().simplified().size() < 4)
      && (firstatom.size() > 0) && (lastatom.size() > 0)) {
    restrEdit->clear();
    restrEdit->append(QString("RIGU %1 > %2").arg(firstatom, lastatom));
    restrEdit->append(QString("SIMU %1 > %2").arg(firstatom, lastatom));
  }
  cell_a->setText("1");
  cell_b->setText("1");
  cell_c->setText("1");
  cell_alpha->setText("90");
  cell_beta->setText("90");
  cell_gamma->setText("90");
  updateButton->show();
  addButton->show();
}

QStringList DSREditWindow::checkAtomLine(QStringList line, int num) {
  //! check an atom text line for consistency
  //! like if there are all xyz parameters or the name makes sense
  QString error;
  error.clear();
  bool isAtFloat = false;
  if (!line.isEmpty()) {
    line.at(0).toFloat(&isAtFloat);
  }
  bool is_scatt_int = true;
  if (line.length() >= 2) {
    line.at(1).toFloat(&is_scatt_int);
  }
  if (line.size() > 0 && line.size() < 5) {
    error = QString(tr("<font color=red><b>Error in atom line %1: Too few parameters.</b></font>")).arg(num);
    emit line_error(error);
    badLine = true;
  } else if (line.size() > 5) {
    error = QString(tr("<font color=red><b>Error in atom line %1: Too many parameters.</font>")).arg(num);
    emit line_error(error);
    badLine = true;
  } else if (line.join("").contains(",")) {
    error = QString(tr("<font color=red><b>Error in atom line %1: Numbers should not contain comma.</font>")).arg(num);
    emit line_error(error);
    badLine = true;
  } else if (!line.isEmpty() && line[0][0].isDigit()) {
    error = QString(tr("<font color=red><b>Error in atom line %1: Atoms can not begin with a number.</font>")).arg(num);
    emit line_error(error);
    badLine = true;
  } else if (!line.isEmpty() && line[0].length() > 4) {
    error = QString(tr("<font color=red><b>Error in atom line %1: Atom name must not have more than four characters.</font>")).arg(num);
    emit line_error(error);
    badLine = true;
  } else if (!line.isEmpty() && line[0].contains("_")) {
    error = QString(tr("<font color=red><b>Error in atom line %1: Atom name must not have underscores.</font>")).arg(num);
    emit line_error(error);
    badLine = true;
  } else if (isAtFloat) {
    error = QString(tr("<font color=red><b>Error in atom line %1: Atoms can not be only numbers.</font>")).arg(num);
    emit line_error(error);
    badLine = true;
  } else if (!myisAscii(line.join(""))) {
    error = QString(tr("<font color=red><b>Error in atom line %1: Non-ascii characters are not allowed.</font>")).arg(num);
    emit line_error(error);
    badLine = true;
  } else if (!is_scatt_int) {
    error = QString(tr("<font color=red><b>Error in atom line %1: Scattering factor can be only integer numbers.</font>")).arg(num);
    emit line_error(error);
    badLine = true;
  } else if (!validateCell()) {
    error = QString(tr("<font color=red><b>Please define proper unit cell parameters!</font>"));
    emit line_error(error);
    badLine = true;
  }else {
    int num2 = 0;
    foreach (QString item, line) {
      bool ok = false;
      item.toFloat(&ok);
      if (num2 >= 2 && !ok){
        error = QString(tr("<font color=red><b>Error in atom line %1: columns 3 to 5 contain non-numerical character(s).</font>")).arg(num);
        emit line_error(error);
        badLine = true;
      }
      num2++;
    }
  }
  if (badLine == true) {
      addButton->setDisabled(true);
      updateButton->setDisabled(true);
  } else {
      addButton->setEnabled(true);
      updateButton->setEnabled(true);
  }
  return line;
}

bool DSREditWindow::myisAscii(QString line) {
  //! test if line contains non-ascii characters
  for (int num=0; num<line.length(); num++) {
    if (line.at(num).unicode() > 127) return false;
  }
  return true;
}

void DSREditWindow::setRestraints() {
  //! takes text from restrEdit and fills myRestraints Qvector<QStringlist>
  renameButton->setDisabled(true);
  myRestraints.clear();
  badLine = false;
  QString error;
  QStringList line_split;
  int num = 0;
  foreach (QString line, restrEdit->toPlainText().split(QRegExp("\n|\r\n|\r"))) {
    line_split = line.split(" ", skipEmptyParts);
    checkRestraintLine(line_split, num+1);
    myRestraints.append(line_split);
    num++;
  }
  if (!badLine) {
    emit line_error("");
  } else {
    return;
  }
  if (myRestraints[0].isEmpty()) {
    addButton->setDisabled(true);
    updateButton->setDisabled(true);
    error = QString(tr("<font color=red><b>Please add at least one restraint to add/update the fragment.</font>"));
    emit line_error(error);
  } else {
    addButton->setEnabled(true);
    updateButton->setEnabled(true);
  }
}

void DSREditWindow::checkRestraintLine(QStringList restrline, int num) {
  //! Check each line of the restraints for errors. Especially atoms that
  //! are not in the atoms list.
  bool ok;
  QString error;
  QStringList atomList;
  foreach (QStringList atomline, myAtoms) {
    if (atomline.isEmpty()) continue;
    atomList.append(atomline.at(0).toUpper());
  }
  foreach (QString item, restrline.mid(0, 1)) {
    if (!RESTRAINT_CARDS.contains(item.toUpper())){
      error = QString(tr("<font color=red><b>Invalid restraint %1 in line %2.</font>")).arg(item).arg(num);
      updateButton->setDisabled(true);
      addButton->setDisabled(true);
      emit line_error(error);
      badLine = true;
    }
    if (badLine == true) {
      return;
    }
  }
  foreach (QString item, restrline.mid(1, -1)) {
    item.toFloat(&ok);
    if (ok) {continue;}
    if (item == ">") {continue;}
    if (item == "<") {continue;}
    if (item == "_") {continue;}
    if (item == "$") {continue;}
    if (!atomList.contains(item.toUpper())) {
      error = QString(tr("<font color=red><b>Atom %1 in restraints line %2 not in atom list!</font>")).arg(item).arg(num);
      updateButton->setDisabled(true);
      addButton->setDisabled(true);
      emit line_error(error);
      badLine = true;
    }
    if (badLine == true) {
      return;
    }
    error = "";
    emit line_error(error);
  }
  num++;
}

void DSREditWindow::makeUnitCellEdit(){
  //! The unit cell input field
  cellLayout = new QHBoxLayout;
  cell_a = new QLineEdit;
  cell_b = new QLineEdit;
  cell_c = new QLineEdit;
  cell_alpha = new QLineEdit;
  cell_beta = new QLineEdit;
  cell_gamma = new QLineEdit;
  QLabel *alabel = new QLabel("a:");
  QLabel *blabel = new QLabel("b:");
  QLabel *clabel = new QLabel("c:");
  QLabel *alphalabel = new QLabel(QChar(0xb1, 0x03)+QString(":"));
  QLabel *betalabel = new QLabel(QChar(0xb2, 0x03)+QString(":"));
  QLabel *gammalabel = new QLabel(QChar(0xb3, 0x03)+QString(":"));
  cellLayout->addWidget(alabel);
  cellLayout->addWidget(cell_a);
  cellLayout->addWidget(blabel);
  cellLayout->addWidget(cell_b);
  cellLayout->addWidget(clabel);
  cellLayout->addWidget(cell_c);
  cellLayout->addWidget(alphalabel);
  cellLayout->addWidget(cell_alpha);
  cellLayout->addWidget(betalabel);
  cellLayout->addWidget(cell_beta);
  cellLayout->addWidget(gammalabel);
  cellLayout->addWidget(cell_gamma);
  // TODO: better validator needed:
  QDoubleValidator *cellaValidator = new QDoubleValidator(0, 999, 6, cell_a);
  cellaValidator->setNotation(QDoubleValidator::StandardNotation);
  QDoubleValidator *cellbValidator = new QDoubleValidator(0, 999, 6, cell_b);
  cellbValidator->setNotation(QDoubleValidator::StandardNotation);
  QDoubleValidator *cellcValidator = new QDoubleValidator(0, 999, 6, cell_c);
  cellcValidator->setNotation(QDoubleValidator::StandardNotation);
  QDoubleValidator *cellalValidator = new QDoubleValidator(0, 360, 6, cell_alpha);
  cellalValidator->setNotation(QDoubleValidator::StandardNotation);
  QDoubleValidator *cellbeValidator = new QDoubleValidator(0, 360, 6, cell_beta);
  cellbeValidator->setNotation(QDoubleValidator::StandardNotation);
  QDoubleValidator *cellgaValidator = new QDoubleValidator(0, 360, 6, cell_gamma);
  cellgaValidator->setNotation(QDoubleValidator::StandardNotation);
  //connect(cell_a, SIGNAL(textChanged(QString)),cell_a, SLOT(validate_cellinput()));
  cell_a->setValidator(cellaValidator);
  cell_b->setValidator(cellbValidator);
  cell_c->setValidator(cellcValidator);
  cell_alpha->setValidator(cellalValidator);
  cell_beta->setValidator(cellbeValidator);
  cell_gamma->setValidator(cellgaValidator);
  cell_a->setMaximumWidth(getCharWidth(7));
  cell_b->setMaximumWidth(getCharWidth(7));
  cell_c->setMaximumWidth(getCharWidth(7));
  cell_alpha->setMaximumWidth(getCharWidth(7));
  cell_beta->setMaximumWidth(getCharWidth(7));
  cell_gamma->setMaximumWidth(getCharWidth(7));
}

void DSREditWindow::set_a_value(QString a) {
  cell_a->setStyleSheet("QLineEdit");
  if (a.contains(",")){
    a.replace(",", ".");
    cell_a->setText(a);
  }
  bool ok;
  this->a = a.toDouble(&ok);
  if (!ok) {
    cell_a->setStyleSheet("QLineEdit{background: rgb(255, 180, 180);}");
  }
  if (validateCell()) {
    emit line_error("");
  } else {
    emit line_error(QString(tr("<font color=red><b>Please define proper unit cell parameters!</font>")));
  }
}

void DSREditWindow::set_b_value(QString b) {
  cell_b->setStyleSheet("QLineEdit");
  if (b.contains(",")){
    b.replace(",", ".");
    cell_b->setText(b);
  }
  bool ok;
  this->b = b.toDouble(&ok);
  if (!ok) {
    cell_b->setStyleSheet("QLineEdit{background: rgb(255, 180, 180);}");
  }
  if (validateCell()) {
    emit line_error("");
  } else {
    emit line_error(QString(tr("<font color=red><b>Please define proper unit cell parameters!</font>")));
  }
}
void DSREditWindow::set_c_value(QString c) {
  cell_c->setStyleSheet("QLineEdit");
  if (c.contains(",")){
    c.replace(",", ".");
    cell_c->setText(c);
  }
  bool ok;
  this->c = c.toDouble(&ok);
  if (!ok) {
    cell_c->setStyleSheet("QLineEdit{background: rgb(255, 180, 180);}");
  }
  if (validateCell()) {
    emit line_error("");
  } else {
    emit line_error(QString(tr("<font color=red><b>Please define proper unit cell parameters!</font>")));
  }
}

void DSREditWindow::set_alpha_value(QString alpha) {
  cell_alpha->setStyleSheet("QLineEdit");
  if (alpha.contains(",")){
    alpha.replace(",", ".");
    cell_alpha->setText(alpha);
  }
  bool ok;
  this->alpha = alpha.toDouble(&ok);
  if (!ok) {
    cell_alpha->setStyleSheet("QLineEdit{background: rgb(255, 180, 180);}");
  }
  if (validateCell()) {
    emit line_error("");
  } else {
    emit line_error(QString(tr("<font color=red><b>Please define proper unit cell parameters!</font>")));
  }
}

void DSREditWindow::set_beta_value(QString beta) {
  cell_beta->setStyleSheet("QLineEdit");
  if (beta.contains(",")){
    beta.replace(",", ".");
    cell_beta->setText(beta);
  }
  bool ok;
  this->beta = beta.toDouble(&ok);
  if (!ok) {
    cell_beta->setStyleSheet("QLineEdit{background: rgb(255, 180, 180);}");
  }
  if (validateCell()) {
    emit line_error("");
  } else {
    emit line_error(QString(tr("<font color=red><b>Please define proper unit cell parameters!</font>")));
  }
}

void DSREditWindow::set_gamma_value(QString gamma) {
  cell_gamma->setStyleSheet("QLineEdit");
  if (gamma.contains(",")){
    gamma.replace(",", ".");
    cell_gamma->setText(gamma);
  }
  bool ok;
  this->gamma = gamma.toDouble(&ok);
  if (!ok) {
    cell_gamma->setStyleSheet("QLineEdit{background: rgb(255, 180, 180);}");
  }
  if (validateCell()) {
    emit line_error("");
  } else {
    emit line_error(QString(tr("<font color=red><b>Please define proper unit cell parameters!</font>")));
  }
}

int DSREditWindow::getCharWidth(int numchars) {
  //! returns the width of a numchar amount of characters
  QString buchstaben;
  buchstaben.clear();
  for (int i=1; i<=numchars; i++) {
    buchstaben += "#";
  }

#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
  return QFontMetrics(this->font()).horizontalAdvance(buchstaben);
}
#else
    return QFontMetrics(this->font()).width(buchstaben);
}
#endif

void DSREditWindow::deleteCurrentFragment(){
  //! delete the currently open fragment
  deleteFragment(m_nametag);
}

void DSREditWindow::deleteFragment(QString nametag){
  //! deletes the fragment with tag "nametag" from and only
  //! from the dsr_user_db.txt.
  QStringList udb =  readFragmentDB();
  QStringList udb2;
  udb2.clear();
  int startlineNumber, endlineNumber;
  startlineNumber = -1;
  endlineNumber = -1;
  for (int i=0; i<udb.size(); i++) {
    if (udb[i].toUpper().startsWith("<"+nametag.toUpper()+">")){
      if (startlineNumber > 0) {
        setInfoLabel(QString(tr("Duplicate entry for %1 found in the users database")).arg(nametag));
        return;} // in this case we have a dublicate entry
      startlineNumber = i;
    }
    if (udb[i].toUpper().startsWith("</"+nametag.toUpper()+">")){
      if (endlineNumber > 0) {
        setInfoLabel(QString(tr("Duplicate entry for %1 found in the users database")).arg(nametag));
        return;} // in this case we have a dublicate entry
      endlineNumber = i;
    }
  }
  if ((startlineNumber < 0) || (endlineNumber < 0)) {
    setInfoLabel(QString(tr("Start or end tag %1 missing in the users database")).arg(nametag));
    return; // in this case any of the db tags was not found
  }
  if (startlineNumber > 0) {
    udb2 = udb.mid(0, startlineNumber);
  }
  udb2 = udb2+udb.mid(endlineNumber+1, -1);
  writeFragmentDB(udb2);
}

QStringList DSREditWindow::readFragmentDB() {
  //! read the entire user databse into a stringlist
  QFile file(mydsrdbpath+"/dsr_user_db.txt");
  QStringList stringList;
  if (file.open(QFile::ReadOnly | QFile::Text)) {
    QTextStream textStream(&file);
    while (true) {
      QString line = textStream.readLine();
      if (line.isNull()) {
          break;
      }
      if (line.isEmpty()) {
        continue;
      } else {
        stringList.append(line);
      }
    }
  }
  file.close();
  return stringList;
}

void DSREditWindow::writeFragmentDB(QStringList database) {
  //! write the entire user databse into a text file
  if (userdb == false) {
    return;
  } // never delete from main DB
  QFile file(mydsrdbpath+"/dsr_user_db.txt");
  if (file.open(QFile::ReadWrite | QFile::Text)) {
    QTextStream out(&file);
    file.resize(0); // delete original
    out << database.join("\n");
  }
  file.close();
}

bool DSREditWindow::doesFragExist(QString name, bool fullname=false) {
  //! checks if a fragment nametag is already in the database
  //! setting fullname to true checks for the full name instead of the tag
  int column = 0;
  if (fullname) {
    column = 1;
  }
  foreach (QStringList line, m_frags) {
    if (line.at(column).toLower() == name.toLower()) {
      return true;
    }
  }
  return false;
}

QString DSREditWindow::randString(int len) {
  //! returns a random string of size len
  //! from http://stackoverflow.com/questions/3244999/create-a-random-string-or-number-in-qt4
  QString str;
  str.resize(len);
  for (int s = 0; s < len ; ++s) {
    str[s] = QChar('A' + char(qrand() % ('Z' - 'A')));
  }
  return str;
}

QString DSREditWindow::inventNameTag(int length=5){
  //! invents a random name tag for a new fragment
  QString name;
  bool there = true;
  while (there) {
    name = randString(length);
    there = doesFragExist(name, false);
  }
  return name;
}

