/*!
  \file
  \brief URG データの記録ウィジット

  \author Satofumi KAMIMURA

  $Id: UrgRecorderWidget.cpp 1439 2009-10-22 02:09:34Z satofumi $
*/

#include "UrgRecorderWidget.h"
#include "ConnectionWidget.h"
#include "RecordConnection.h"
#include "UrgCtrl.h"
#include "UrgUsbCom.h"
#include "FindComPorts.h"
#include <QTimer>
#include <QDateTime>
#include <QDir>
#include <QTextStream>
#include <QMessageBox>
#include <QShortcut>
#include <QSettings>

using namespace qrk;
using namespace std;


namespace
{
  const char* Organization = "Hokuyo LTD.";
  const char* Application = "URG Recorder";

  enum {
    DefaultCaptureTimes = 10,
  };
}


struct UrgRecorderWidget::pImpl
{
  UrgRecorderWidget* widget_;
  ConnectionWidget connection_widget_;

  UrgUsbCom urg_usb_;
  FindComPorts urg_finder_;
  UrgCtrl urg_;
  Connection* serial_connection_;

  QTimer capture_timer_;
  QString save_directory_;
  size_t capture_max_;
  size_t total_times_;
  bool is_raw_record_;


  pImpl(UrgRecorderWidget* widget)
    : widget_(widget), serial_connection_(NULL),
      capture_max_(1), total_times_(0), is_raw_record_(false)
  {
    urg_finder_.addBaseName("/dev/ttyACM");
    urg_finder_.addBaseName("/dev/tty.usbmodem");
    urg_finder_.addDriverName("URG Series USB Device Driver");
    urg_finder_.addDriverName("URG-X002 USB Device Driver");
  }


  void initializeForm(void)
  {
    // 位置と大きさを読み出す
    loadSettings();

    // ConnectionWidget の配置
    widget_->connection_layout_->addWidget(&connection_widget_);
    widget_->connection_dummy_label_->hide();

    // イベントの接続
    connect(&connection_widget_, SIGNAL(rescanRequest()),
            widget_, SLOT(rescanPressed()));
    connect(&connection_widget_,
            SIGNAL(connectRequest(bool, const std::string&)),
            widget_, SLOT(connectPressed(bool, const std::string&)));

    connect(widget_->times_spinbox_, SIGNAL(valueChanged(int)),
            widget_, SLOT(timesChanged(int)));

    connect(widget_->record_button_, SIGNAL(clicked()),
            widget_, SLOT(recordPressed()));
    connect(widget_->cancel_button_, SIGNAL(clicked()),
            widget_, SLOT(cancelPressed()));

    connect(widget_->raw_button_, SIGNAL(toggled(bool)),
            widget_, SLOT(rawButtonChanged(bool)));

    connect(&capture_timer_, SIGNAL(timeout()), widget_, SLOT(recordData()));
  }


  void loadSettings(void)
  {
    QSettings settings(Organization, Application);
    widget_->restoreGeometry(settings.value("geometry").toByteArray());
    int capture_times =
      settings.value("capture_times", DefaultCaptureTimes).toInt();
    widget_->times_spinbox_->setValue(capture_times);
    widget_->times_progress_->setMaximum(capture_times);

    is_raw_record_ = settings.value("raw_button_checked", false).toBool();
    widget_->raw_button_->setChecked(is_raw_record_);
    rawButtonChanged(is_raw_record_);
  }


  void saveSettings(void)
  {
    QSettings settings(Organization, Application);
    settings.setValue("geometry", widget_->saveGeometry());
    settings.setValue("capture_times", widget_->times_spinbox_->value());
    settings.setValue("raw_button_checked", widget_->raw_button_->isChecked());
  }


  void setRecording(bool recording)
  {
    widget_->record_button_->setEnabled(! recording);
    widget_->cancel_button_->setEnabled(recording);
    widget_->output_group_->setEnabled(! recording);
    widget_->times_group_->setEnabled(! recording);
  }


  void recordPressed(void)
  {
    setRecording(true);

    // 出力先ディレクトリの作成
    save_directory_ =
      QDateTime::currentDateTime().toString("yyyy-MM-dd_hh_mm_ss");
    QDir dir;
    dir.mkdir(save_directory_);

    // 取得モードの設定
    is_raw_record_ = widget_->raw_button_->isChecked();
    if (is_raw_record_) {
      is_raw_record_ = true;
      serial_connection_ = urg_.connection();
      string send_file = save_directory_.toStdString() + "/send.txt";
      string receive_file = save_directory_.toStdString() + "/receive.txt";
      urg_.setConnection(new RecordConnection(serial_connection_,
                                              send_file.c_str(),
                                              receive_file.c_str()));

      // ログに URG の情報を含めるための呼び出し
      urg_.loadParameter();
    }

    // データ取得の開始
    capture_max_ = widget_->times_spinbox_->value();
    total_times_ = 0;
    urg_.setCaptureMode(AutoCapture);
    capture_timer_.setInterval(urg_.scanMsec() / 2);
    capture_timer_.start();
  }


  void recordData(void)
  {
    vector<long> data;
    int n = urg_.capture(data);
    if (n <= 0) {
      return;
    }
    ++total_times_;

    if (! is_raw_record_) {

      // ファイルへのデータ書き出し
      char buffer[] = "/data_xxxx.csv";
      snprintf(buffer, sizeof(buffer), "/data_%04d.csv", total_times_);
      QFile file(save_directory_ + QString(buffer));
      if (! file.open(QIODevice::WriteOnly)) {
        // !!! エラー表示
        return;
      }

      QTextStream fout(&file);
      saveFile(fout, data);
    }

    widget_->times_progress_->setValue(total_times_);
    if (total_times_ >= capture_max_) {
      stopRecording();
    }
  }


  void stopRecording(void)
  {
    setRecording(false);
    capture_timer_.stop();
    widget_->times_progress_->setValue(0);

    urg_.stop();

    if (is_raw_record_) {
      Connection* connection = urg_.connection();
      if (connection != serial_connection_) {
        delete connection;
        urg_.setConnection(serial_connection_);
      }
    }
  }


  void saveFile(QTextStream& fout, vector<long>& data)
  {
    size_t n = data.size();
    for (size_t i = 0; i < n; ++i) {
      long length = data[i];
      double radian = urg_.index2rad(i);
      double x = length * cos(radian);
      double y = length * sin(radian);
      fout << i << ',' << length << ','
           << radian << ',' << x << ',' << y << endl;
    }
  }


  void rawButtonChanged(bool checked)
  {
    if (checked) {
      widget_->sample_text_->clear();
      widget_->sample_text_->
        insertPlainText("# receive data sample\n"
                        "MD0044072501000\n"
                        "99b\n"
                        "...");
    } else {
      widget_->sample_text_->clear();
      widget_->sample_text_->
        insertPlainText("# index, length, radian, x, y\n"
                        "0,669,-2.08621,-329.749,-582.088\n"
                        "1,667,-2.08008,-325.196,-582.354\n"
                        "...");
    }
  }
};


UrgRecorderWidget::UrgRecorderWidget(QWidget* parent)
  : QWidget(parent), pimpl(new pImpl(this))
{
  setupUi(this);

  // フォームを初期化し、最初の表示を行う
  pimpl->initializeForm();
  rescanPressed();

  pimpl->connection_widget_.setFocus();

  // Ctrl-q, Alt-F4 で終了させる
  (void) new QShortcut(Qt::CTRL + Qt::Key_Q, this, SLOT(close()));
  (void) new QShortcut(Qt::ALT + Qt::Key_F4, this, SLOT(close()));
}


UrgRecorderWidget::~UrgRecorderWidget(void)
{
}


void UrgRecorderWidget::closeEvent(QCloseEvent* event)
{
  static_cast<void>(event);

  pimpl->urg_.stop();
  pimpl->saveSettings();
}


void UrgRecorderWidget::rescanPressed(void)
{
  vector<string> devices;
  pimpl->urg_finder_.find(devices);
  for (vector<string>::iterator it = devices.begin();
       it != devices.end(); ++it) {
    if (pimpl->urg_usb_.isUsbCom(it->c_str())) {
      *it = *it + " [URG]";
    }
  }
  pimpl->connection_widget_.setDevices(devices);
}


void UrgRecorderWidget::connectPressed(bool connection,
                                       const string& device)
{
  // !!! 接続処理をスレッドで行うように調整する
  bool connected = connection;

  if (connection) {
    if (! pimpl->urg_.connect(device.c_str())) {
      QMessageBox::warning(this, tr("Connection error"), pimpl->urg_.what());
      connected = false;
    }
  } else {
    pimpl->stopRecording();
  }

  record_group_->setEnabled(connected);
  cancel_button_->setEnabled(false);

  pimpl->connection_widget_.setConnected(connected);

  if (connected) {
    // フォーカスを Record ボタンに移動させる
    record_button_->setFocus();
  }
}


void UrgRecorderWidget::recordPressed(void)
{
  if (pimpl->capture_timer_.isActive()) {
    return;
  }
  pimpl->recordPressed();
}


void UrgRecorderWidget::cancelPressed(void)
{
  pimpl->stopRecording();
}


void UrgRecorderWidget::timesChanged(int times)
{
  times_progress_->setMaximum(times);

  // 再描画が行われるように、２回ほど値をセットしている
  times_progress_->setValue(1);
  times_progress_->setValue(0);
}


void UrgRecorderWidget::recordData(void)
{
  pimpl->recordData();
}


void UrgRecorderWidget::rawButtonChanged(bool checked)
{
  pimpl->rawButtonChanged(checked);
}
