#include <stdio.h>
#include <QTabWidget>
#include <QFileDialog>
#include <QDir>
#include <QFile>
#include <QTextStream>
#include <QXmlSimpleReader>
#include <QMessageBox>
#include <QXmlInputSource>
#include <QXmlAttributes>
#include <QSocketNotifier>
#include <QtDebug>
#include "AardMain.h"
#include "RemoteTable.h"
#include "Blinky.h"
#include "OneSample.h"
#include "OneButton.h"
#include "RenameRemote.h"
#include "ButtonEditor.h"
#include "Commands.h"
#include "RawIR.h"
#include "AardRemoteMessage.h"
#include "LogWindow.h"

int AardMain::remote_num = 0;

AardMain::AardMain() {
   aard_notify = NULL;
   ui.setupUi(this);
   ui.statusbar->show();
   connect(ui.actionExit, SIGNAL(triggered()), qApp, SLOT(quit()));

   connect(ui.actionOpenAardRemote, SIGNAL(triggered()), this,
           SLOT(doOpenAardRemote()));
   connect(ui.actionNew, SIGNAL(triggered()), this, SLOT(doNewRemote()));
   connect(ui.actionOpen, SIGNAL(triggered()), this, SLOT(doOpenConfig()));
   connect(ui.actionSave, SIGNAL(triggered()), this, SLOT(doSaveConfig()));
   connect(ui.actionSave_As, SIGNAL(triggered()), this, SLOT(doSaveConfigAs()));
   connect(ui.actionButtonDetails, SIGNAL(triggered()), this,
           SLOT(doButtonDetails()));
   connect(ui.actionTest, SIGNAL(triggered()), this, SLOT(doTest()));
   connect(ui.actionClose, SIGNAL(triggered()), this, SLOT(doClose()));
   connect(ui.actionDelete_Remote, SIGNAL(triggered()), this,
           SLOT(doDeleteRemote()));
   connect(ui.actionDelete_Button, SIGNAL(triggered()), this,
           SLOT(doDeleteButton()));
   connect(ui.actionRecord_Samples, SIGNAL(toggled(bool)), this,
           SLOT(doRecordingToggle(bool)));
   connect(ui.actionRename_Remote, SIGNAL(triggered()), this,
           SLOT(doRenameRemote()));
   connect(ui.actionView_Log, SIGNAL(triggered()), this,
           SLOT(doViewLog()));

   QWidget * initial_tab = ui.tabWidget->currentWidget();
   initNewTab();
   ui.tabWidget->removeTab(ui.tabWidget->indexOf(initial_tab));

   blinky = new Blinky(ui.statusbar);
   ui.statusbar->addPermanentWidget(blinky);

   rename_dialog = new RenameRemote(this);

   file_dialog = new QFileDialog(this);
   file_dialog->setDefaultSuffix("aar");
   file_dialog->setResolveSymlinks(false);
   file_dialog->setViewMode(QFileDialog::List);
   file_dialog->setDirectory(QDir::current());

   QStringList filters;
   filters << "Aardlearn files (*.aar)"
           << "All Files (*)";
   file_dialog->setFilters(filters);

   dev_dialog = new QFileDialog(this);
   dev_dialog->setResolveSymlinks(false);
   dev_dialog->setViewMode(QFileDialog::List);
   dev_dialog->setDirectory("/dev");
   dev_dialog->setFilter((QDir::Filters)-1);

   button_editor = new ButtonEditor(ui.actionRecord_Samples, this);

   log_window = new LogWindow(&aard_device, this);
}

AardMain::~AardMain() {
}

bool
AardMain::startDocument() {
   DeleteAllTabs();
   reading_remote = NULL;
   reading_button = NULL;
   reading_characters.clear();
   reader_error.clear();
   first_element = true;
   reader_current_tab = -1;
   return true;
}

bool
AardMain::startElement(const QString & /* namespaceURI */,
   const QString & localName,
   const QString & /* qName */,
   const QXmlAttributes & atts) {
   if (first_element) {
      if (localName != "aardlearn") {
         reader_error = "Not an aardlearn format data file.";
         return false;
      }
      first_element = false;
   }
   if (localName == "aardlearn") {
      QString version = atts.value("version");
      if (version != "1") {
         reader_error = "Not a version 1 aardlearn data file.";
         return false;
      }
      bool ok;
      reader_current_tab = OptionalAttributeValue(atts, "current", -1, &ok);
      if (! ok) {
         return false;
      }
   } else if (localName == "remote") {
      QString name = atts.value("name");
      initNewTab();
      int new_tab_num = ui.tabWidget->count() - 1;
      ui.tabWidget->setTabText(new_tab_num, name);
      reading_remote = dynamic_cast<RemoteTable *>(
                          ui.tabWidget->widget(new_tab_num));
      if (reading_remote == NULL) {
         reader_error = "Internal error creating remote definition.";
         return false;
      }
      bool ok;
      reader_col0_width = OptionalAttributeValue(atts, "w0", -1, &ok);
      if (! ok) {
         return false;
      }
      reader_col1_width = OptionalAttributeValue(atts, "w1", -1, &ok);
      if (! ok) {
         return false;
      }
      reader_current_row = OptionalAttributeValue(atts, "row", -1, &ok);
      if (! ok) {
         return false;
      }
   } else if (localName == "button") {
      QString name = atts.value("name");
      if (reading_remote == NULL) {
         reader_error = "Button defined outside of remote.";
         return false;
      }
      int new_button_row = reading_remote->rowCount() - 1;
      reading_button = dynamic_cast<OneButton *>(
                          reading_remote->item(new_button_row, 0));
      if (reading_button == NULL) {
         reader_error = "Internal error creating button definition.";
         return false;
      }
      reading_button->setText(name);
   }
   return true;
}

bool
AardMain::endElement(const QString & /* namespaceURI */,
   const QString & localName,
   const QString & /* qName */) {
   if (localName == "remote") {
      if (reader_col0_width > 0) {
         reading_remote->setColumnWidth(0, reader_col0_width);
      }
      if (reader_col1_width > 0) {
         reading_remote->setColumnWidth(1, reader_col1_width);
      }
      if ((reader_current_row >= 0) &&
          (reader_current_row < reading_remote->rowCount())) {
         reading_remote->setCurrentCell(reader_current_row, 0);
      }
      reading_remote = NULL;
      reading_button = NULL;
      reading_characters.clear();
   } else if (localName == "button") {
      reading_button = NULL;
      reading_characters.clear();
   } else if (localName == "sample") {
      if (reading_button == NULL) {
         reader_error = "Sample not defined in button.";
         return false;
      }
      QTextStream strin(&reading_characters, QIODevice::ReadOnly);
      recording.clear();
      while (! strin.atEnd()) {
         char c;
         unsigned int val;
         unsigned short data;
         strin.skipWhiteSpace();
         strin >> c;
         strin >> val;
         if (c == 'p') {
            data = BGIR_MARK | (unsigned short)BGIR_DURATION(val);
         } else if (c == 's') {
            data = BGIR_SPACE | (unsigned short)BGIR_DURATION(val);
         } else {
            reader_error = "Bad format sample data.";
            return false;
         }
         recording.push_back(data);
      }
      reading_characters.clear();
      reading_button->AppendSample(new OneSample(recording));
      recording.clear();
   }
   return true;
}

bool
AardMain::characters(const QString & ch) {
   reading_characters = ch;
   return true;
}

bool
AardMain::endDocument() {
   reading_remote = NULL;
   reading_button = NULL;
   reading_characters.clear();
   if ((ui.tabWidget->count() == 0) || (! reader_error.isEmpty())) {
      doClose();
   }
   reader_error.clear();
   if ((reader_current_tab >= 0) &&
       (reader_current_tab < ui.tabWidget->count())) {
      ui.tabWidget->setCurrentIndex(reader_current_tab);
   }
   return true;
}

QString
AardMain::errorString() const {
   return reader_error;
}

bool
AardMain::fatalError(const QXmlParseException & exception) {
   QMessageBox::warning(this,
                        "Aardlearn",
                        QString("Error at line %1, column %2:\n%3")
                                .arg(exception.lineNumber())
                                .arg(exception.columnNumber())
                                .arg(exception.message()));
   endDocument();
   return false;
}

int
AardMain::OptionalAttributeValue(
   const QXmlAttributes & atts,
   const QString        & qName,
   int                    defval,
   bool                 * ok) {

   *ok = true;
   QString valstr = atts.value(qName);
   if (valstr.isEmpty()) {
      return defval;
   }
   defval = valstr.toLong(ok);
   if (! ok) {
      reader_error = QString("Invalid integer attribute: %1.").arg(qName);
   }
   return defval;
}

void
AardMain::SendSample(OneSample * samp) {
   if (! aard_device.HasError()) {
      aard_device.SendCommand(MKCR_SENDRAW, samp->Data(), samp->Length());
   }
}

void
AardMain::doOpenAardRemote() {
   if (aard_notify != NULL) {
      aard_notify->setEnabled(false);
      disconnect(aard_notify, SIGNAL(activated(int)), this,
                 SLOT(doRecordData()));
      delete aard_notify;
      aard_notify = NULL;
   }
   dev_dialog->setAcceptMode(QFileDialog::AcceptOpen);
   dev_dialog->setFileMode(QFileDialog::ExistingFile);
   dev_dialog->setReadOnly(true);
   int stat = dev_dialog->exec();
   if (stat == QDialog::Accepted) {
      QStringList files = dev_dialog->selectedFiles();
      if (files.isEmpty()) {
         return;
      }
      dev_name = files.first();
      int fd = aard_device.OpenAardRemote(dev_name.toAscii().constData());
      if (fd >= 0) {
         aard_device.WaitForStartup();
         aard_notify = new QSocketNotifier(fd, QSocketNotifier::Read, this);
         connect(aard_notify, SIGNAL(activated(int)), this,
                 SLOT(doRecordData()));
         aard_notify->setEnabled(true);
      } else {
         // PUT CODE HERE - popup error message...
      }
   }
}

void
AardMain::doImport() {
   // PUT CODE HERE
}

void
AardMain::doButtonDetails() {
   OneButton * the_button = NULL;
   RemoteTable * remote =
      dynamic_cast<RemoteTable *>(ui.tabWidget->currentWidget());
   if (remote != NULL) {
      the_button =
         dynamic_cast<OneButton *>(remote->item(remote->currentRow(), 0));
   }
   button_editor->doEditButton(the_button);
}

void
AardMain::doTest() {
   qDebug() << "doTest";
   // PUT CODE HERE - Try to build the state table the recognizer needs,
   // then run it to see if we recognize buttons, record recognized buttons
   // in their entries so we can show check mark rather than question.
}

void
AardMain::doNewRemote() {
   initNewTab();
}

void
AardMain::doOpenConfig() {
   if (! GetConfigFileName(true)) {
      return;
   }

   QXmlSimpleReader reader;
   reader.setContentHandler(this);
   reader.setErrorHandler(this);

   QFile file(config_file_name);
   if (!file.open(QFile::ReadOnly | QFile::Text)) {
      QMessageBox::warning(this, tr("Aardlearn"),
                           tr("Cannot read file %1:\n%2.")
                            .arg(config_file_name)
                            .arg(file.errorString()));
      return;
   }

   QXmlInputSource xmlInputSource(&file);
   reader.parse(xmlInputSource);
}

void
AardMain::doSaveConfig() {
   if (config_file_name.isEmpty()) {
      if (! GetConfigFileName(false)) {
         return;
      }
   }
   WriteConfigFile();
}

void
AardMain::doSaveConfigAs() {
   if (! GetConfigFileName(false)) {
      return;
   }
   WriteConfigFile();
}

void
AardMain::doDeleteRemote() {
   if (ui.tabWidget->count() <= 1) {
      doClose();
   } else {
      QWidget * cur_remote = ui.tabWidget->currentWidget();
      if (cur_remote != NULL) {
         ui.tabWidget->removeTab(ui.tabWidget->currentIndex());
         delete cur_remote;
      }
   }
}

void
AardMain::doDeleteButton() {
   RemoteTable * table =
      dynamic_cast<RemoteTable *>(ui.tabWidget->currentWidget());
   if (table != NULL) {
      table->removeRow(table->currentRow());
      table->CheckLastRow();
   }
}

void
AardMain::doClose() {
   DeleteAllTabs();
   initNewTab();
}

void
AardMain::doRecordingToggle(bool checked) {
   if (aard_device.HasError()) {
      return;
   }
   if (checked) {
      aard_device.SendCommand(MKCR_RECEIVER, 1);
   } else {
      aard_device.SendCommand(MKCR_RECEIVER, 0);
   }
   recording.clear();
}

void
AardMain::doRecordData() {
   aard_notify->setEnabled(false);
   if (aard_device.HasError()) {
      return;
   }
   while (aard_device.ReadMoreData() > 0) {
      AardRemoteMessage * msg = aard_device.PollMessage();
      if (msg != NULL) {
         if ((msg->MessageKind() == MK_ASYNC) &&
             (msg->FunctionCode() == MKAS_RAWIR)) {
            blinky->SetIncomingIR(true);
            const std::vector<unsigned short> & dat = msg->MessageBody();
            for (unsigned int i = 1; i < dat.size(); ++i) {
               unsigned short code = dat[i];
               if (BGIR_IS_STATUS(code)) {
                  if (code & BGIR_END) {
                     blinky->SetIncomingIR(false);
                     QTableWidget * table =
                        dynamic_cast<QTableWidget *>(ui.tabWidget->
                           currentWidget());
                     if (table != NULL) {
                        OneButton * button =
                           dynamic_cast<OneButton *>(table->
                              item(table->currentRow(), 0));
                        if (button != NULL) {
                           button->AppendSample(new OneSample(recording));
                        }
                     }

                     // Through with that sequence, clear buffer to accumulate
                     // the next.

                     recording.clear();
                  }
                  // PUT CODE HERE - notice error flags in status
               } else {
                  recording.push_back(code);
               }
            }
         }
         delete msg;
      }
   }
   if (! aard_device.HasError()) {
      aard_notify->setEnabled(true);
   }
}

void
AardMain::doRenameRemote() {
   rename_dialog->doRenameTab(ui.tabWidget);
}

void
AardMain::doViewLog() {
   log_window->show();
}

void
AardMain::initNewTab() {
   QTabWidget * tab_widget = ui.tabWidget;
   ++remote_num;
   QString tab_label = QString("Remote %1").arg(remote_num);
   RemoteTable * table = new RemoteTable;
   tab_widget->addTab(table, tab_label);
   tab_widget->setCurrentWidget(table);
   table->setFocus();
}

void
AardMain::DeleteAllTabs() {
   int num = ui.tabWidget->count();
   for (int i = num-1; i >= 0; --i) {
      QWidget * remote_i = ui.tabWidget->widget(i);
      ui.tabWidget->removeTab(i);
      delete remote_i;
   }
   remote_num = 0;
}

bool
AardMain::GetConfigFileName(bool for_reading) {
   if (for_reading) {
      file_dialog->setAcceptMode(QFileDialog::AcceptOpen);
      file_dialog->setFileMode(QFileDialog::ExistingFile);
      file_dialog->setReadOnly(true);
   } else {
      file_dialog->setAcceptMode(QFileDialog::AcceptSave);
      file_dialog->setConfirmOverwrite(true);
      file_dialog->setFileMode(QFileDialog::AnyFile);
      file_dialog->setReadOnly(false);
   }
   int stat = file_dialog->exec();
   if (stat == QDialog::Accepted) {
      QStringList files = file_dialog->selectedFiles();
      if (files.isEmpty()) {
         return false;
      }
      config_file_name = files.first();
      return true;
   } else {
      return false;
   }
}

void
AardMain::WriteConfigFile() {
   QFile outfile(config_file_name);
   if (outfile.open(QFile::WriteOnly | QFile::Truncate)) {
      QTextStream outstr(&outfile);

      // Output header for whole config

      int current = 0;
      QTabWidget * tabs = ui.tabWidget;
      if (tabs != NULL) {
         current = tabs->currentIndex();
      }

      outstr << "<aardlearn version=\"1\" current=\"" << current << "\">" << endl;

      if (tabs != NULL) {
         int num_tabs = tabs->count();
         for (int t = 0; t < num_tabs; ++t) {
            QString tab_name = tabs->tabText(t);
            RemoteTable * remote = dynamic_cast<RemoteTable *>(tabs->widget(t));
            if (remote != NULL) {

               // Output each remote

               WriteRemote(outstr, tab_name, remote);

            }
         }
      }

      outstr << "</aardlearn>" << endl;

      outstr.flush();
      outfile.close();
   }
}

void
AardMain::WriteRemote(
   QTextStream & outstr,
   QString tab_name,
   RemoteTable * remote) {

   int w0 = remote->columnWidth(0);
   int w1 = remote->columnWidth(1);
   int row = remote->currentRow();
   outstr << "  <remote name=\"" << tab_name <<
             "\" w0=\"" << w0 << "\" w1=\"" << w1 << "\" row=\"" << row <<
             "\">" << endl;

   int num_buttons = remote->rowCount();
   for (int b = 0; b < num_buttons; ++b) {
      OneButton * button =
         dynamic_cast<OneButton *>(remote->item(b, 0));
      if (button != NULL) {
         QString button_name = button->text();
         if (! button_name.isEmpty()) {

            // Output each button

            WriteButton(outstr, button_name, button);

         }
      }
   }

   outstr << "  </remote>" << endl;
}

void
AardMain::WriteButton(
   QTextStream & outstr,
   QString button_name,
   OneButton * button) {

   outstr << "    <button name=\"" << button_name << "\">" << endl;

   unsigned int num_samples = button->NumSamples();
   for (unsigned int s = 0; s < num_samples; ++s) {
      WriteSample(outstr, (*button)[s]);
   }

   outstr << "    </button>" << endl;
}

void
AardMain::WriteSample(QTextStream & outstr, OneSample * sample) {
   outstr << "      <sample>";

   unsigned int sample_len = sample->Length();
   QString sep("");
   QString newsep(" ");
   for (unsigned int s = 0; s < sample_len; ++s) {
      unsigned short data = (*sample)[s];
      outstr << sep;
      outstr << (BGIR_IS_MARK(data) ? "p" : "s");
      outstr << (BGIR_DURATION(data));
      sep = newsep;
   }

   outstr << "</sample>" << endl;
}

bool
AardMain::AnalyzeData() {
#ifdef not_defined
   bool rval;
   CopyData();
   rval = FirstAdHocAnalysis();
   if (! rval) {
      RestoreCopy();
   }
   return rval;
#endif
   return false;
}

void
AardMain::CopyData() {
   QTabWidget * tabs = ui.tabWidget;
   if (tabs != NULL) {
      int num_tabs = tabs->count();
      for (int t = 0; t < num_tabs; ++t) {
         RemoteTable * remote = dynamic_cast<RemoteTable *>(tabs->widget(t));
         if (remote != NULL) {
            remote->CopyData();
         }
      }
   }
}

void
AardMain::RestoreCopy() {
   QTabWidget * tabs = ui.tabWidget;
   if (tabs != NULL) {
      int num_tabs = tabs->count();
      for (int t = 0; t < num_tabs; ++t) {
         RemoteTable * remote = dynamic_cast<RemoteTable *>(tabs->widget(t));
         if (remote != NULL) {
            remote->RestoreCopy();
         }
      }
   }
}
