/*!
  \file
  \brief S-format loader

  \author Satofumi KAMIMURA

  $Id$
*/

#include "SloaderWidget.h"
#include "ProgressWidget.h"
#include "TransmitThread.h"
#include "FindComPorts.h"
#include "UrgUsbCom.h"
#include "SerialDevice.h"
#include <QTimer>
#include <QSettings>
#include <QFileDialog>
#include <QShortcut>
#include <QCloseEvent>

using namespace qrk;
using namespace std;


namespace
{
  const char* Organization = "QRK";
  const char* Application = "gsloader";
}


struct SloaderWidget::pImpl
{
  SloaderWidget* widget_;
  ProgressWidget progress_widget_;
  TransmitThread transmit_thread_;
  QString default_dir_;
  size_t file_lines_;
  QTimer update_timer_;
  SerialDevice serial_;
  QString received_text_;


  pImpl(SloaderWidget* widget)
    : widget_(widget), default_dir_("./"), file_lines_(0)
  {
  }


  void initializeForm(void)
  {
    connect(widget_->rescan_button_, SIGNAL(clicked()),
            widget_, SLOT(rescanDevice()));
    connect(widget_->path_button_, SIGNAL(clicked()),
            widget_, SLOT(openFile()));
    connect(widget_->load_button_, SIGNAL(clicked()),
            widget_, SLOT(loadFile()));
    connect(&transmit_thread_, SIGNAL(fail()),
            widget_, SLOT(transmitFailed()));
    connect(&transmit_thread_, SIGNAL(success()),
            widget_, SLOT(transmitSuccessed()));
    connect(&transmit_thread_, SIGNAL(percent(int)),
            &progress_widget_, SLOT(setProgress(int)));
    connect(&update_timer_, SIGNAL(timeout()),
            widget_, SLOT(displayReceiveData()));
    connect(widget_->send_lineedit_, SIGNAL(returnPressed()),
            widget_, SLOT(setSendText()));

    (void) new QShortcut(Qt::CTRL + Qt::Key_Q, widget_, SLOT(close()));
  }


  void saveSettings(void)
  {
    QSettings settings(Organization, Application);

    settings.setValue("geometry", widget_->saveGeometry());
    settings.setValue("progress_geometry", progress_widget_.saveGeometry());
    settings.setValue("default_dir", default_dir_);

    settings.setValue("mot_file", widget_->file_lineedit_->text());
  }


  void loadSettings(void)
  {
    QSettings settings(Organization, Application);

    widget_->restoreGeometry(settings.value("geometry").toByteArray());
    progress_widget_.
      restoreGeometry(settings.value("progress_geometry").toByteArray());

    default_dir_ = settings.value("default_dir", "./").toString();

    QString file_path = settings.value("mot_file", "").toString();
    widget_->file_lineedit_->setText(file_path);
  }


  void setLoadEnabled(void)
  {
    file_lines_ = fileLines();
    bool enable = (widget_->com_combobox_->count() > 0) && (file_lines_ > 0);

    widget_->load_button_->setEnabled(enable);
  }


  size_t fileLines(void)
  {
    // ファイルを読みだし、行数を返す
    QString file_name = widget_->file_lineedit_->text();
    if (file_name.isEmpty()) {
      return 0;
    }

    QFile file(file_name);
    if (! file.open(QIODevice::ReadOnly | QIODevice::Text)) {
      return 0;
    }

    size_t lines = 0;
    while (! file.atEnd()) {
      QByteArray line = file.readLine();
      ++lines;
    }

    return lines;
  }


  void setDefaultDir(const QString dir_name)
  {
    default_dir_ = dir_name;
  }
};


SloaderWidget::SloaderWidget(QWidget* parent)
  : QWidget(parent), pimpl(new pImpl(this))
{
  setupUi(this);
  pimpl->initializeForm();
  pimpl->loadSettings();

  rescanDevice();
}


SloaderWidget::~SloaderWidget(void)
{
  pimpl->saveSettings();
}


void SloaderWidget::closeEvent(QCloseEvent* event)
{
  pimpl->progress_widget_.hide();

  event->accept();
}


void SloaderWidget::setFile(QString file)
{
  file_lineedit_->setText(file);
  QDir dir(file);
  pimpl->setDefaultDir(QDir::cleanPath(dir.absolutePath()));
  pimpl->setLoadEnabled();

  if (load_button_->isEnabled()) {
    loadFile();
  }
}


void SloaderWidget::rescanDevice(void)
{
  com_combobox_->clear();

  FindComPorts com_finder;
  com_finder.addBaseName("/dev/ttyACM");
  com_finder.addBaseName("/dev/tty.usbmodem");
  com_finder.addDriverName("URG Series USB Device Driver");
  com_finder.addDriverName("URG-X002 USB Device Driver");

  vector<string> devices;
  com_finder.find(devices);

  UrgUsbCom urg_usb;
  for (vector<string>::const_iterator it = devices.begin();
       it != devices.end(); ++it) {
    string device_name = it->c_str();
#if 0
    if (urg_usb.isUsbCom(device_name.c_str())) {
      device_name += " [URG]";
    }
#endif
    com_combobox_->addItem(device_name.c_str());
    // !!! 前回に指定されたデバイスならば、それを選択する
  }

  pimpl->setLoadEnabled();
}


void SloaderWidget::openFile(void)
{
  QString file_name =
    QFileDialog::getOpenFileName(this, tr(""), pimpl->default_dir_,
                                 tr("mot file (*.mot *.S *.rom)"));
  if (file_name.isEmpty()) {
    return;
  }

  setFile(file_name);
}


void SloaderWidget::loadFile(void)
{
  QString device = com_combobox_->currentText();
  QString file = file_lineedit_->text();
  pimpl->transmit_thread_.setSettings(pimpl->serial_,
                                      file, pimpl->file_lines_, device);

  // ファイルをスレッドで転送する
  pimpl->progress_widget_.show();
  pimpl->transmit_thread_.start();
}


void SloaderWidget::transmitFailed(void)
{
  // !!!
  fprintf(stderr, "fail.\n");
}


void SloaderWidget::transmitSuccessed(void)
{
  pimpl->progress_widget_.hide();

  // 実行時のログを表示する
  pimpl->update_timer_.start(10);
  send_lineedit_->setEnabled(true);
  send_lineedit_->setFocus();
}


void SloaderWidget::displayReceiveData(void)
{
  enum { BufferSize = 256 };
  char buffer[BufferSize + 1];
  int n = pimpl->serial_.receive(buffer, BufferSize, 0);
  if (n <= 0) {
    return;
  }
  buffer[n] = '\0';

  pimpl->received_text_ += QString(buffer);
  terminal_textedit_->setPlainText(pimpl->received_text_);
}


void SloaderWidget::setSendText(void)
{
  string send_text = send_lineedit_->text().toStdString();
  send_text += "\n";
  int n = send_text.size();
  pimpl->serial_.send(send_text.c_str(), n);

  send_lineedit_->clear();
}
