/*!
  \file
  \brief SCIP データの表示ウィンドウ

  \author Satofumi KAMIMURA

  $Id: ScipPlayerWindow.cpp 1332 2009-09-23 01:04:31Z satofumi $

  \todo MD の 00P 応答のときに、適切に読み出せるようにする
*/

#include "ScipPlayerWindow.h"
#include "ScipDataReader.h"
#include "UrgDrawWidget.h"
#include "UrgCtrl.h"
#include "ScipHandler.h"
#include "CustomConnection.h"
#include "ConvertStdStringPath.h"
#include <QTimer>
#include <QUrl>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QFileDialog>
#include <QShortcut>
#include <QSettings>
#include <QMessageBox>

using namespace qrk;
using namespace std;


namespace
{
  const char* Organization = "Hokuyo LTD.";
  const char* Application = "Scip Player";

  typedef vector<string> ScipData;
}


struct ScipPlayerWindow::pImpl
{
  ScipPlayerWindow* parent_;
  UrgDrawWidget urg_draw_widget_;
  ScipDataReader* data_reader_;

  ScipData scip_data_;
  long current_timestamp_;

  CustomConnection con_;
  UrgCtrl urg_;

  QTimer continuous_timer_;
  int play_ratio_;
  bool led_on_;

  pImpl(ScipPlayerWindow* parent)
    : parent_(parent), data_reader_(NULL), current_timestamp_(0),
      play_ratio_(0), led_on_(false)
  {
    urg_.setConnection(&con_);
    continuous_timer_.setSingleShot(true);
  }


  ~pImpl(void)
  {
    delete data_reader_;
  }


  void initializeForm(void)
  {
    // 表示ウィジット
    parent_->dummy_label_->hide();
    parent_->main_layout_->addWidget(&urg_draw_widget_);

    parent_->path_edit_->setAcceptDrops(false);
    parent_->setAcceptDrops(true);

    // イベント
    connect(parent_->path_button_, SIGNAL(clicked()),
            parent_, SLOT(pathPressed()));
    connect(parent_->reload_button_, SIGNAL(clicked()),
            parent_, SLOT(reloadPressed()));
    connect(parent_->next_button_, SIGNAL(clicked()),
            parent_, SLOT(nextPressed()));
    connect(parent_->continuous_button_, SIGNAL(clicked()),
            parent_, SLOT(continuousPressed()));
    connect(&continuous_timer_, SIGNAL(timeout()),
            parent_, SLOT(timerTimeout()));

    // メニュー
    connect(parent_->action_about_, SIGNAL(triggered()),
            parent_, SLOT(aboutApplication()));
    connect(parent_->action_quit_, SIGNAL(triggered()),
            parent_, SLOT(close()));

    // ショートカット
    (void) new QShortcut(Qt::Key_F5, parent_, SLOT(reloadPressed()));
    (void) new QShortcut(Qt::Key_Less, parent_, SLOT(zoomSmaller()));
    (void) new QShortcut(Qt::Key_Comma, parent_, SLOT(zoomSmaller()));
    (void) new QShortcut(Qt::Key_Greater, parent_, SLOT(zoomLarger()));
    (void) new QShortcut(Qt::Key_Period, parent_, SLOT(zoomLarger()));
  }


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

    settings.setValue("geometry", parent_->saveGeometry());
    settings.setValue("file_path", parent_->path_edit_->text());
  }


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

    parent_->restoreGeometry(settings.value("geometry").toByteArray());
    QString log_file = settings.value("file_path", "").toString();
    parent_->path_edit_->setText(log_file);
  }


  void reload(void)
  {
    // !!! この初期化処理は、適切にまとめるべき
    continuous_timer_.stop();
    delete data_reader_;
    data_reader_ = NULL;
    scip_data_.clear();
    con_.clear();
    urg_draw_widget_.clear();
    current_timestamp_ = 0;
    play_ratio_ = 0;
    led_on_ = false;

    QString log_file = parent_->path_edit_->text();
    string std_log_file = qrk::toStdStringPath(log_file);
    data_reader_ = new ScipDataReader(std_log_file);

    prepareUrgReply();
    // !!! 接続に失敗したら、ファイルフォーマットが無効、という表示をすべき
    if (! urg_.connect("dummy")) {
      fprintf(stderr, "UrgCtrl::connect: %s\n", urg_.what());
    }

    // 最初の描画、次データの取得、と２回のデータ更新を行う
    for (int i = 0; i < 2; ++i) {
      while ((! data_reader_->isEmpty()) && (! updateDrawData())) {
        ;
      }
    }
  }


  void prepareUrgReply(void)
  {
    con_.clear();
    con_.setReadData("QT\r00P\r\r");

    // ダミーデータ
    con_.setReadData("PP\r"
                     "00P\r"
                     "MODL:URG-04LX(Hokuyo Automatic Co.,Ltd.);N\r"
                     "DMIN:20;4\r"
                     "DMAX:5600;_\r"
                     "ARES:1024;\r"
                     "AMIN:44;7\r"
                     "AMAX:725;o\r"
                     "AFRT:384;6\r"
                     "SCAN:600;e\r"
                     "\r");
  }


  bool updateDrawData(void)
  {
    if (! scip_data_.empty()) {
      // 保持しているデータを描画
      current_timestamp_ = timestamp(scip_data_);
      setScipData(scip_data_);
      urg_draw_widget_.setUrgData(&urg_);
      urg_draw_widget_.redraw();
      scip_data_.clear();
    }

    ScipData line_block;
    while (data_reader_->readReplyLines(line_block)) {

      string& first_line = line_block[0];
      if (! first_line.compare(0, 2, "BM")) {
        con_.setReadData("BM\r00P\r\r");

      } else if (! first_line.compare(0, 2, "PP")) {
        // パラメータ読み出し
        setScipData(line_block);
        urg_.loadParameter();

      } else if (! first_line.compare(0, 1, "G")) {
        // Gx データの読み出し
        swap(scip_data_, line_block);
        setPlayEnabled(true);
        return true;

      } else if (! first_line.compare(0, 1, "M")) {

        if (line_block.size() <= 2) {
          // MD を RawManualCapture で無理矢理に再生させるための処理
          // !!! 重複をどうにかする
          if (! led_on_) {
            con_.setReadData("BM\r00P\r\r");
            led_on_ = true;
          }

        } else {
          // Mx データの読み出し
          // !!! 重複をどうにかする
          swap(scip_data_, line_block);
          setPlayEnabled(true);
          return true;
        }
      }
    }

    setPlayEnabled(false);
    return false;
  }


  void setPlayEnabled(bool enable)
  {
    parent_->next_button_->setEnabled(enable);
    parent_->continuous_button_->setEnabled(enable);
  }


  void setScipData(const ScipData& line_block)
  {
    for (ScipData::const_iterator it = line_block.begin();
         it != line_block.end(); ++it) {
      con_.setReadData(*it + "\r");
    }
  }


  void continuousPlay(void)
  {
    if (updateDrawData()) {
      long delay = timestamp(scip_data_) - current_timestamp_;
      if (play_ratio_ > 0) {
        delay /= play_ratio_;
      }
      continuous_timer_.start(max(delay, 1L));
    }
  }


  long timestamp(const ScipData& data)
  {
    if (data.size() <= 2) {
      return 0;
    }
    return ScipHandler::decode(data[2].c_str(), 4);
  }
};


ScipPlayerWindow::ScipPlayerWindow(void) : pimpl(new pImpl(this))
{
  setupUi(this);
  pimpl->initializeForm();
  pimpl->loadSettings();

  pimpl->reload();
}


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


void ScipPlayerWindow::aboutApplication(void)
{
  QMessageBox::about(this, tr("About Scip Player"),
                     tr("<h2>Scip Player ($Rev: 1332 $)</h2>"
                        "<p>Demo Application for URG sensor</p>"
                        "<p>Report bugs to "
                        "&lt;s-kamimura@hokuyo-aut.co.jp&gt;</p>"));
}


void ScipPlayerWindow::pathPressed(void)
{
  QString file_name =
    QFileDialog::getOpenFileName(this, tr("SCIP Log file"),
                                 path_edit_->text(),
                                 tr("Log files (*.txt *.log)"));
  if (file_name.isEmpty()) {
    return;
  }

  path_edit_->setText(file_name);
  pimpl->reload();
}


void ScipPlayerWindow::reloadPressed(void)
{
  pimpl->reload();
}


void ScipPlayerWindow::nextPressed(void)
{
  pimpl->continuous_timer_.stop();
  if (pimpl->play_ratio_ > 0) {
    --pimpl->play_ratio_;
  }
  pimpl->updateDrawData();
}


void ScipPlayerWindow::timerTimeout(void)
{
  pimpl->continuousPlay();
}


void ScipPlayerWindow::continuousPressed(void)
{
  ++pimpl->play_ratio_;
  pimpl->continuousPlay();
}


void ScipPlayerWindow::dragEnterEvent(QDragEnterEvent* event)
{
  if (event->mimeData()->hasFormat("text/uri-list")) {
    event->acceptProposedAction();
  }
}


void ScipPlayerWindow::dropEvent(QDropEvent* event)
{
  QList<QUrl> urls = event->mimeData()->urls();
  if (urls.isEmpty()) {
    return;
  }

  QString file_name = urls.first().toLocalFile();
  if (file_name.isEmpty()) {
    return;
  }

  path_edit_->setText(file_name);
  pimpl->reload();
}


void ScipPlayerWindow::zoomSmaller(void)
{
  pimpl->urg_draw_widget_.updateZoomRatio(+1);
}


void ScipPlayerWindow::zoomLarger(void)
{
  pimpl->urg_draw_widget_.updateZoomRatio(-1);
}
