/*
 * Copyright (C) 2014 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 <sstream>
#include <ignition/math/Vector2.hh>

#include <gazebo/msgs/msgs.hh>
#include "TimerGUIPlugin.hh"

using namespace gazebo;

// Register this plugin with the simulator
GZ_REGISTER_GUI_PLUGIN(TimerGUIPlugin)

/////////////////////////////////////////////////
TimerGUIPlugin::TimerGUIPlugin()
  : GUIPlugin()
{
  // Set the frame background and foreground colors
  this->setStyleSheet(
      "QFrame {"
        "background-color : rgba(255, 255, 255, 255);"
        "color : black;"
        "font-size: 24px;"
      "}");

  // Create the main layout
  QHBoxLayout *mainLayout = new QHBoxLayout;

  // Create the frame to hold all the widgets
  QFrame *mainFrame = new QFrame();

  // Create the layout that sits inside the frame
  QVBoxLayout *frameLayout = new QVBoxLayout();

  // Create a time label
  QLabel *timeLabel = new QLabel(tr("00:00:00.00"));

  // Add the label to the frame's layout
  frameLayout->addWidget(timeLabel);
  frameLayout->setAlignment(timeLabel, Qt::AlignCenter);
  connect(this, SIGNAL(SetTime(QString)),
      timeLabel, SLOT(setText(QString)), Qt::QueuedConnection);

  // Create a start/stop button
  this->startStopButton = new QPushButton();
  this->startStopButton->installEventFilter(this);
  this->startStopButton->setFocusPolicy(Qt::NoFocus);
  this->startStopButton->setText(QString("Start"));

  this->startStyle =
      "QPushButton {\
         background: qradialgradient(cx: 0.3, cy: -0.4, fx: 0.3, fy: -0.4, \
         radius: 1.35, stop: 0 #ddd, stop: 1 #59b353);\
         border: 2px solid #8bca88;\
         border-radius: 4px;\
         font: bold 20px;\
         color: #eee;\
         margin-right: 10px;\
         margin-left: 10px;\
      }\
      QPushButton:hover {\
         background: qradialgradient(cx: 0.3, cy: -0.4, fx: 0.3, fy: -0.4, \
         radius: 1.35, stop: 0 #ddd, stop: 1 #70c464);\
      }";

  this->stopStyle =
      "QPushButton {\
         background: qradialgradient(cx: 0.3, cy: -0.4, fx: 0.3, fy: -0.4, \
         radius: 1.35, stop: 0 #ddd, stop: 1 #D85C48);\
         border: 2px solid #e18071;\
         border-radius: 4px;\
         font: bold 20px;\
         color: #eee;\
         margin-right: 10px;\
         margin-left: 10px;\
      }\
      QPushButton:hover {\
         background: qradialgradient(cx: 0.3, cy: -0.4, fx: 0.3, fy: -0.4, \
         radius: 1.35, stop: 0 #ddd, stop: 1 #bf5140);\
      }";

  this->startStopButton->setStyleSheet(this->startStyle.c_str());
  this->startStopButton->hide();

  // Add the button to the frame's layout
  frameLayout->addWidget(this->startStopButton);
  connect(this->startStopButton, SIGNAL(clicked()), this,
      SLOT(OnStartStopButton()));
  connect(this, SIGNAL(SetStartStopButton(QString)),
      this, SLOT(OnSetStartStopButton(QString)), Qt::QueuedConnection);

  // Create a reset button
  this->resetButton = new QPushButton();
  this->resetButton->installEventFilter(this);
  this->resetButton->setFocusPolicy(Qt::NoFocus);
  this->resetButton->setText(QString("Reset"));
  this->resetButton->setStyleSheet(
      "QPushButton {\
         background: qradialgradient(cx: 0.3, cy: -0.4, fx: 0.3, fy: -0.4, \
         radius: 1.35, stop: 0 #ddd, stop: 1 #666);\
         border: 2px solid #ccc;\
         border-radius: 4px;\
         font: bold 20px;\
         color: #eee;\
         margin-right: 10px;\
         margin-left: 10px;\
      }\
      QPushButton:hover {\
         background: qradialgradient(cx: 0.3, cy: -0.4, fx: 0.3, fy: -0.4, \
         radius: 1.35, stop: 0 #ddd, stop: 1 #777);\
      }");
  this->resetButton->hide();

  // Add the button to the frame's layout
  frameLayout->addWidget(this->resetButton);
  connect(this->resetButton, SIGNAL(clicked()), this, SLOT(OnResetButton()));

  // Add frameLayout to the frame
  mainFrame->setLayout(frameLayout);

  // Add the frame to the main layout
  mainLayout->addWidget(mainFrame);

  // Remove margins to reduce space
  frameLayout->setContentsMargins(4, 4, 4, 4);
  mainLayout->setContentsMargins(0, 0, 0, 0);

  this->setLayout(mainLayout);

  // Connect to the PreRender Gazebo signal
  this->connections.push_back(event::Events::ConnectPreRender(
                              std::bind(&TimerGUIPlugin::PreRender, this)));

  // Initialize variables
  this->posX = 0;
  this->posY = 0;
}

/////////////////////////////////////////////////
TimerGUIPlugin::~TimerGUIPlugin()
{
}

/////////////////////////////////////////////////
void TimerGUIPlugin::Load(sdf::ElementPtr _elem)
{
  bool hasStartButton = false;
  bool hasResetButton = false;

  // If a countdown time was given in SDF, read the countdown time and
  // initialize the Timer object as a countdown timer.
  // Time is read in SDF as (seconds nanosecnds)
  if (_elem->HasElement("countdown_time"))
  {
    sdf::Time maxTime =
        _elem->GetElement("countdown_time")->Get<sdf::Time>();
    this->timer = common::Timer(common::Time(maxTime.sec, maxTime.nsec), true);
  }

  // Check if there is a start button
  if (_elem->HasElement("start_stop_button"))
  {
    hasStartButton = _elem->Get<bool>("start_stop_button");
    if (hasStartButton)
      this->startStopButton->show();
  }

  // Check if there is a reset button
  if (_elem->HasElement("reset_button"))
  {
    hasResetButton = _elem->Get<bool>("reset_button");
    if (hasResetButton)
      this->resetButton->show();
  }

  // Size this widget
  ignition::math::Vector2d s;
  if (_elem->HasElement("size"))
  {
    s = _elem->Get<ignition::math::Vector2d>("size");
  }

  // Minumum horizontal size
  s.X() = std::max(s.X(), 200.0);

  // Minimum vertical size according to the elements present
  if (hasStartButton && hasResetButton)
    s.Y() = std::max(s.Y(), 120.0);
  else if (hasStartButton || hasResetButton)
    s.Y() = std::max(s.Y(), 80.0);
  else
    s.Y() = std::max(s.Y(), 30.0);

  this->resize(s.X(), s.Y());

  // Position this widget
  if (_elem->HasElement("pos"))
  {
    ignition::math::Vector2d p = _elem->Get<ignition::math::Vector2d>("pos");

    // Negative positions are counted from the ends
    // If there are negative positions, we need to filter window resize
    // events to reposition the timer
    if (p.X() < 0 || p.Y() < 0)
    {
      this->parent()->installEventFilter(this);
    }

    if (p.X() < 0)
    {
      if (this->parent())
      {
        this->posX = p.X() - s.X();
        p.X() = static_cast<QWidget *>(this->parent())->width() + this->posX;
      }
      else
      {
        gzwarn << "Couldn't get parent, setting position x to zero" <<
            std::endl;
        p.X() = 0;
      }
    }

    if (p.Y() < 0)
    {
      if (this->parent())
      {
        this->posY = p.Y() - s.Y();
        p.Y() = static_cast<QWidget *>(this->parent())->height() + this->posY;
      }
      else
      {
        gzwarn << "Couldn't get parent, setting position y to zero" <<
            std::endl;
        p.Y() = 0;
      }
    }

    // Check for x position greater than parent width
    if (this->parent() && p.X() >
        static_cast<QWidget *>(this->parent())->width())
    {
      gzwarn << "GUI widget x pos > parent width, "
        << "clamping to parent width - this widget's width.\n";
      p.X() = static_cast<QWidget *>(this->parent())->width() - this->width();
    }

    // Check for y position greater than parent height
    if (this->parent() &&
        p.Y() > static_cast<QWidget *>(this->parent())->height())
    {
      gzwarn << "GUI widget y pos > parent height, "
        << "clamping to parent height - this widget's height.\n";
      p.Y() = static_cast<QWidget *>(this->parent())->height() -
          this->height();
    }

    this->move(p.X(), p.Y());
  }
  else
  {
    int xPos, yPos;
    if (this->parent())
    {
      xPos = static_cast<QWidget *>(this->parent())->width() - this->width() -
          10;
    }
    else
      xPos = 600;

    yPos = 10;
    this->move(xPos, yPos);
  }

  // Create a node for transportation
  this->node = transport::NodePtr(new transport::Node());
  this->node->Init();

  // Subscribe to the control topic
  if (_elem->HasElement("topic"))
  {
    this->ctrlSub = this->node->Subscribe(_elem->Get<std::string>("topic"),
        &TimerGUIPlugin::OnTimerCtrl, this);
  }
  else
  {
    this->ctrlSub = this->node->Subscribe("~/timer_control",
        &TimerGUIPlugin::OnTimerCtrl, this);
  }
}

/////////////////////////////////////////////////
common::Time TimerGUIPlugin::GetCurrentTime() const
{
  return this->timer.GetElapsed();
}

/////////////////////////////////////////////////
void TimerGUIPlugin::PreRender()
{
  std::lock_guard<std::mutex> lock(this->timerMutex);
  this->SetTime(QString::fromStdString(
      this->timer.GetElapsed().FormattedString(
      common::Time::FormatOption::HOURS)));
}

/////////////////////////////////////////////////
void TimerGUIPlugin::OnTimerCtrl(ConstGzStringPtr &_msg)
{
  if (_msg->data() == "start")
    this->Start();
  else if (_msg->data() == "stop")
    this->Stop();
  else if (_msg->data() == "reset")
    this->Reset();
  else
    gzwarn << "Unable to process command[" << _msg->data() << "]\n";
}

/////////////////////////////////////////////////
void TimerGUIPlugin::Start()
{
  std::lock_guard<std::mutex> lock(this->timerMutex);
  this->timer.Start();

  this->SetStartStopButton("Stop");
}

/////////////////////////////////////////////////
void TimerGUIPlugin::Stop()
{
  std::lock_guard<std::mutex> lock(this->timerMutex);
  this->timer.Stop();

  this->SetStartStopButton("Start");
}

/////////////////////////////////////////////////
void TimerGUIPlugin::OnSetStartStopButton(QString _state)
{
  if (!this->startStopButton->isVisible())
    return;

  this->startStopButton->setText(_state);

  if (_state == "Start")
    this->startStopButton->setStyleSheet(this->startStyle.c_str());
  else if (_state == "Stop")
    this->startStopButton->setStyleSheet(this->stopStyle.c_str());
}

/////////////////////////////////////////////////
void TimerGUIPlugin::Reset()
{
  // stop before resetting
  this->Stop();
  {
    std::lock_guard<std::mutex> lock(this->timerMutex);
    this->timer.Reset();
  }
}

////////////////////////////////////////////////
void TimerGUIPlugin::OnStartStopButton()
{
  if (!timer.GetRunning())
    this->Start();
  else
    this->Stop();
}

////////////////////////////////////////////////
void TimerGUIPlugin::OnResetButton()
{
  this->Reset();
}

/////////////////////////////////////////////////
bool TimerGUIPlugin::eventFilter(QObject *_obj, QEvent *_event)
{
  QWidget *widget = qobject_cast<QWidget *>(_obj);
  if (widget == this->parent() && _event->type() == QEvent::Resize)
  {
    int pX = this->posX;
    int pY = this->posY;

    // Zero values mean that was a positive position, so keep the same
    if (pX == 0)
      pX = this->pos().x();
    else
      pX = widget->width() + pX;

    if (pY == 0)
      pY = this->pos().y();
    else
      pY = widget->height() + pY;

    this->move(pX, pY);
  }
  return QObject::eventFilter(_obj, _event);
}
