/*
 * Copyright (C) 2012 Open Source Robotics Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
#include <functional>
#include <mutex>
#include <sstream>

#include <boost/lexical_cast.hpp>

#include "gazebo/gui/Actions.hh"
#include "gazebo/gui/GuiEvents.hh"
#include "gazebo/gui/GuiIface.hh"
#include "gazebo/gui/LogPlayWidget.hh"
#include "gazebo/gui/TimePanel.hh"
#include "gazebo/gui/TimePanelPrivate.hh"
#include "gazebo/gui/TimeWidget.hh"
#include "gazebo/rendering/UserCamera.hh"

#include "gazebo/transport/Node.hh"

using namespace gazebo;
using namespace gui;

/////////////////////////////////////////////////
TimePanel::TimePanel(QWidget *_parent)
  : QWidget(_parent), dataPtr(new TimePanelPrivate)
{
  this->setObjectName("timePanel");

  // Time Widget
  this->dataPtr->timeWidget = new TimeWidget(this);
  this->dataPtr->timeWidget->setObjectName("timeWidget");
  connect(this, SIGNAL(SetTimeWidgetVisible(bool)),
      this->dataPtr->timeWidget, SLOT(setVisible(bool)));

  // LogPlay Widget
  this->dataPtr->logPlayWidget = new LogPlayWidget(this);
  this->dataPtr->logPlayWidget->setObjectName("logPlayWidget");
  this->dataPtr->logPlayWidget->setVisible(false);
  connect(this, SIGNAL(SetLogPlayWidgetVisible(bool)),
      this->dataPtr->logPlayWidget, SLOT(setVisible(bool)));

  // Layout
  QHBoxLayout *mainLayout = new QHBoxLayout;
  mainLayout->addWidget(this->dataPtr->timeWidget);
  mainLayout->addWidget(this->dataPtr->logPlayWidget);
  this->setLayout(mainLayout);

  this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
  this->layout()->setContentsMargins(0, 0, 0, 0);

  // Transport
  this->dataPtr->node = transport::NodePtr(new transport::Node());
  this->dataPtr->node->TryInit(common::Time::Maximum());

  this->dataPtr->statsSub = this->dataPtr->node->Subscribe(
      "~/world_stats", &TimePanel::OnStats, this);

  this->dataPtr->userCmdPub =
      this->dataPtr->node->Advertise<msgs::UserCmd>("~/user_cmd");

  // Timer
  QTimer *timer = new QTimer(this);
  connect(timer, SIGNAL(timeout()), this, SLOT(Update()));
  timer->start(33);

  // Connections
  this->dataPtr->connections.push_back(
      gui::Events::ConnectFullScreen(
      std::bind(&TimePanel::OnFullScreen, this, std::placeholders::_1)));

  if (g_playAct)
    connect(g_playAct, SIGNAL(changed()), this, SLOT(OnPlayActionChanged()));

  QShortcut *space = new QShortcut(Qt::Key_Space, this);
  QObject::connect(space, SIGNAL(activated()), this, SLOT(TogglePause()));

  this->dataPtr->paused = false;
}

/////////////////////////////////////////////////
void TimePanel::OnFullScreen(bool /*_value*/)
{
  /*if (_value)
    this->hide();
  else
    this->show();
    */
}

/////////////////////////////////////////////////
TimePanel::~TimePanel()
{
  this->dataPtr->node.reset();

  delete this->dataPtr;
  this->dataPtr = NULL;
}

/////////////////////////////////////////////////
void TimePanel::OnPlayActionChanged()
{
  // Tests don't see external actions
  if (!g_stepAct)
    return;

  if (this->IsPaused())
  {
    g_stepAct->setToolTip("Step the world");
    g_stepAct->setEnabled(true);
  }
  else
  {
    g_stepAct->setToolTip("Pause the world before stepping");
    g_stepAct->setEnabled(false);
  }
}

/////////////////////////////////////////////////
void TimePanel::ShowRealTimeFactor(bool _show)
{
  if (this->dataPtr->timeWidget->isVisible())
    this->dataPtr->timeWidget->ShowRealTimeFactor(_show);
  else
    gzwarn << "Time widget not visible" << std::endl;
}

/////////////////////////////////////////////////
void TimePanel::ShowRealTime(bool _show)
{
  if (this->dataPtr->timeWidget->isVisible())
    this->dataPtr->timeWidget->ShowRealTime(_show);
  else
    gzwarn << "Time widget not visible" << std::endl;
}

/////////////////////////////////////////////////
void TimePanel::ShowSimTime(bool _show)
{
  if (this->dataPtr->timeWidget->isVisible())
    this->dataPtr->timeWidget->ShowSimTime(_show);
  else
    gzwarn << "Time widget not visible" << std::endl;
}

/////////////////////////////////////////////////
void TimePanel::ShowIterations(bool _show)
{
  if (this->dataPtr->timeWidget->isVisible())
    this->dataPtr->timeWidget->ShowIterations(_show);
  else
    gzwarn << "Time widget not visible" << std::endl;
}

/////////////////////////////////////////////////
void TimePanel::ShowFPS(bool _show)
{
  if (this->dataPtr->timeWidget->isVisible())
    this->dataPtr->timeWidget->ShowFPS(_show);
  else
    gzwarn << "Time widget not visible" << std::endl;
}

/////////////////////////////////////////////////
void TimePanel::ShowStepWidget(bool _show)
{
  if (this->dataPtr->timeWidget->isVisible())
    this->dataPtr->timeWidget->ShowStepWidget(_show);
  else
    gzwarn << "Time widget not visible" << std::endl;
}

/////////////////////////////////////////////////
bool TimePanel::IsPaused() const
{
  return this->dataPtr->paused;
}

/////////////////////////////////////////////////
void TimePanel::SetPaused(bool _paused)
{
  this->dataPtr->paused = _paused;

  if (this->dataPtr->timeWidget->isVisible())
    this->dataPtr->timeWidget->SetPaused(_paused);
  else if (this->dataPtr->logPlayWidget->isVisible())
    this->dataPtr->logPlayWidget->SetPaused(_paused);
}

/////////////////////////////////////////////////
void TimePanel::TogglePause()
{
  if (this->IsPaused())
    g_playAct->trigger();
  else
    g_pauseAct->trigger();
}

/////////////////////////////////////////////////
void TimePanel::OnStats(ConstWorldStatisticsPtr &_msg)
{
  std::lock_guard<std::mutex> lock(this->dataPtr->mutex);

  if (_msg->has_paused())
    this->SetPaused(_msg->paused());

  if (!this->isVisible())
    return;

  this->dataPtr->simTimes.push_back(msgs::Convert(_msg->sim_time()));
  if (this->dataPtr->simTimes.size() > 20)
    this->dataPtr->simTimes.pop_front();

  this->dataPtr->realTimes.push_back(msgs::Convert(_msg->real_time()));
  if (this->dataPtr->realTimes.size() > 20)
    this->dataPtr->realTimes.pop_front();

  if (_msg->has_log_playback_stats() &&
      !this->dataPtr->logPlayWidget->isVisible())
  {
    this->SetTimeWidgetVisible(false);
    this->SetLogPlayWidgetVisible(true);
    gui::Events::windowMode("LogPlayback");
  }
  else if (!_msg->has_log_playback_stats() &&
      !this->dataPtr->timeWidget->isVisible())
  {
    this->SetTimeWidgetVisible(true);
    this->SetLogPlayWidgetVisible(false);
    gui::Events::windowMode("Simulation");
  }

  if (this->dataPtr->timeWidget->isVisible())
  {
    // Set simulation time
    this->dataPtr->timeWidget->EmitSetSimTime(QString::fromStdString(
        msgs::Convert(_msg->sim_time()).FormattedString()));

    // Set real time
    this->dataPtr->timeWidget->EmitSetRealTime(QString::fromStdString(
        msgs::Convert(_msg->real_time()).FormattedString()));

    // Set the iterations
    this->dataPtr->timeWidget->EmitSetIterations(QString::fromStdString(
        boost::lexical_cast<std::string>(_msg->iterations())));
  }
  else if (this->dataPtr->logPlayWidget->isVisible())
  {
    // Set current time
    this->dataPtr->logPlayWidget->EmitSetCurrentTime(
        msgs::Convert(_msg->sim_time()));

    // Set start time in text and in ms
    this->dataPtr->logPlayWidget->EmitSetStartTime(
        msgs::Convert(_msg->log_playback_stats().start_time()));

    // Set end time in text and in ms
    this->dataPtr->logPlayWidget->EmitSetEndTime(
        msgs::Convert(_msg->log_playback_stats().end_time()));
  }
}

/////////////////////////////////////////////////
void TimePanel::Update()
{
  if (!this->isVisible())
    return;

  std::lock_guard<std::mutex> lock(this->dataPtr->mutex);

  // Avoid apparent race condition on start, seen on Windows.
  if (!this->dataPtr->simTimes.size() || !this->dataPtr->realTimes.size())
    return;

  std::ostringstream percent;

  common::Time simAvg, realAvg;
  std::list<common::Time>::iterator simIter, realIter;

  simIter = ++(this->dataPtr->simTimes.begin());
  realIter = ++(this->dataPtr->realTimes.begin());
  while (simIter != this->dataPtr->simTimes.end() &&
      realIter != this->dataPtr->realTimes.end())
  {
    simAvg += ((*simIter) - this->dataPtr->simTimes.front());
    realAvg += ((*realIter) - this->dataPtr->realTimes.front());
    ++simIter;
    ++realIter;
  }
  if (realAvg == 0)
    simAvg = 0;
  else
    simAvg = simAvg / realAvg;

  if (simAvg > 0)
    percent << std::fixed << std::setprecision(2) << simAvg.Double();
  else
    percent << "0";

  if (this->dataPtr->timeWidget->isVisible())
    this->dataPtr->timeWidget->SetPercentRealTimeEdit(percent.str().c_str());

  rendering::UserCameraPtr cam = gui::get_active_camera();
  if (cam)
  {
    std::ostringstream avgFPS;
    avgFPS << std::fixed << std::setprecision(2) << cam->AvgFPS();

    if (this->dataPtr->timeWidget->isVisible())
    {
      // Set the avg fps
      this->dataPtr->timeWidget->EmitSetFPS(QString::fromStdString(
          boost::lexical_cast<std::string>(avgFPS.str().c_str())));
    }
  }
}

/////////////////////////////////////////////////
void TimePanel::OnTimeReset()
{
  msgs::WorldControl msg;
  msg.mutable_reset()->set_all(false);
  msg.mutable_reset()->set_time_only(true);

  // Register user command on server
  msgs::UserCmd userCmdMsg;
  userCmdMsg.set_description("Reset time");
  userCmdMsg.set_type(msgs::UserCmd::WORLD_CONTROL);
  userCmdMsg.mutable_world_control()->CopyFrom(msg);
  this->dataPtr->userCmdPub->Publish(userCmdMsg);
}

/////////////////////////////////////////////////
void TimePanel::OnStepValueChanged(int _value)
{
  emit gui::Events::inputStepSize(_value);
}
