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

  \author Satofumi KAMIMURA

  $Id: UrgRecorderWidget.cpp 1990 2012-05-18 00:36:04Z satofumi $
*/

#include "UrgRecorderWidget.h"
#include "SerialConnectionWidget.h"
#include "EthernetConnectionWidget.h"
#include "RecordConnection.h"
#include "UrgDevice.h"
#include "UrgUsbCom.h"
#include "FindComPorts.h"
#include "DetectOS.h"
#include "TcpipSocket.h"
#include <QTimer>
#include <QDateTime>
#include <QDir>
#include <QTextStream>
#include <QMessageBox>
#include <QShortcut>
#include <QSettings>
#include <fstream>

using namespace qrk;
using namespace std;

#if defined(MSC)
#define snprintf _snprintf
#endif

//#define SAVE_WITH_TIMESTAMP


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

    enum {
        UrgDefaultPort = 10940,
        DefaultCaptureTimes = 10,
    };
}


struct UrgRecorderWidget::pImpl
{
    UrgRecorderWidget* widget_;
    SerialConnectionWidget serial_connection_widget_;
    EthernetConnectionWidget ethernet_connection_widget_;

    UrgUsbCom urg_usb_;
    FindComPorts urg_finder_;
    UrgDevice urg_;
    Connection* serial_connection_;
    TcpipSocket ethernet_connection_;

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

    bool intensity_mode_;
    bool is_using_serial_;

    ofstream* timestamp_out_;
    QDateTime first_time_;


    pImpl(UrgRecorderWidget* widget)
        : widget_(widget), serial_connection_(NULL),
          capture_max_(1), total_times_(0), is_raw_record_(false),
          intensity_mode_(false), is_using_serial_(true),
          timestamp_out_(NULL)
    {
        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");

        ethernet_connection_widget_.setPort(UrgDefaultPort);
    }


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

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

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

        connect(&ethernet_connection_widget_,
                SIGNAL(connectRequest(bool, const std::string&,
                                      unsigned short)),
                widget_, SLOT(connectPressed(bool, const std::string&,
                                             unsigned short)));

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

        connect(widget_->change_button_, SIGNAL(clicked()),
                widget_, SLOT(changeButtonPressed()));
    }


    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_);

        is_using_serial_ = settings.value("serial_connection", true).toBool();
        if (is_using_serial_) {
            selectSerial();
        } else {
            selectEthernet();
        }

        string address =
            settings.value("address",
                           UrgDefaultAddress).toString().toStdString();
        ethernet_connection_widget_.setAddress(address);
        unsigned short port = settings.value("port", UrgDefaultPort).toInt();
        ethernet_connection_widget_.setPort(port);
    }


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

        settings.setValue("serial_connection", is_using_serial_);
        settings.setValue("address",
                          ethernet_connection_widget_.address().c_str());
        settings.setValue("port", ethernet_connection_widget_.port());
    }


    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;
        if (intensity_mode_) {
            urg_.setCaptureMode(IntensityCapture);
        } else {
            urg_.setCaptureMode(AutoCapture);
        }

#if defined(SAVE_WITH_TIMESTAMP)
        string pc_timestamp_file =
            save_directory_.toStdString() + "/pc_timestamp.txt";
        timestamp_out_ = new ofstream(pc_timestamp_file.c_str());

        first_time_ = QDateTime::currentDateTime();
        urg_.setTimestamp(0);
#endif

        capture_timer_.setInterval(urg_.scanMsec() / 2);
        capture_timer_.start();
    }


    void recordData(void)
    {
        vector<long> data;
        vector<long> intensity_data;
        long timestamp;

        int n;
        if (intensity_mode_) {
            n = urg_.captureWithIntensity(data, intensity_data, &timestamp);
        } else {
            n = urg_.capture(data, &timestamp);
        }

        if (n <= 0) {
            return;
        }
        ++total_times_;

        // タイムスタンプの記録
#if defined(SAVE_WITH_TIMESTAMP)
        QDateTime current_pc_timestamp = first_time_.addMSecs(timestamp);
        *timestamp_out_ << current_pc_timestamp.
            toString("yyyy-MM-dd hh:mm:ss.zzz").toStdString() << endl;
#endif

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

            QTextStream fout(&file);
            saveFile(fout, data, intensity_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 defined(SAVE_WITH_TIMESTAMP)
        delete timestamp_out_;
        timestamp_out_ = NULL;
#endif

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


    void saveFile(QTextStream& fout, const vector<long>& data,
                  vector<long>& intensity_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;

            if (intensity_mode_) {
                fout << ',' << intensity_data[i];
            }

            fout << 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"
                                "...");
        }
    }


    void selectSerial(void)
    {
        widget_->change_button_->setText(tr("Change to Ethernet"));
        ethernet_connection_widget_.hide();
        serial_connection_widget_.show();

        if (serial_connection_) {
            urg_.setConnection(serial_connection_);
        }

        is_using_serial_ = true;
    }


    void selectEthernet(void)
    {
        widget_->change_button_->setText(tr("Change to Serial"));
        serial_connection_widget_.hide();
        ethernet_connection_widget_.show();

        urg_.setConnection(&ethernet_connection_);

        is_using_serial_ = false;
    }


    void set_enables(bool connected)
    {
        widget_->record_group_->setEnabled(connected);
        widget_->cancel_button_->setEnabled(false);

        serial_connection_widget_.setConnected(connected);
        ethernet_connection_widget_.setConnected(connected);

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


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

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

    pimpl->serial_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::setIntensityMode(void)
{
    if (! pimpl->intensity_mode_) {
        pimpl->intensity_mode_ = true;
        if (pimpl->intensity_mode_) {
            setWindowTitle(windowTitle() + " " + tr("[intensity]"));
        }
    }
}


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->serial_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();
    }

    pimpl->set_enables(connected);
}


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

    fprintf(stderr, "%p, %p\n", &pimpl->ethernet_connection_, pimpl->urg_.connection());
    if (connection) {
        if (! pimpl->urg_.connect(address.c_str(), port)) {
            QMessageBox::warning(this, tr("Connection error"),
                                 pimpl->urg_.what());
            connected = false;
        }
    } else {
        pimpl->stopRecording();
    }

    pimpl->set_enables(connected);
}


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);
}


void UrgRecorderWidget::changeButtonPressed(void)
{
    if (pimpl->is_using_serial_) {
        pimpl->selectEthernet();
    } else {
        pimpl->selectSerial();
    }
}
