/***************************** LICENSE START ***********************************

 Copyright 2014 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/
#include "MvQWeatherRoomDialog.h"

#include <QComboBox>
#include <QDialogButtonBox>
#include <QFile>
#include <QImageReader>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QMouseEvent>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QPainter>
#include <QSettings>
#include <QTextStream>
#include <QVBoxLayout>

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>


MvQVideoWallCellInfo::MvQVideoWallCellInfo(double x, double y, double w, double h, int number)
{
   x_ = x;
   y_ = y;
   w_ = w;
   h_ = h;
   number_ = number;
}


//===========================================
//
// MvQVideoWallServer
//
//============================================

MvQVideoWallServer::MvQVideoWallServer(QObject* parent) :
   QObject(parent),
   wallInfo_(0)
{
   QNetworkAccessManager *manager = new QNetworkAccessManager(this);

   connect( manager, SIGNAL(finished(QNetworkReply*)),
            this, SLOT(slotWallServerReply(QNetworkReply*)) );

   const char *caux = getenv ("METVIEW_WALL_URL");
   if (!caux)
   {
      QMessageBox::warning(0, QString("Metview"),
                           tr("Env var METVIEW_WALL_URL is not defined"));
      return;
   }
   URL_ = caux;

   caux = getenv ("METVIEW_WALL_TOKEN");
   if (!caux)
   {
      QMessageBox::warning(0, QString("Metview"),
                           tr("Env var METVIEW_WALL_TOKEN is not defined"));
      return;
   }
   token_ = caux;

   QString req = QString("%1/get-wall-list/?key=%2").arg(URL_).arg(token_);

   QNetworkRequest netReq(QUrl::fromPercentEncoding(req.toUtf8()));

   manager->get(netReq);
}

void MvQVideoWallServer::slotWallServerReply(QNetworkReply* reply)
{
   if(reply->error() != QNetworkReply::NoError)
   {
      QMessageBox::warning(0, QString("Metview"),
                           tr("Unable to get server response.\n %1").arg(reply->errorString()));

      reply->deleteLater();
      return;
   }

   // Get response from the Wall Info request
   QByteArray ba = reply->readAll();
   QString res(ba);

   // Retrieve list of walls
   if ( !this->parseWallServer( res ) )
   {
      reply->deleteLater();
      return;
   }

   emit gotServerResponse();

   reply->deleteLater();
}

bool MvQVideoWallServer::parseWallServer( QString& response )
{
   // Write the response to a temporary text file
   QString tmpFilename(marstmp());
   QFile tmpFile(tmpFilename);
   tmpFile.open(QIODevice::WriteOnly);

   // Check if the temporary file was opened
   if (!tmpFile.isOpen())
   {
      QMessageBox::warning(0, QString("Metview"),
                           tr("Unable to open temporary file %1").arg(tmpFilename));
      return false;
   }

   // Write the server response to the file
   QTextStream outStream(&tmpFile);
   outStream << response;
   tmpFile.close();

   // Parse the response using the boost JSON property tree parser
   using boost::property_tree::ptree;
   ptree pt;
   std::string tmpFilenameStd = tmpFilename.toUtf8().constData();
   try
   {
      read_json(tmpFilenameStd, pt);
   }
   catch (const boost::property_tree::json_parser::json_parser_error& e)
   {
      QMessageBox::warning(0, QString("Metview"),
                           tr("Unable to parse JSON response from the server"));
      return false;
   }

   // Get list of walls
   wallNames_.clear();
   wallIds_.clear();
   for (ptree::const_iterator itOuter = pt.begin(); itOuter != pt.end(); ++itOuter)
   {
      if (itOuter->first != "walls")
         continue;

      ptree const &layout = itOuter->second;
      MvQVideoWallInfo wallInfo;

      // Loop walls
      for (ptree::const_iterator itLayout = layout.begin(); itLayout != layout.end(); ++itLayout)
      {
         ptree const &layout = itLayout->second;

         // For a given wall, find the title and name info
         for (ptree::const_iterator itRows = layout.begin(); itRows != layout.end(); ++itRows)
         {
            if ( itRows->first == "title" )
               wallNames_.push_back(itRows->second.get_value<std::string>("name"));
            else if ( itRows->first == "name" )
               wallIds_.push_back(itRows->second.get_value<std::string>("name"));
         }
      }
   }

   // Consistency check
   if ( wallNames_.empty() || wallNames_.size() != wallIds_.size() )
   {
      QMessageBox::warning(0, QString("Metview"),
                           tr("Problem parsing JSON file: %1").arg(tmpFilename));
      return false;
   }

   return true;
}

void MvQVideoWallServer::createWallLayout( int index )
{
   // Build and call a network request to inquiry about a layout
   // related to the input index.
   // Instantiate a network
   QNetworkAccessManager* manager = new QNetworkAccessManager(this);
   connect( manager, SIGNAL(finished(QNetworkReply*)),
            this, SLOT(slotWallLayoutReply(QNetworkReply*)) );

   QString aux = wallIds_[index].c_str();
   QString req = QString("%1/wall-status/?key=%2&json={\"wall\":\"%3\"}").arg(URL_).arg(token_).arg(aux);

   QNetworkRequest netReq(QUrl::fromPercentEncoding(req.toUtf8()));

   manager->get(netReq);
}

void MvQVideoWallServer::slotWallLayoutReply(QNetworkReply* reply)
{
   if(reply->error() != QNetworkReply::NoError)
   {
      QMessageBox::warning(0, QString("Metview"),
                           tr("Unable to get wall layout response.\n %1").arg(reply->errorString()));

      // Clear layout
       if ( wallInfo_ )
       {
            delete wallInfo_;
            wallInfo_=0;
       }

       emit gotServerLayoutResponse();

       reply->deleteLater();
       return;
   }

   // Get response from the wall layout request
   QByteArray ba = reply->readAll();
   QString res(ba);

   // Retrieve layout
   if ( !this->parseWallLayout( res ) )
   {
      reply->deleteLater();
      return;
   }

   emit gotServerLayoutResponse();

   reply->deleteLater();
}

bool MvQVideoWallServer::parseWallLayout( QString &response )
{
   // Write the response to a temporary text file
   QString tmpFilename(marstmp());
   QFile tmpFile(tmpFilename);
   tmpFile.open(QIODevice::WriteOnly);

   // Check if the temporary file was opened
   if (!tmpFile.isOpen())
   {
      QMessageBox::warning(0, QString("Metview"),
                      tr("Unable to open temporary file: %1").arg(tmpFilename));
      return false;
   }

   // Write the server response to the file
   QTextStream outStream(&tmpFile);
   outStream << response;
   tmpFile.close();

   // Parse the response using the boost JSON property tree parser
   using boost::property_tree::ptree;
   ptree pt;
   std::string tmpFilenameStd = tmpFilename.toUtf8().constData();
   try
   {
      read_json(tmpFilenameStd, pt);
   }
   catch (const boost::property_tree::json_parser::json_parser_error& e)
   {
      QMessageBox::warning(0, QString("Metview"),
                      tr("Unable to parse JSON response from the server"));
      return false;
   }

   // Main Loop
   for (ptree::const_iterator itOuter = pt.begin(); itOuter != pt.end(); ++itOuter)
   {
      if ( itOuter->first != "config" )
         continue;

      // Start a new wall layout. There is only one layout to be built.
      if ( wallInfo_ )
         delete wallInfo_;

      wallInfo_ = new MvQVideoWallInfo;

      // Process the config tag
      ptree const &layout = itOuter->second;
      for (ptree::const_iterator itLayout = layout.begin(); itLayout != layout.end(); ++itLayout)
      {
         if (itLayout->first != "layout")
            continue;

         // Find out the layout design (shape and number of cells) by
         // looping each cell and looking for colspan and rowspan
         int totalRows = 0; // must take into account the rowspans
         int totalCols = 0; // must take into account the colspans
         int r = 0;
         ptree const &layout = itLayout->second;
         for (ptree::const_iterator itRows = layout.begin(); itRows != layout.end(); ++itRows)
         {
            // For each tag in the current cell
            ptree const &row = itRows->second;
            int c = 0;
            for (ptree::const_iterator itCols = row.begin(); itCols != row.end(); ++itCols)
            {
               int colSpan = itCols->second.get<int>("colspan", 1);
               int rowSpan = itCols->second.get<int>("rowspan", 1);

               if (r + rowSpan > totalRows)
                  totalRows = r + rowSpan;

               if (c + colSpan  > totalCols)
                  totalCols = c + colSpan;

               c += colSpan;
            }
            r += 1;
         }

         // Build the Qt layout by looping each cell for the second time
         int cellNumber = -1;
         double rowHeight = 1.0 / totalRows;
         double currentY  = 0.0;

         for (ptree::const_iterator itRows = layout.begin(); itRows != layout.end(); ++itRows)
         {
            ptree const &row = itRows->second;
            double colWidth  = 1.0 / totalCols;
            double currentX  = 0.0;

            // For each tag in the current cell
            for (ptree::const_iterator itCols = row.begin(); itCols != row.end(); ++itCols)
            {
               string cellStr = itCols->second.get<string>("name", "no-nameX");
               if (cellStr != "no-nameX")
               {
                  int colSpan = itCols->second.get<int>("colspan", 1);
                  int rowSpan = itCols->second.get<int>("rowspan", 1);

                  cellNumber++;
                  MvQVideoWallCellInfo cellInfo(currentX, currentY, colWidth*colSpan, rowHeight*rowSpan, cellNumber);
                  wallInfo_->addCellInfo(cellInfo);

                  // set up the x-coord for the next column
                  currentX += colWidth*colSpan;
               }
               else
                 currentX += colWidth;
            }

            // set up the y-coord for the next row
            currentY += rowHeight;
         }

         break;   // Wall layout has been created
      }
   }

   return true;
}

// Returns -1 if a wall of that name does not exist
int MvQVideoWallServer::wallInfoIndexFromName( const string& wallName )
{
   if ( wallName.empty() )
      return -1;

   for(size_t i = 0; i < numWalls(); i++)
   {
      if ( wallNames_[i] == wallName )
         return i;
   }

   // No wall found
   return -1;
}

// If the wallName does not exist, returns the first Id
string MvQVideoWallServer::wallInfoId( const string& wallName )
{
   int index = wallInfoIndexFromName(wallName);

   return (index == -1) ? wallIds_[0] : wallIds_[index];
}

//===========================================
//
// MvQVideoWallWidget
//
//============================================

MvQVideoWallWidget::MvQVideoWallWidget(QWidget *parent) : 
  QWidget(parent),
  offset_(1),
  current_(0)
{
   setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum));
   setMinimumSize(QSize(128,128));

   setMouseTracking(true);

   int size = 128;
   QImageReader imgR(":/uPlot/wall_screen.svg");
   if(imgR.canRead())
   {
      imgR.setScaledSize(QSize(size,size));
      QImage img=imgR.read();
      pix_=QPixmap::fromImage(img);
   }

   QImageReader imgRs(":/uPlot/wall_screen_selected.svg");
   if(imgRs.canRead())
   {
      imgRs.setScaledSize(QSize(size,size));
      QImage img=imgRs.read();
      pixSelect_=QPixmap::fromImage(img);
   }
}

// Populates the video wall widget with the screens from the given wall info
void MvQVideoWallWidget::setWall( MvQVideoWallInfo* info )
{
   if (!info)
      items_ << QRect(0,0,100,100);
   else
   {
      items_.clear();
      for (unsigned int i = 0; i < info->cellInfos().size(); i++)
      {
         MvQVideoWallCellInfo *cell = &(info->cellInfos()[i]);
         items_ << QRect(cell->x()*100., cell->y()*100., cell->w()*100., cell->h()*100.);
      }
      setEnabled(true);
   }

   update();
}

void MvQVideoWallWidget::paintEvent(QPaintEvent*)
{
   int w=width()-2*offset_;
   int h=height()-2*offset_;

   QPainter painter(this);

   if (!isEnabled())
   {
      QString message("Retrieving layout from server...");
      QTextOption textOptions;
      textOptions.setAlignment(Qt::AlignHCenter);
      textOptions.setWrapMode(QTextOption::WordWrap);

      painter.drawText(QRect(0,0,w,h), message, textOptions);
      return;
   }

   QColor col(128,128,255);
   QBrush brush;
   brush.setStyle(Qt::SolidPattern);

   painter.setPen(Qt::black);

   for(int i=0; i < items_.count(); i++)
   {
      QRectF r(offset_+(w*items_[i].x())*0.01,offset_+(h*items_[i].y())*0.01,
               (w*items_[i].width())*0.01,(h*items_[i].height())*0.01);

      if( i == current_ )
         painter.drawPixmap(r,pixSelect_,QRectF(0,0,pixSelect_.width(),pixSelect_.height()));
      else
         painter.drawPixmap(r,pix_,QRectF(0,0,pix_.width(),pix_.height()));
   }
}

// Selects a screen in the video wall; called, for instance, when the user
// types a new screen number into the text box
void MvQVideoWallWidget::updateCellSelection(const QString &selection)
{
   int screenNum = selection.toInt();
   current_ = screenNum-1;
   update();
}


void MvQVideoWallWidget::mousePressEvent(QMouseEvent *event)
{
   if(select(event->pos()))
   {
      update();
      QString newSelection;
      newSelection.setNum(current_+1);
      emit selectionChanged(newSelection);
   }
}

bool MvQVideoWallWidget::select(QPoint pos)
{
   int x=(pos.x()-offset_)*100/width();
   int y=(pos.y()-offset_)*100/height();

   for(int i = 0; i < items_.count(); i++)
   {
      if(items_.at(i).contains(QPoint(x,y)))
      {
         current_  = i;
         return true;
      }
   }

   return false;
}

//===========================================
//
// MvQWeatherRoomDialog
//
//============================================

MvQWeatherRoomDialog::MvQWeatherRoomDialog(int currentFrame, int totalFrames, QWidget *parent) :
   QDialog(parent),
   currentFrame_(currentFrame),
   totalFrames_(totalFrames),
   lastWall_(""),
   lastCell_(1),
   updatingFromServer_(true)
{
   // Wall combo
   QVBoxLayout* vb = new QVBoxLayout();
   setLayout(vb);
   QLabel *label = new QLabel(tr("Video wall:"),this);
   wallCb_ = new QComboBox(this);
   QHBoxLayout* hb = new QHBoxLayout();
   hb->addWidget(label);
   hb->addWidget(wallCb_,1);
   vb->addLayout(hb);

   // Initialize wall server
   wallServer_ = new MvQVideoWallServer(this);
   connect(wallServer_, SIGNAL(gotServerResponse()), this, SLOT(updateWallServerSelector()));
   connect(wallServer_, SIGNAL(gotServerLayoutResponse()), this, SLOT(updateWallLayoutSelector()));
   connect(wallCb_, SIGNAL(currentIndexChanged(int)), this, SLOT(updateWallSelection(int)));
   wallCb_->addItem("No wall");

   //Cell selector
   cellSelector_= new MvQVideoWallWidget(this);
   vb->addWidget(cellSelector_,1);
   cellSelector_->setWall(NULL);
   cellSelector_->setEnabled(false);

   // Cell selection text box (for if the layout plot is not sufficient)
   QHBoxLayout* hb2 = new QHBoxLayout();
   QLabel *screenSelectionLabel = new QLabel(tr("Cell:"),this);
   hb2->addWidget(screenSelectionLabel);
   leCellSelection_ = new QLineEdit(this);
   QValidator *intValidator = new QIntValidator(1, 100, this);
   leCellSelection_->setValidator(intValidator);
   hb2->addWidget(leCellSelection_);
   vb->addLayout(hb2);

   // Two-way connection between the screen selection widget and text box
   connect(leCellSelection_, SIGNAL(textEdited(const QString &)),
           cellSelector_, SLOT(updateCellSelection(const QString &)));

   connect(cellSelector_, SIGNAL(selectionChanged(const QString &)),
           leCellSelection_, SLOT(setText(const QString &)));

   // Frame selection
   //buildFrameSelection(vb);

   // Buttonbox
   QDialogButtonBox* buttonBox= new QDialogButtonBox(this);
   buttonBox->addButton(QDialogButtonBox::Ok);
   buttonBox->addButton(QDialogButtonBox::Cancel);

   connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
   connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));

   vb->addWidget(buttonBox);

   // Read the previous gui settings
   readSettings();

   // Initialise the text box
   QString newSelection;
   newSelection.setNum(lastCell_);
   leCellSelection_->setText(newSelection);
}

// Destructor
MvQWeatherRoomDialog::~MvQWeatherRoomDialog()
{
   writeSettings();
}

void MvQWeatherRoomDialog::setVisible(bool b)
{
   QDialog::setVisible(b);
}

void MvQWeatherRoomDialog::updateWallSelection(int wallIndex)
{
   if ( !updatingFromServer_ && (wallIndex > -1) && (wallServer_->numWalls() > 0) )
      wallServer_->createWallLayout(wallIndex);
}

void MvQWeatherRoomDialog::updateWallServerSelector()
{
   // Update the list of walls
   updatingFromServer_ = true;
   wallCb_->clear();
   for (size_t i = 0; i < wallServer_->numWalls(); i++)
   {
      QString name = wallServer_->wallInfoName(i).c_str();
      if ( !name.isEmpty() )
         wallCb_->addItem(name);
   }

   // Get the wall index. Previous one if exists or the first one by default
   int lastWallIndex = wallServer_->wallInfoIndexFromName(lastWall_);
   if ( lastWallIndex < 0 )
   {
      lastCell_ = 1;  // default value
      lastWallIndex = 0;
   }

   // Update drop down menu interface
   wallCb_->setCurrentIndex(lastWallIndex);
   updatingFromServer_ = false;  // we've finished updating the list

   // Call a second network request to get the wall layout
   wallServer_->createWallLayout(lastWallIndex);
}

void MvQWeatherRoomDialog::updateWallLayoutSelector()
{
   cellSelector_->setWall(wallServer_->wallInfo());
   setSelectedCell(this->getCell());
}

// Forces the given cell to be the selected one
void MvQWeatherRoomDialog::setSelectedCell(int icell)
{
   int cellNumber = ( icell > wallServer_->numCells() || icell < 1 ) ? 1 : icell;
   QString newSelection;
   newSelection.setNum(cellNumber);
   leCellSelection_->setText(newSelection);
   cellSelector_->updateCellSelection(newSelection);
}

int MvQWeatherRoomDialog::getCell ()
{
   int cell = leCellSelection_->text().toInt();
   return cell;
}

string MvQWeatherRoomDialog::getWallName ()
{
   return wallCb_->currentText().toStdString();
}

string MvQWeatherRoomDialog::getWallId ()
{
   string name = this->getWallName();
   return wallServer_->wallInfoId(name);
}

// Called when the user clicks Ok or <CR>
void MvQWeatherRoomDialog::accept()
{
   lastWall_ = getWallName();  // store the selections
   lastCell_ = getCell();      // store the selections

   // Check if selected wall is available
   if ( !wallServer_->wallInfo() )
   {
      QMessageBox::warning(0, QString("Metview"), tr("Selected wall is not available.\n Please select another wall"));
      return;
   }

   // Check if cell number is valid
   if ( lastCell_ < 1 || lastCell_ > cellSelector_->numCells() )
   {
      QString message = QString("Invalid cell number.\n Please select a value between 1 and %1").arg(cellSelector_->numCells());
      QMessageBox::warning(0, QString("Metview"), tr(message.toStdString().c_str()));
      return;
   }

   QDialog::accept();
}

void MvQWeatherRoomDialog::readSettings()
{
   // Restore previous gui settings
   QSettings settings("ECMWF","uPlot_MvQWeatherRoomDialog");
   settings.beginGroup("main");
   if(settings.contains("size"))
      resize(settings.value("size").toSize());
   else
      resize(QSize(420,400));

   settings.endGroup();

   if(settings.contains("last_wall"))
      lastWall_ = settings.value("lastWall").toString().toStdString();

   lastCell_ = settings.value("lastCell", 1).toInt();
}

void MvQWeatherRoomDialog::writeSettings()	
{
   // Save current gui settings
   QSettings settings("ECMWF","uPlot_MvQWeatherRoomDialog");
   settings.beginGroup("main");
   settings.setValue("size",size());
   settings.endGroup();

   settings.setValue("lastWall", QString(lastWall_.c_str()));
   settings.setValue("lastCell", lastCell_);
}

#if 0
void MvQWeatherRoomDialog::slotRangeFrameSelection(bool checked)
{
   // Enable/Disable line edit
   //leFs3_->setEnabled(checked);
}

void MvQWeatherRoomDialog::buildFrameSelection(QLayout *layout)
{
   // Group Box with radio buttons and one line edit
   QGroupBox* gbFs = new QGroupBox( tr("Frame Selection"),this );
   cbFs1_ = new QRadioButton( tr("&Current"), gbFs);
   cbFs2_ = new QRadioButton( tr("&All"), gbFs );
   cbFs3_ = new QRadioButton( tr("&Range"), gbFs );
   cbFs1_->setChecked(true);

   // Line edit with a validator
   leFs3_ = new QLineEdit(this);
   leFs3_->setToolTip(tr("Specify one or more frame ranges, e.g. 1-3,6,7"));
   leFs3_->setEnabled(false);
   QRegExp rx("^\\d{1,}(-\\d{1,})?(,\\d{1,}|,\\d{1,}-\\d{1,})*");
   QValidator *validator = new QRegExpValidator(rx, this);
   leFs3_->setValidator(validator);

   // Set layout
   QGridLayout* glFs = new QGridLayout( gbFs );
   glFs->addWidget( cbFs1_, 0, 0 );
   glFs->addWidget( cbFs2_, 1, 0 );
   glFs->addWidget( cbFs3_, 2, 0 );
   glFs->addWidget( leFs3_, 2, 1 );

   connect(cbFs3_,SIGNAL(toggled(bool)), this, SLOT(slotRangeFrameSelection(bool)));

   // Attach widget to the parent
   layout->addWidget(gbFs);
}

bool MvQWeatherRoomDialog::getFrameSelection( MvRequest& req )
{
   // Three options: CURRENT, ALL, RANGE
   // ALL: nothing need to be done
   if ( cbFs2_->isChecked() )
      return true;

   // CURRENT: get the current frame numbeer
   int i;
   QList<int> list;
   if ( cbFs1_->isChecked() )
   {
      list.append(current_+1);  // start from 1, not from 0
   }
   else  // RANGE
   {
      // Get the list of range entries (split by comma)
      QStringList list1 = leFs3_->text().split(",", QString::SkipEmptyParts);

      // Analyse all entries
      for ( i = 0; i < list1.size(); i++)
      {
         // Check if it is a single value or a range of values
         QStringList list2 = list1.at(i).split("-", QString::SkipEmptyParts);
         if ( list2.size() == 1 ) // single value
            list.append(list2.at(0).toInt());
         else
         {
            int i1 = list2.at(0).toInt();
            int i2 = list2.at(1).toInt();
            if ( i1 > i2 )  // invert sequence
            {
               int iaux = i1;
               i1 = i2;
               i2 = iaux;
            }
            for ( int j = i1; j <= i2; j++ )
               list.append(j);
         }
      }

      // Check if the range of values is valid
      qSort(list.begin(), list.end());
      if ( list.at(list.size()-1) > total_ )
      {
         ostrstream info;
         info << "Enter values from 1 to " << total_ << ends;
         errorMessage("Invalid range!",(const char*)info.str());
         return false;
      }
   }

   // Save list of frames selection for all drivers
   while ( req )
   {
      for ( i = 0; i < list.size(); i++)
         req.addValue("OUTPUT_FRAME_LIST",list.at(i));

      req.advance();
   }

   req.rewind();

   return true;
}

void MvQWeatherRoomDialog::errorMessage (const char* msg, const char* info)
{
   QMessageBox msgBox;
   msgBox.setIcon(QMessageBox::Critical);
   msgBox.setWindowTitle(tr("Export Error"));
   msgBox.setText(QString::fromUtf8(msg));
   if ( info )
      msgBox.setInformativeText(QString::fromUtf8(info));

   msgBox.exec();
}
#endif
