/*!
  \file
  \brief メインフォーム管理

  \author Satofumi KAMIMURA

  $Id: Moving3DWindow.cpp 1971 2011-11-09 07:25:26Z satofumi $

  \todo 全体データを VRML 形式で保存する
  \todo スキャン範囲を config のプレビュー画面にアニメーション表示する
  \todo 描画のフェード時間を設定可能にする
  \todo 描画時のデータ操作が排他になるようにロックする
  \todo 判定エリアが未定義のときに、判定領域を描画しないようにする
*/

#include "Moving3DWindow.h"
#include "Draw3DWidget.h"
#include "findUrgPorts.h"
//#include "FindComPorts.h"
#include "UrgCtrl.h"
//#include "UrgUsbCom.h"
#include "UrgLogHandler.h"
#include "convert2d.h"
//#include "AngleUtils.h"
#include "MathUtils.h"
#include "LineUtils.h"
#include "StopWatch.h"
#include "ticks.h"
#include "ConvertStdStringPath.h"
#include "split.h"
#include <QFileDialog>
#include <QGraphicsLineItem>
#include <QDateTime>
#include <QTimer>
#include <QShortcut>
#include <QMessageBox>
#include <QSettings>
#include <fstream>
#include <cerrno>

using namespace qrk;
using namespace std;


namespace
{
  typedef enum {
    Recording,                  //!< 記録中
    Playing,                    //!< 再生中
    Pausing,                    //!< 再生のポーズ中
    Waiting,                    //!< 操作待ち
  } PlayState;

  typedef vector<Point<long> > Points;
}


struct Moving3DWindow::pImpl
{
  enum {
    FirstSliderSecond = 10,
    DefaultSliderMaximum = 500,
    RedrawMsec = 50,
    PlayTimerUpdateMsec = 10,
  };

  const static bool Start = true;
  const static bool Stop = false;

  const static bool NoWarning = false;
  const static bool ShowWarning = true;

  const static double MinRatio = 0.11;
  const static double MaxRatio = 6.19;


  Draw3DWidget draw_widget_;

  UrgCtrl urg_;
  size_t data_max_;
  vector<long> capture_data_;
  size_t scan_msec_;

  UrgLogHandler urg_logger_;
  UrgLogHandler obstacle_logger_;
  size_t first_timestamp_;
  size_t first_ticks_;
  QTimer capture_timer_;
  QTimer redraw_timer_;

  size_t record_max_;           // 初期スライダーの表示幅 [sec]

  Points area_points_;
  QGraphicsScene scene_;
  vector<QGraphicsItem*> area_items_;

  size_t last_timestamp_;
  Point3d<int> last_draw_position_;

  StopWatch play_timer_;        // 再生時間のタイマー
  QTimer playing_update_timer_; // 再生情報の更新を行うためのタイマー
  int play_period_max_;

  double magnify_;

  vector<long> detect_array_;

  pImpl(void)
    : first_timestamp_(0), first_ticks_(0),
      record_max_(FirstSliderSecond), last_timestamp_(0), play_period_max_(0),
      magnify_(1.0)
  {
  }


  void initializeForm(Moving3DWindow* parent)
  {
    // 描画ウィンドウまわり
    parent->dummy_main_label_->hide();
    parent->main_layout_->addWidget(&draw_widget_);

    // 接続ポートまわり
    parent->com_combobox_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
    setRecordEnabled(parent, true);
    createComCombobox(parent);

    // 閲覧まわり
    connect(parent->front_button_, SIGNAL(clicked()),
            parent, SLOT(front()));
    connect(parent->overview_button_, SIGNAL(clicked()),
            parent, SLOT(overview()));

    // 記録まわり
    parent->progress_slider_->setMaximum(DefaultSliderMaximum);
    connect(parent->record_button_, SIGNAL(clicked()), parent, SLOT(record()));
    connect(parent->stop_button_, SIGNAL(clicked()), parent, SLOT(stop()));
    connect(parent->action_rescan_, SIGNAL(triggered()),
            parent, SLOT(rescanPort()));
    connect(&capture_timer_, SIGNAL(timeout()),
            parent, SLOT(recordCaptureData()));

    // 描画まわり
    connect(&redraw_timer_, SIGNAL(timeout()), parent, SLOT(redraw()));
    connect(parent->afterimage_spinbox_, SIGNAL(valueChanged(double)),
            parent, SLOT(updateDrawPeriod(double)));

    // 判定エリアまわり
    parent->area_graphics_->setScene(&scene_);
    connect(parent->rescan_button_, SIGNAL(clicked()),
            parent, SLOT(rescanArea()));

    // 再生まわり
    connect(parent->play_button_, SIGNAL(clicked()),
            parent, SLOT(play()));
    connect(parent->pause_button_, SIGNAL(clicked()),
            parent, SLOT(pause()));
    connect(parent->play_stop_button_, SIGNAL(clicked()),
            parent, SLOT(play_stop()));
    connect(&playing_update_timer_, SIGNAL(timeout()),
            parent, SLOT(readPlayData()));
    connect(parent->play_file_path_button_, SIGNAL(clicked()),
            parent, SLOT(playFileLoad()));

    // 障害物のバーまわり
    parent->dummy_bar_label_->hide();

    // ズームまわり
    connect(parent->zoom_slider_, SIGNAL(valueChanged(int)),
            parent, SLOT(changeZoom(int)));

    // 各種キーバインド
    (void) new QShortcut(Qt::Key_Space, parent, SLOT(pauseAndPlay()));
    (void) new QShortcut(Qt::Key_Greater, parent, SLOT(zoomIn()));
    (void) new QShortcut(Qt::Key_Period, parent, SLOT(zoomIn()));
    (void) new QShortcut(Qt::Key_Less, parent, SLOT(zoomOut()));
    (void) new QShortcut(Qt::Key_Comma, parent, SLOT(zoomOut()));
    (void) new QShortcut(Qt::Key_Up, parent, SLOT(viewUp()));
    (void) new QShortcut(Qt::Key_Down, parent, SLOT(viewDown()));
    (void) new QShortcut(Qt::Key_Left, parent, SLOT(viewLeft()));
    (void) new QShortcut(Qt::Key_Right, parent, SLOT(viewRight()));

    // Ctrl-Q, Alt-F4, ESC で終了
    (void) new QShortcut(Qt::CTRL + Qt::Key_Q, parent, SLOT(close()));
    (void) new QShortcut(Qt::ALT + Qt::Key_F4, parent, SLOT(close()));
    (void) new QShortcut(Qt::Key_Escape, parent, SLOT(close()));

    // アクションの初期化
    connect(parent->action_about_, SIGNAL(triggered()),
            parent, SLOT(aboutApplication()));

    // フォーカスの初期化
    parent->record_button_->setFocus();
  }


  void updateInformation(Moving3DWindow* parent)
  {
    // 描画エリアの更新
    rescanArea(parent, NoWarning);

    // !!! 記録時の判定領域を、ファイルに書き出しておいて利用すべき
    // !!! あと、以下の実装は適切に関数化すべき
    double h = getSensorHeight(parent);
    draw_widget_.setDrawLineData(area_points_, static_cast<int>(h));

    // センサ位置設定の取得
    // !!!

    // 描画開始
    redraw_timer_.start(RedrawMsec);
  }


  bool rescanArea(Moving3DWindow* parent, bool show_warning = true)
  {
    // 判定エリアの描画
    scene_.clear();
    area_items_.clear();

    // 地面の描画
    QPen pen;
    pen.setStyle(Qt::DotLine);
    QGraphicsLineItem* ground_x = new QGraphicsLineItem(-200, 0, +200, 0);
    ground_x->setPen(pen);
    scene_.addItem(ground_x);
    QGraphicsLineItem* ground_y = new QGraphicsLineItem(0, -200, 0, +20);
    ground_y->setPen(pen);
    scene_.addItem(ground_y);

    // センサ位置の描画
    const double ratio = 0.08;
    double h_mm = getSensorHeight(parent);
    QGraphicsRectItem* sensor_item =
      new QGraphicsRectItem(-25.0 * ratio, (-h_mm +25.0) * ratio,
                            50.0 * ratio, 50.0 * ratio);
    pen.setStyle(Qt::SolidLine);
    pen.setWidth(2);
    pen.setColor(QColor("darkorange"));
    sensor_item->setPen(pen);
    scene_.addItem(sensor_item);

    // 判定エリア情報の取得
    QString file_name = parent->detect_area_edit_->text();
    string std_path = qrk::toStdStringPath(file_name);

    ifstream fin(std_path.c_str());
    if (! fin.is_open()) {
      if (show_warning) {
        QMessageBox::warning(parent, "Area",
                             strerror(errno), QMessageBox::Close);
      }
      return false;
    }

    area_points_.clear();
    string line;
    while (getline(fin, line)) {
      vector<string> tokens;
      if (qrk::split(tokens, line, " \t,") == 2) {
        int x = static_cast<int>(atof(tokens[0].c_str()) * 1000);
        int y = static_cast<int>(atof(tokens[1].c_str()) * 1000);
        area_points_.push_back(Point<long>(x, y));
      }
    }
    if (area_points_.size() <= 2) {
      QMessageBox::warning(parent, tr("Area"),
                           tr("required more than 3 points."),
                           QMessageBox::Close);
      return false;
    }

    // 判定領域の描画
    Points::iterator first = area_points_.begin();
    for (Points::iterator second = first + 1;
         second != area_points_.end(); ++second, ++first) {

      QGraphicsLineItem* item =
        new QGraphicsLineItem(first->x * ratio, -first->y * ratio,
                              second->x * ratio, -second->y * ratio);
      area_items_.push_back(item);
    }
    for (vector<QGraphicsItem*>::iterator it = area_items_.begin();
         it != area_items_.end(); ++it) {
      scene_.addItem(*it);
    }

    return true;
  }


  void saveSettings(Moving3DWindow* parent)
  {
    QSettings settings("Hokuyo LTD.", "Moving3D");

    // ウィンドウの位置と場所
    QRect position = settings.value("geometry", parent->geometry()).toRect();
    parent->setGeometry(position);

    // フォーム設定
    settings.setValue("tab_index", parent->main_tab_->currentIndex());
    settings.setValue("last_logfile", parent->play_file_edit_->text());
    settings.setValue("area_file", parent->detect_area_edit_->text());

    // 視点、拡大率
    Point3d<int> rotation = draw_widget_.viewRotation();
    settings.setValue("rotation_x", rotation.x);
    settings.setValue("rotation_y", rotation.y);
    settings.setValue("rotation_z", rotation.z);
    double zoom_ratio = draw_widget_.zoomRatio();
    settings.setValue("zoom_ratio", zoom_ratio);

    // センサ高さ
    settings.setValue("sensor_height",
                      parent->sensor_height_spinbox_->value());
    settings.setValue("ground_offset",
                      parent->ground_offset_spinbox_->value());

    // 移動速度
    settings.setValue("move_velocity",
                      parent->m_sec_spinbox_->value());
    settings.setValue("afterimage_sec",
                      parent->afterimage_spinbox_->value());
  }


  void loadSettings(Moving3DWindow* parent)
  {
    QSettings settings("Hokuyo LTD.", "Moving3D");

    // ウィンドウの位置と場所
    QRect position = parent->geometry();
    QSize size = parent->size();
    settings.setValue("geometry", QRect(position.x(), position.y(),
                                        size.width(), size.height()));

    // フォーム設定
    parent->main_tab_->setCurrentIndex(settings.value("tab_index", 0).toInt());
    parent->play_file_edit_->
      setText(settings.value("last_logfile", "").toString());
    // !!! ファイルの存在確認を行うべき

    parent->detect_area_edit_->
      setText(settings.value("area_file", "detect_area.csv").toString());

    // 視点、拡大率
    Point3d<int> rotation;
    rotation.x = settings.value("rotation_x", 0).toInt();
    rotation.y = settings.value("rotation_y", 0).toInt();
    rotation.z = settings.value("rotation_z", 0).toInt();
    draw_widget_.setViewRotation(rotation);
    double zoom_ratio = settings.value("zoom_ratio", 1.0).toDouble();
    draw_widget_.setViewZoom(zoom_ratio);
    updateZoomView(parent);

    // センサ高さ
    parent->sensor_height_spinbox_->
      setValue(settings.value("sensor_height", 1.000).toDouble());
    parent->ground_offset_spinbox_->
      setValue(settings.value("ground_offset", 0.000).toDouble());

    // 移動速度
    parent->m_sec_spinbox_->
      setValue(settings.value("move_velocity", 0.600).toDouble());
    double msec = settings.value("afterimage_sec", 8.0).toDouble();
    parent->afterimage_spinbox_->setValue(msec);
    parent->updateDrawPeriod(msec);
  }


  void createComCombobox(Moving3DWindow* parent)
  {
    //UrgUsbCom urg_usb;
    //FindComPorts urg_finder(&urg_usb);
    vector<string> ports;
    findUrgPorts(ports);
    //vector<string> ports = urg_finder.find();

    // ポートがなければ、記録させない
    parent->record_button_->setEnabled(! ports.empty());

    // 見付かったポートをコンボボックスに登録する
    parent->com_combobox_->clear();
    for (vector<string>::iterator it = ports.begin();
         it != ports.end(); ++it) {
      parent->com_combobox_->addItem(it->c_str());
    }
  }


  void record(Moving3DWindow* parent)
  {
    const string device =
      parent->com_combobox_->currentText().toStdString();

    if (! urg_.connect(device.c_str())) {
      QMessageBox::warning(parent, "URG", urg_.what(), QMessageBox::Close);
      return;
    }

    // タイムスタンプを取得してから、AutoCapture モードに設定する
    urg_.setCaptureMode(ManualCapture);
    loadUrgParameters();
    loadFirstTimestamp();
    urg_.setCaptureMode(AutoCapture);

    // 障害物を検出するための準備
    createAreaDetectArray(parent);

    setRecordStart(parent, Start);
    setRecordEnabled(parent, false);

    parent->stop_button_->setFocus();
  }


  void createAreaDetectArray(Moving3DWindow* parent)
  {
    if (area_points_.size() <= 1) {
      return;
    }

    double h = getSensorHeight(parent);

    // !!! URG が上向きで取り付けられていることに依存している
    // !!! 判定のループは、内側と外側が逆の方がよい。いずれ作りなおすこと
    detect_array_.clear();
    int max_length = urg_.maxDistance();
    for (size_t i = 0; i < data_max_; ++i) {
      double radian = urg_.index2rad(i) + (M_PI / 2.0);
      double x = max_length * cos(radian);
      double y = max_length * sin(radian);

      Point<double> a(0, h), b(x, y + h), p;

      // 登録してある判定用の直線と交差判定を行う
      Points::iterator first = area_points_.begin();
      double cross_min_length = max_length + 1.0;
      for (Points::iterator it = first + 1;
           it != area_points_.end(); ++it, ++first) {
        Point<double> c(first->x, first->y), d(it->x, it->y);
        if (! isCrossLines<double>(p, Line<double>(a, b), Line<double>(c, d))) {
          continue;
        }

        double cross_length = length<Point<double> >(a, p);
        if (cross_length < cross_min_length) {
          cross_min_length = cross_length;
        }
      }
      detect_array_.push_back(static_cast<int>(cross_min_length));
    }
  }


  void stop(Moving3DWindow* parent)
  {
    urg_.disconnect();

    setRecordStart(parent, Stop);
    setRecordEnabled(parent, true);
  }


  void loadFirstTimestamp(void)
  {
    long timestamp = 0;
    int n = urg_.capture(capture_data_, &timestamp);
    first_timestamp_ = (n > 0) ? timestamp : 0;
    first_ticks_ = ticks();
  }


  void loadUrgParameters(void)
  {
    data_max_ = urg_.maxScanLines();
    scan_msec_ = urg_.scanMsec();
  }


  void setRecordEnabled(Moving3DWindow* parent, bool enable)
  {
    parent->record_button_->setEnabled(enable);
    parent->stop_button_->setEnabled(! enable);
  }


  void setRecordStart(Moving3DWindow* parent, bool isStart)
  {
    // 記録用のタイマーの動作設定
    if (isStart) {
      QDateTime date_time = QDateTime::currentDateTime();
      string log_file =
        "log_" + date_time.toString("yyMMdd_hhmmss").toStdString() + ".txt";
      if (! urg_logger_.create(log_file.c_str())) {
        QMessageBox::warning(parent, "Log File", strerror(errno),
                             QMessageBox::Close);
      }
      parent->play_file_edit_->setText(log_file.c_str());
      setPlayEnabled(parent, Recording);

      // 障害物の情報を記録
      // !!! エラーチェックをすべき
      string obstacle_file = "obstacle" + log_file;
      obstacle_logger_.create(obstacle_file.c_str());

      record_max_ = FirstSliderSecond;
      parent->progress_slider_->setMaximum(DefaultSliderMaximum);
      capture_timer_.start(scan_msec_);
    } else {
      urg_logger_.close();
      obstacle_logger_.close();
      capture_timer_.stop();

      // 再生可能にする
      setPlayEnabled(parent, Waiting);
    }
  }


  void setPlayEnabled(Moving3DWindow* parent, PlayState state)
  {
    bool playing = ((state == Waiting) || (state == Pausing)) ? true : false;
    parent->play_button_->setEnabled(playing);
    parent->pause_button_->setEnabled((state == Playing) ? true : false);
    parent->play_stop_button_->
      setEnabled(((state == Recording) || (state == Waiting)) ? false : true);

    bool no_recording = (state == Recording) ? false : true;
    parent->play_file_label_->setEnabled(no_recording);
    parent->play_file_edit_->setEnabled((state == Waiting) ? true : false);
    parent->play_file_path_button_->setEnabled(no_recording);
  }


  // 再生処理
  void readPlayData(Moving3DWindow* parent)
  {
    long current_timestamp = play_timer_.ticks();
    if (updateTimeView(parent, current_timestamp)) {
      // 再生終了
      parent->play_stop();
      return;
    }

    // データの読み出しと登録
    size_t timestamp = 0;
    Points points;
    if (urg_logger_.getData(timestamp, points, current_timestamp)) {
      vector<Point3d<int> > points_3d;
      createCurrentData(parent, points_3d, timestamp, points);
      draw_widget_.addData(timestamp, points_3d);
      last_timestamp_ = timestamp;
    }

    Points obstacle_points;
    if (obstacle_logger_.getData(timestamp, obstacle_points,
                                 current_timestamp)) {
      parent->changeObstacleLabel(obstacle_points.empty() ? false : true);

      vector<Point3d<int> > points_3d;
      createCurrentData(parent, points_3d, timestamp, obstacle_points);
      draw_widget_.addAdditionalData(timestamp, points_3d);
    }
  }


  void recordCaptureData(Moving3DWindow* parent)
  {
    long timestamp = 0;
    int n = urg_.capture(capture_data_, &timestamp);
    if (n <= 0) {
      return;
    }

    Points points;
    Position<long> offset;
    convert2d(points, &urg_, capture_data_, offset);
    size_t record_msec = timestamp - first_timestamp_;
    //fprintf(stderr, "[%d]\n", timestamp);
    //fprintf(stderr, "<%ld, %d>\n", urg_.recentTimestamp(), first_timestamp_);
    urg_logger_.addData(record_msec, points);

    // データの登録
    vector<Point3d<int> > points_3d;
    createCurrentData(parent, points_3d, record_msec, points);
    draw_widget_.addData(record_msec, points_3d);

    // 判定領域の中に物体があるかの判定
    Points obstacle_points;
    createObstaclePoints(obstacle_points);
    parent->changeObstacleLabel(obstacle_points.empty() ? false : true);
    obstacle_logger_.addData(record_msec, obstacle_points);

    vector<Point3d<int> > obstacle_points_3d;
    createCurrentData(parent, obstacle_points_3d, record_msec, obstacle_points);
    draw_widget_.addAdditionalData(record_msec, obstacle_points_3d);

    // 記録時間の更新
    updateTimeView(parent, record_msec, true);
  }


  void createObstaclePoints(Points& points)
  {
    int min_distance = urg_.minDistance();

    for (size_t i = 0; i < data_max_; ++i) {
      long distance = capture_data_[i];
      long judge_distance = detect_array_[i];
      if (! ((distance > min_distance) && (distance <= judge_distance))) {
        continue;
      }
      double radian = urg_.index2rad(i);
      int x = static_cast<int>(distance * cos(radian));
      int y = static_cast<int>(distance * sin(radian));

      points.push_back(Point<long>(x, y));
    }
  }


  void createCurrentData(Moving3DWindow* parent,
                         vector<Point3d<int> >& points_3d,
                         int msec, Points& points)
  {
    int z = getCurrentPosition(parent, msec);

    for (Points::iterator it = points.begin(); it != points.end(); ++it) {
      points_3d.push_back(Point3d<int>(it->y, it->x, z));
    }
  }


  bool updateTimeView(Moving3DWindow* parent, int msec, bool extendable = false)
  {
    size_t sec = msec / 1000;
    enum { BufferSize = 7 };
    char buffer[BufferSize];
    snprintf(buffer, BufferSize, "% d:%02d", sec / 60, sec % 60);
    parent->time_label_->setText(buffer);

    double progress_percent = 100 * msec / (record_max_ * 1000.0);
    int maximum = parent->progress_slider_->maximum();

    bool over = (progress_percent > 100.0) ? true : false;
    if (extendable && over) {
      // 記録時間のスライダが最大幅を越えたら、伸長する
      record_max_ *= 2;
      parent->progress_slider_->setMaximum(maximum * 2);
    }
    int value = static_cast<int>(maximum * progress_percent / 100.0);
    parent->progress_slider_->setValue(value);

    return over;
  }


  int getCurrentPosition(Moving3DWindow* parent, int timestamp)
  {
    double velocity = parent->m_sec_spinbox_->value();
    int z = static_cast<int>(timestamp * velocity);

    return z;
  }


  double getSensorHeight(Moving3DWindow* parent)
  {
    return 1000.0 * (parent->sensor_height_spinbox_->value() +
                     parent->ground_offset_spinbox_->value());
  }


  void redraw(Moving3DWindow* parent)
  {
    // 記録時
    if (nowRecording(parent)) {
      last_timestamp_ = ticks() - first_ticks_;

    } else if (nowPlaying()) {
      // 再生時は、スライダーと同期した位置を timestamp として利用する
      last_timestamp_ = play_timer_.ticks();

    } else {
      // 待機時は、前回のタイムスタンプを用いる
    }

    int z = getCurrentPosition(parent, last_timestamp_);
    last_draw_position_ = Point3d<int>(0, 0, z);
    draw_widget_.redraw(last_timestamp_, last_draw_position_);
  }


  bool nowRecording(Moving3DWindow* parent)
  {
    return parent->record_button_->isEnabled() ? false : true;
  }


  bool nowPlaying(void)
  {
    return (play_timer_.ticks() == 0) ? false : true;
  }


  void updateZoomView(Moving3DWindow* parent)
  {
    double ratio = draw_widget_.zoomRatio();
    int percent =
      static_cast<int>(100.0 * (ratio - MinRatio) / (MaxRatio - MinRatio));
    if (ratio < MinRatio) {
      percent = 0;
    } else if (ratio > MaxRatio) {
      percent = 100;
    }
    parent->zoom_slider_->setValue(percent);
  }


  void changeZoom(int percent)
  {
    double ratio = MinRatio + (MaxRatio - MinRatio) * percent / 100.0;
    draw_widget_.setViewZoom(ratio);
  }
};


Moving3DWindow::Moving3DWindow(void) : pimpl(new pImpl)
{
  setupUi(this);

  pimpl->initializeForm(this);
  pimpl->loadSettings(this);
  pimpl->updateInformation(this);
}


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


// 接続ポートの再スキャン
void Moving3DWindow::rescanPort(void)
{
  pimpl->createComCombobox(this);
}


// 記録の開始
void Moving3DWindow::record(void)
{
  pimpl->record(this);
}


// 記録の終了
void Moving3DWindow::stop(void)
{
  pimpl->stop(this);
}


// 取得データの記録
void Moving3DWindow::recordCaptureData(void)
{
  pimpl->recordCaptureData(this);
}


void Moving3DWindow::front(void)
{
  pimpl->draw_widget_.setViewRotation(Point3d<int>(0, 0, 0));
}


void Moving3DWindow::overview(void)
{
  pimpl->draw_widget_.setViewRotation(Point3d<int>(30, 325, 0));
}


void Moving3DWindow::rescanArea(void)
{
  pimpl->rescanArea(this);
}


void Moving3DWindow::redraw(void)
{
  pimpl->redraw(this);
}


void Moving3DWindow::play(void)
{
  if (! pimpl->nowPlaying()) {
    // 再生開始

    if (pimpl->nowRecording(this)) {
      // 記録を停止し、その記録を再生する
      stop();
    }

    QString file_name = play_file_edit_->text();
    string std_path = qrk::toStdStringPath(file_name);
    if (! pimpl->urg_logger_.load(std_path.c_str())) {
      if (pimpl->urg_logger_.getLastTimestamp() == 0) {
        QMessageBox::warning(this, tr("Log File"), tr("Invalid data file."),
                             QMessageBox::Close);
      } else {
        QMessageBox::warning(this, tr("Log File"), strerror(errno),
                             QMessageBox::Close);
      }
      return;
    }

    // !!! ディレクトリ名を保持した上で、ファイル名の調整を行うべき
    QString obstacle_file_name = "obstacle" + play_file_edit_->text();
    string obstacle_std_path = qrk::toStdStringPath(obstacle_file_name);
    pimpl->obstacle_logger_.load(obstacle_std_path.c_str());

    // 再生開始
    pimpl->draw_widget_.clear();
    pimpl->play_period_max_ = pimpl->urg_logger_.getLastTimestamp();
    pimpl->record_max_ = pimpl->play_period_max_ / 1000;
    progress_slider_->setMaximum(pImpl::DefaultSliderMaximum *
                                 pimpl->record_max_ / pImpl::FirstSliderSecond);

    pimpl->play_timer_.start();
    pimpl->setPlayEnabled(this, Playing);
    pimpl->playing_update_timer_.start(pImpl::PlayTimerUpdateMsec);

  } else {
    // 一時停止からの復帰
    pimpl->play_timer_.resume();
    pimpl->setPlayEnabled(this, Playing);
  }
}


void Moving3DWindow::pause(void)
{
  pimpl->play_timer_.pause();
  pimpl->setPlayEnabled(this, Pausing);
}


void Moving3DWindow::play_stop(void)
{
  pimpl->playing_update_timer_.stop();
  pimpl->play_timer_.stop();
  pimpl->setPlayEnabled(this, Waiting);
}


void Moving3DWindow::readPlayData(void)
{
  pimpl->readPlayData(this);
}


void Moving3DWindow::initializeView(void)
{
  front();
  initializeZoom();
}


void Moving3DWindow::pauseAndPlay(void)
{
  if (! pimpl->nowPlaying()) {

    if (! pimpl->nowRecording(this)) {
      // 待機中ならば、再生を開始する
      play();
    }
    return;
  }

  if (pimpl->play_timer_.isPause()) {
    play();
  } else {
    pause();
  }
}


void Moving3DWindow::initializeZoom(void)
{
  pimpl->draw_widget_.setViewZoom(1.0);
  pimpl->updateZoomView(this);
}


void Moving3DWindow::zoomIn(void)
{
  pimpl->draw_widget_.zoomIn();
  pimpl->updateZoomView(this);
}


void Moving3DWindow::zoomOut(void)
{
  pimpl->draw_widget_.zoomOut();
  pimpl->updateZoomView(this);
}


void Moving3DWindow::changeZoom(int value)
{
  int maximum = zoom_slider_->maximum();
  int percent = static_cast<int>(100.0 * value / maximum);
  pimpl->changeZoom(percent);
}


void Moving3DWindow::viewUp(void)
{
  pimpl->draw_widget_.viewUp();
}


void Moving3DWindow::viewDown(void)
{
  pimpl->draw_widget_.viewDown();
}


void Moving3DWindow::viewLeft(void)
{
  pimpl->draw_widget_.viewLeft();
}


void Moving3DWindow::viewRight(void)
{
  pimpl->draw_widget_.viewRight();
}


void Moving3DWindow::changeObstacleLabel(bool detected)
{
  if (detected) {
    detect_label_->setText(tr("Detected !!!"));
  } else {
    detect_label_->setText(tr("No Detection."));
  }
}


void Moving3DWindow::aboutApplication(void)
{
  QMessageBox::about(this, tr("About Moving3D"),
                     tr("<h2>Moving3D</h2>"
                        "<p>Demo Application for URG.</p>"
                        "<p>Report bugs to "
                        "&lt;satofumi@users.sourceforge.jp&gt;</p>"));
}


void Moving3DWindow::updateDrawPeriod(double value)
{
  // 値は毎回取得する
  int msec = static_cast<int>(value * 1000.0);
  pimpl->draw_widget_.setDrawPeriod(msec);
}


void Moving3DWindow::playFileLoad(void)
{
  QString fileName =
    QFileDialog::getOpenFileName(this, tr("Open log file."), ".",
                                 tr("log (*.txt)"));

  if (! fileName.isEmpty()) {
    // 日本語を含むパスへの対処
    string std_path = qrk::toStdStringPath(fileName);
    QFileInfo fi(std_path.c_str());
    QString name = fi.fileName();

    play_file_edit_->setText(name);
  }
}
