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

  \author Satofumi KAMIMURA

  $Id: ScipPlayerWindow.cpp 1978 2012-04-05 16:23:43Z satofumi $

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

#include "ScipPlayerWindow.h"
#include "ScipDataReader.h"
#include "UrgDrawWidget.h"
#include "UrgDevice.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* widget_;
    UrgDrawWidget urg_draw_widget_;
    ScipDataReader* data_reader_;

    ScipData scip_data_;
    long current_timestamp_;
    long last_timestamp_;

    CustomConnection con_;
    UrgDevice urg_;

    QTimer continuous_timer_;
    int play_ratio_;
    bool led_on_;

    QStringList csv_files_;
    QString csv_files_dir_;
    bool csv_mode_;
    int csv_index_;


    pImpl(ScipPlayerWindow* parent)
        : widget_(parent), data_reader_(NULL),
          current_timestamp_(0), last_timestamp_(0),
          play_ratio_(0), led_on_(false), csv_mode_(false), csv_index_(0)
    {
        urg_.setConnection(&con_);
        continuous_timer_.setSingleShot(true);
        urg_draw_widget_.setDrawPeriod(1);
    }


    ~pImpl(void)
    {
        delete data_reader_;
    }


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

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

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

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

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


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

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


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

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


    void reload(void)
    {
        // !!! この初期化処理は、適切にまとめるべき
        widget_->setWindowTitle("Scip Player");
        continuous_timer_.stop();
        scip_data_.clear();
        con_.clear();
        urg_draw_widget_.clear();
        urg_draw_widget_.redraw();
        current_timestamp_ = 0;
        play_ratio_ = 0;
        led_on_ = false;

        QString log_file = widget_->path_edit_->text();

        QFileInfo file_info(log_file);
        QString suffix = file_info.suffix().toLower();
        csv_mode_ = (! suffix.compare("csv")) ? true : false;
        if (csv_mode_) {
            // 拡張子が CSV だったら、専用のモードでファイルを処理する
            handleCsvFile(log_file);

        } else {
            handleRawFile(log_file);
        }
    }


    void handleRawFile(const QString& log_file)
    {
        string std_log_file = qrk::toStdStringPath(log_file);
        delete data_reader_;
        data_reader_ = new ScipDataReader(std_log_file);

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

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

#if 0
        // 最初のデータの表示を行う
        updateDrawData();
#endif
    }


    void handleCsvFile(const QString& file_name)
    {
        // ディレクトリ中の CSV ファイルを、名前順に読み出す
        QFileInfo file_info(file_name);
        QDir dir = file_info.dir();
        csv_files_dir_ = file_info.absolutePath();
        QStringList filters;
        filters << "*.csv" << "*.CSV";
        dir.setNameFilters(filters);
        csv_files_ =
            dir.entryList(QDir::Files | QDir::NoSymLinks |
                          QDir::NoDotAndDotDot | QDir::Readable, QDir::Name);

        // 初期化
        csv_index_ = 0;
        current_timestamp_ = 0;

        // 最初のデータを表示させる
        updateCsvFile();
    }


    bool updateCsvFile(void)
    {
        if (csv_index_ >= csv_files_.size()) {
            return false;
        }

        // CSV データを配置する
        QString file_name = csv_files_[csv_index_];
        QFile file(csv_files_dir_ + "/" + file_name);

        widget_->setWindowTitle("Scip Player: " + file_name);

        file.open(QIODevice::ReadOnly | QIODevice::Text);
        // !!! エラー処理をすべき

        vector<Point<long> > points;
        while (! file.atEnd()) {
            QByteArray line = file.readLine();
            setLine(points, line);
        }

        urg_draw_widget_.clear();
        urg_draw_widget_.setUrgData(points, 0);
        urg_draw_widget_.redraw();

        ++csv_index_;
        return true;
    }


    void setLine(vector<Point<long> >& points, const QByteArray& line)
    {
        QList<QByteArray> tokens = line.split(',');
        if (tokens.size() < 5) {
            // トークンが不足していたら戻る
            return;
        }

        // !!! Top-URG の場合は 23 なのを考慮すべき
        if (atoi(tokens[1]) < 20) {
            // 距離データがエラー値の場合は戻る
            return;
        }
        long x = atoi(tokens[3]);
        long y = atoi(tokens[4]);
        points.push_back(Point<long>(-y, x));
    }


    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 (csv_mode_) {
            return updateCsvFile();
        } else {
            return updateRawDraw();
        }
    }



    bool updateRawDraw(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();

            QString message = QString("timestamp: %1 [msec] (%2)")
                .arg(current_timestamp_)
                .arg(current_timestamp_ - last_timestamp_);
            widget_->statusBar()->showMessage(message, 60 * 1000);
            last_timestamp_ = current_timestamp_;
        }

        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;
                    }
                    if (! first_line.compare(1, 1, "E")) {
                        fprintf(stderr, "set intensity mode.\n");
                        urg_.setCaptureMode(IntensityCapture);
                    }

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

        setPlayEnabled(false);
        return false;
    }


    void setPlayEnabled(bool enable)
    {
        widget_->next_button_->setEnabled(enable);
        widget_->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 100;
            return 25;
        }
        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: 1978 $)</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 *.csv)"));
    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);
}


void ScipPlayerWindow::initializeView(void)
{
    pimpl->urg_draw_widget_.initializeView();
}
