/***************************************************************************
                          mapview.cpp  -  description
                             -------------------
    begin                : Sun Jul 21 2002
    copyright            : (C) 2002 by Andre Somers
    email                : andre@kflog.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include <qframe.h>
#include <qpalette.h>
#include <qcanvas.h>
#include <qpe/resource.h>
#include <qpe/global.h>
#include <qwhatsthis.h>
#include "mapview.h"
#include "gpsnmea.h"
#include "cumulusapp.h"
#include "waypointcatalog.h"
#include "mapmatrix.h"
#include "wgspoint.h"
#include "mapcalc.h"
#include "wp.h"
#include "speed.h"
#include "igclogger.h"
#include "mapinfobox.h"
#include "map.h"
#include "generalconfig.h"
#include "interfaceelements.h"
#include "multilayout.h"
#include "filetools.h"

//general KFLOG file token: @KFL
#define KFLOG_FILE_MAGIC    0x404b464c
//file type and version
#define FILE_TYPE 0x4c
#define FILE_VERSION 0x01

#define MAX_LINES 10   //ten lines max for each direction
//#define VLINE_BASE MVM_MAX_ELEMENT;
//#define HLINE_BASE VLINE_BASE+MAX_LINES;

const int VLINE_BASE = MVW_MAX_ELEMENT;
const int HLINE_BASE = VLINE_BASE + MAX_LINES;

MapView::MapView(QWidget *parent, const char *name ) : QWidget(parent,name)
{
    wp = 0;
    resize(parent->size());

    elements = new QVector<QWidget>;
    elements->setAutoDelete(true);
    elements->fill(0, MVW_MAX_ELEMENT + (2 * MAX_LINES) + 1);  //we need some extra space for lines
    loadLayout("/home/andre/Applications/cumulus/default_cruising.cul");

    // Later on the Pretext can change depending on Mode
    GeneralConfig *conf = GeneralConfig::instance();
    _altimeter_mode = conf->getAltimeterGndMode();

    // load pixmap of arrows for relative bearing
    _arrows = Resource::loadPixmap("cumulus/arrows20pix-15.png");

    //make the main box layout
    QBoxLayout *topLayout = new QVBoxLayout( this );
    //layout for the Heading, Bearing, Distance and Speed boxes (top line in the main layout)
    QBoxLayout *HBDSLayout = new QHBoxLayout(topLayout);

    //add Heading widget
    _heading = new MapInfoBox( this );
    _heading->setValue("-");
    _heading->setPreText("Trk");
    HBDSLayout->addWidget( _heading, 18);
    QWhatsThis::add (_heading, tr("Heading"));

    //add Bearing widget
    _bearing = new MapInfoBox( this  );
    _bearing->setValue("-");
    _bearing->setPreText("Brg");
    HBDSLayout->addWidget( _bearing, 18);
    QWhatsThis::add (_bearing, tr("Bearing"));

    // add relative bearing widget
    _rel_bearing = new QLabel(this,"");
    HBDSLayout->addWidget(_rel_bearing, 7);
    QWhatsThis::add (_rel_bearing, tr("Relative bearing"));

    //add Distance widget
    _distance = new MapInfoBox( this );
    _distance->setValue("-");
    _distance->setPreText("Dis");
    HBDSLayout->addWidget( _distance, 20);
    QWhatsThis::add (_distance, tr("Distance"));
    connect(_distance, SIGNAL(mousePress()), this, SLOT(slot_toggleDistanceEta()));

    //add ETA widget
    _eta = new MapInfoBox( this );
    _eta->setValue("-");
    _eta->setPreText( "Eta" );
    HBDSLayout->addWidget( _eta, 20 );
    QWhatsThis::add (_eta, tr("Estimated time of arrival"));
    _eta->hide();
    connect(_eta, SIGNAL(mousePress()), this, SLOT(slot_toggleDistanceEta()));

    //add Speed widget
    _speed = new MapInfoBox( this );
    _speed->setValue("-");
    _speed->setPreText("Gs");
    HBDSLayout->addWidget( _speed, 15);
    QWhatsThis::add (_speed, tr("Speed"));

    //separator
    QFrame * sep=new QFrame(this);
    sep->setFrameStyle(QFrame::Sunken);
    sep->setFrameShape(QFrame::HLine);
    topLayout->addWidget(sep);

    //layout for the Flighttime, Waypoint and Elevation boxes (second line in the main layout)
    QBoxLayout *FWELayout = new QHBoxLayout(topLayout);

    /*
      //add FlightTime widget
      _flighttime = new MapInfoBox( this );
      _flighttime->setPreText( "T: " );
      _flighttime->setValue("02:27");
      FWELayout->addWidget( _flighttime,10 );
    */
    //add Altitude widget
    _altitude = new MapInfoBox( this );
    _altitude->setValue("-");
    // _altitude->setPreText("Msl");    done now accoring to altimeter setting in cumulusapp
    FWELayout->addWidget( _altitude, 20);
    QWhatsThis::add (_altitude, tr("Altitude"));
    connect(_altitude, SIGNAL(mousePress()), (CumulusApp*)parent, SLOT(slotAltimeterMode()));

    //add glide path widget
    _glidepath = new MapInfoBox( this );
    _glidepath->setValue("-");
    _glidepath->setPreText("Arv");
    FWELayout->addWidget( _glidepath,23);
    QWhatsThis::add (_glidepath, tr("Glide path"));
    connect(_glidepath, SIGNAL(mousePress()), (CumulusApp*)parent, SLOT(slotSwitchToReachListView()));

    //add Waypoint widget
    _waypoint = new MapInfoBox( this,10 );
    _waypoint->setValue("-");
    _waypoint->setPreText("To");
    FWELayout->addWidget( _waypoint, 50);
    QWhatsThis::add (_waypoint, tr("Waypoint"));
    connect(_waypoint, SIGNAL(mousePress()), (CumulusApp*)parent, SLOT(slotSwitchToWPListView()));

    //add elevation widget
    /*
    _elevation = new MapInfoBox( this );
    _elevation->setValue("-");
    _elevation->setPreText("Elv");
    FWELayout->addWidget( _elevation,15 );
    QWhatsThis::add (_elevation, tr("Elevation"));
    */

    //separator
    sep=new QFrame(this);
    sep->setFrameStyle(QFrame::Sunken);
    sep->setFrameShape(QFrame::HLine);
    topLayout->addWidget(sep);

    //layout for the Vario, McCready, Speed2fly and Glide Path boxes (third line in the main layout)
    QBoxLayout *VMFGLayout = new QHBoxLayout(topLayout);

    //add vario widget
    _vario = new MapInfoBox(this);
    _vario->setValue("-");
    _vario->setPreText("Var");
    VMFGLayout->addWidget(_vario,22);
    QWhatsThis::add (_vario, tr("Variometer"));
    connect(_vario, SIGNAL(mousePress()), (CumulusApp*)parent, SLOT(slotVarioMode()));

    //add wind widget; this is head/tailwind, no direction given !
    _wind = new MapInfoBox(this);
    _wind->setValue("-");
    _wind->setPreText("Wd");
    VMFGLayout->addWidget(_wind,30);
    QWhatsThis::add (_wind, tr("Wind"));

    //add McCready widget
    _mc = new MapInfoBox( this );
    _mc->setValue("0.0");
    _mc->setPreText("Mc");
    VMFGLayout->addWidget(_mc,20);
    QWhatsThis::add (_mc, tr("McCready"));
    connect(_mc, SIGNAL(mousePress()), (CumulusApp*)parent, SLOT(slotGliderFlight()));

    //add best speed widget
    _speed2fly = new MapInfoBox(this);
    _speed2fly->setValue("-");
    _speed2fly->setPreText("S2f");
    VMFGLayout->addWidget(_speed2fly,20);
    QWhatsThis::add (_speed2fly, tr("Best speed"));

    //separator
    sep=new QFrame(this);
    sep->setFrameStyle(QFrame::Sunken);
    sep->setFrameShape(QFrame::HLine);
    topLayout->addWidget(sep);

    //Add Map widget
    //QCanvasView *map = new QCanvasView(this);
    //  QFrame* mapframe=new QFrame(this, "mapframe");
    //  mapframe->setFrameStyle(QFrame::Panel | QFrame::Raised);
    // topLayout->addSpacing(1);

    QBoxLayout *MapLayout = new QHBoxLayout(topLayout);
    _theMap = new Map(this, "map");
    MapLayout->addSpacing(1);
    MapLayout->addWidget(_theMap, 10);
    MapLayout->addSpacing(1);
    _theMap->setMode(Map::headUp);
    topLayout->addSpacing(0);

    //Add statusbar widget
    _statusbar = new QStatusBar(this, "status");
    _statusbar->setSizeGripEnabled(false);

    _statusGps = new QLabel(tr("Man"),_statusbar);
    //  _statusGps->setFrameStyle(QFrame::Panel | QFrame::Sunken);
    _statusGps->setFrameStyle(QFrame::NoFrame);
    _statusGps->setAlignment(AlignCenter);
    _statusGps->setMargin(0);
    _statusGps->setMaximumSize(28,15);
    _statusGps->setMinimumSize(10,15);
    _statusbar->addWidget(_statusGps);

    _statusFlightstatus = new QLabel("<qt> " + tr("?","Unknown") + "</qt>",_statusbar);
    _statusFlightstatus->setFrameStyle(QFrame::NoFrame);
    _statusFlightstatus->setAlignment(AlignCenter);
    _statusFlightstatus->setMargin(0);
    //_statusFlightstatus->setFixedSize(10,15);
    _statusFlightstatus->setMinimumSize(_statusFlightstatus->fontMetrics().boundingRect(" L ? ").width(),5);
    _statusFlightstatus->setTextFormat(Qt::RichText);
    _statusbar->addWidget(_statusFlightstatus);

    _statusFiller = new QLabel(_statusbar);
    //  _statusLogging->setFrameStyle(QFrame::Panel | QFrame::Sunken);
    _statusFiller->setFrameStyle(QFrame::NoFrame);
    _statusFiller->setAlignment(AlignCenter);
    _statusFiller->setMargin(0);
    _statusFiller->setMaximumSize(this->width(),15);
    _statusbar->addWidget(_statusFiller, 10);

    _statusbar->setMaximumSize(this->width(),18);
    //  _statusbar->setFrameStyle(QFrame::Raised);

    loggingTimer = new QTimer(this);
    connect (loggingTimer, SIGNAL(timeout()), this, SLOT(setFlightStatus()));
    topLayout->addWidget(_statusbar);
    //Activate the layout
    topLayout->activate();
}

MapView::~MapView()
{
  delete elements;
}

/** called if heading has changed */
void MapView::slot_Heading(int head){
    //  QPoint curPos;

//    _heading->setValue(QString("%1°").arg(head));
    _heading->setValue(QString("%1").arg(head));
    _theMap->setHeading(head);
}

/** Called if speed has changed */
void MapView::slot_Speed(const Speed& speed)
{
    if (speed.getMph()<0) {
        _speed->setValue("-");
    } else {
        _speed->setValue(speed.getTextHorizontal(false,0));
    }
}

/** called if the waypoint is changed in the calculator */
void MapView::slot_Waypoint(const QString& wpName)
{
  // qDebug("MapView::slot_Waypoint");
  QString dest;

  if( calculator->getselectedWp() )
    {
      wp = calculator->getselectedWp();
      dest=QString("%1 (%2)").arg(wp->name.left(14)).arg( Altitude::getText(wp->elevation, false, 0));
      _waypoint->setValue(dest);
    }
  else
    {
      // @AP: No waypoint is selected, reset waypoint and glidepath display
      _waypoint->setValue(wpName);
      _glidepath->setValue("-");
    }

    _theMap->quickDraw();  // this is not really helpful -> it is: the bearingline won't change otherwise!
}

/** This slot is called by calculator if a new bearing has been calculated */
void MapView::slot_Bearing(int bearing){
    if (bearing<0) {
        _bearing->setValue("-");
    } else  {
//        QString val=QString("%1°").arg(bearing);
        QString val=QString("%1").arg(bearing);
        _bearing->setValue(val);
    }
}

/** This slot is called by calculator if a new relative bearing has been calculated */
void MapView::slot_RelBearing(int relbearing)
{
    // qDebug("relbearing: %d", relbearing );
    if (relbearing < -360)
    {
        // we need an icon when no relative bearing is available ?!
        return;
    }

    while (relbearing < 0)
        relbearing += 360;
    int rot=((relbearing+7)/15) % 24;  //we only want to rotate in steps of 15 degrees. Finer is not usefull.
    QPixmap arrow (20,20);
    bitBlt(&arrow, 0, 0, &_arrows, rot*20, 0, 20, 20);
    _rel_bearing->setPixmap (arrow);
}

/** This slot is called by calculator if a new distance has been calculated. */
void MapView::slot_Distance(const Distance& distance)
{
    if (distance.getMeters()<0) {
        _distance->setValue("-");
    } else  {
        _distance->setValue(distance.getText(false,1,uint (2)));
    }
}

/** This slot is called by calculator if a new ETA (Estimated Time to Arrival, or the time that is approximatly needed to arrive at the waypoint) has been calculated. */
void MapView::slot_ETA(const QTime& eta)
{
  if( eta.isNull() )
    {
      _eta->setValue("-");
    }
  else
    {
      QString txt; txt.sprintf("%d:%02d", eta.hour(), eta.minute());
      _eta->setValue(txt);
    }
}

/** This slot is called if a new positionfix has been established. */
void MapView::slot_Position(const QPoint& position)
{
    _theMap->checkAirspace (position);
    _statusFiller->setText(WGSPoint::printPos(position.x(),true) +
                           " / " + WGSPoint::printPos(position.y(),false));
}

/** This slot is called if the status of the GPS changes. */
void MapView::slotGPSStatus(GPSNMEA::connectedStatus status){
    if(status>GPSNMEA::notConnected) {
        _statusGps->setText(tr("GPS"));
    } else {
        _statusGps->setText(tr("Man"));
        _heading->setValue("-");
        _speed->setValue("-");
        _altitude->setValue("-");
    }
    _theMap->setShowGlider(status==GPSNMEA::validFix); //only show glider symbol if the GPS is connected.
}

/** This slot is called if a log entry has been made. */
void MapView::slot_LogEntry(){
    loggingTimer->start(750, true);
    setFlightStatus();
}

/** This slot is being called if the altitude has changed. */
void MapView::slot_Altitude(const Altitude& alt){
    Altitude alti = calculator->getAltimeterAltitude();
    _altitude->setValue(alti.getText (false,0));
}

/** This slot is called when the "above glide path" value has changed */
void MapView::slot_GlidePath (const Altitude& above)
{
    _glidepath->setValue(above.getText(false,0));
}

/** This slot is called when the best speed value has changed */
void MapView::slot_bestSpeed (const Speed& speed)
{
    _speed2fly->setValue(speed.getTextHorizontal(false,0));
}

/** This slot is called if a new McCready value has been set */
void MapView::slot_Mc (const Speed& mc)
{
    _mc->setValue (mc.getTextVertical(false,1));
}

/** This slot is called if a new variometer value has been set */
void MapView::slot_vario (const Speed& vario)
{
    _vario->setValue (vario.getTextVertical(false,1));
}

/** This slot is called if a new wind value has been set */
void MapView::slot_wind (Vector& wind)
{  qDebug("slot_wind ");
    QString w;
    w.sprintf("%d°/%s",wind.getAngleDeg(),wind.getSpeed().getTextHorizontal(false,0).latin1() );
    _wind->setValue (w);
    _theMap->slotNewWind();
}

/** This slot is called if the settings have been changed. It refreshes all displayed data because units might have beeen changed. */
void MapView::slot_settingschange()
{
    // qDebug("MapView::slot_settingschange");
    slot_Altitude(calculator->getlastAltitude());
    slot_Position(calculator->getlastPosition());
    slot_Speed(calculator->getlastSpeed());
    slot_Distance(calculator->getlastDistance());
    slot_GlidePath(calculator->getlastGlidePath());
    slot_bestSpeed(calculator->getlastBestSpeed());
    slot_Waypoint("");
    _theMap->slot_settingschanged();
}

/** This slot is called if the number of satelites changes. */
void MapView::slotSatConstellation()
{
    if (gps)
    {
        SatInfo info = gps->getLastSatInfo();
        QString msg = QString ("G-%1").arg(info.satCount);
        //    QString msg = QString ("%1").arg(info.satCount);
        _statusGps->setText (msg);
    }
}

/** This slot updates the FlightStatus statusbar-widget with the current logging and flightmode status */
void MapView::setFlightStatus(){
    QString status="<qt>";
    //logging status
    if (logger->getisLogging()) {                    //are we logging right now?
        if (loggingTimer->isActive()) {                //  we are. Is the internal loggingTimer active (wich means there was an entry recently)?
            status+="<b>" + tr("L","Logging") + " </b>"; //    yes, so insert a bold faced L.
        } else {
            status+=tr("L","Logging") + " ";             //    no, so just insert an L
        }
    } else {
        if (logger->getisStandby()) {
            status+=tr("Ls","LoggingStandby") + " ";
        } else {
            status+="";                                    //  we are not logging
        }
    }

    //flightmode status
    switch (calculator->currentFlightMode()) {
    case CuCalc::unknown: status+=(tr("?","Unknown"));
        break;
    case CuCalc::standstill: status+=(tr("S","Standstill"));
        break;
    case CuCalc::cruising: status+=(tr("C","Cruising"));
        break;
    case CuCalc::circlingL: status+=(tr("L","Circling Left"));
        break;
    case CuCalc::circlingR: status+=(tr("R","Circling Right"));
        break;
    case CuCalc::wave: status+=(tr("W","Wave"));
        break;
    }

    //finalize
    status+="</qt>";
    _statusFlightstatus->setText(status);

}

bool MapView::loadLayout(QString pathName) {
    Q_INT8 typeID, versionID;
    Q_UINT32 magic;
    QString name;

    qDebug ("reading file %s", pathName.latin1() );

    //check if we have a filename
    if(pathName.isEmpty())
        // File does not exist
        return false;

    //check if we can open the file
    QFile f(pathName);
    if(!f.open(IO_ReadOnly))
    {
      // Can't read input data:
      // We need to output a warning...
       qWarning("Cumulus: Can't open layout file %s for reading!"
        "Arborting ...",
        pathName.latin1() );
       return false;
    }

    QDataStream in(&f);
    in.setVersion(2);

    //check magic key
    in >> magic;
    if( magic != KFLOG_FILE_MAGIC ) { // wrong source file
        f.close();

        qWarning("Cumulus: wrong magic key %x read! Arborting ...", magic);
        return false;
    }

    //check filetype
    in >> typeID;
    if( typeID != FILE_TYPE ) { // wrong source file
        f.close();

        qWarning("Cumulus: wrong file type key %x read! Arborting ...", typeID);
        return false;
    }

    //check fileversion
    in >> versionID;
    if( versionID != FILE_VERSION ) { // wrong source file
        f.close();

        qWarning("Cumulus: wrong file version %x read! Arborting ...", versionID);
        return false;
    }

    //ok, it seems we have a valid layout file.

    //get the layout name
    ShortLoad(in, name);
    qDebug("Loading layout '%s'", name.latin1());

    QBoxLayout * l = new QVBoxLayout(); //this is the "master layout"
    getElement(0, true); //reset linecounter
    loadLayoutElement(in, l); //start loading elements

    f.close();
    return true;
}

bool MapView::loadLayoutElement(QDataStream& in, QBoxLayout * l) {
    Q_INT8 typeID, flags, eCnt;
    bool result=true;
    QBoxLayout * childLayout;

    in >> typeID;
    qDebug("   loading element type %d", typeID);

    switch (typeID) {
      case 0:
        return true;

      case MVW_HOR_LAYOUT:
        childLayout = new QHBoxLayout(l);
        in >> eCnt;
        for (int i=0; i < eCnt && result; i++) {
          result=result && loadLayoutElement(in, childLayout);
        }
        return result;

      case MVW_VER_LAYOUT:
        childLayout = new QVBoxLayout(l);
        in >> eCnt;
        for (int i=0; i < eCnt && result; i++) {
          result=result && loadLayoutElement(in, childLayout);
        }
        return result;

      case MVW_LINE:
        in >> flags;
        if (l->direction()==QBoxLayout::LeftToRight || l->direction()==QBoxLayout::RightToLeft) {
          //horizontal parent layout, so make a vertical line
        } else {
          //vertical parent layout, so make a horizontal line
        }
        return true;
      default:
        qDebug ("Element type not handled!");
        return false;

    }  //switch

}

QWidget * MapView::getElement(int typeID, bool resetLineCounter=false) {
  static int hLineCnt;
  static int vLineCnt;

  QFrame * sep;
  Map * m;

  if (resetLineCounter) {
    hLineCnt=0;
    vLineCnt=0;
  }

  switch (typeID) {
    case 0:
      return 0;

    case 1: //special case, used for lines in horizontal layouts (vertical lines)
      vLineCnt++;
      if (!elements->at(VLINE_BASE + vLineCnt)) {
        sep=new QFrame(this);
        sep->setFrameStyle(QFrame::Sunken);
        sep->setFrameShape(QFrame::VLine);
        elements->insert(VLINE_BASE + vLineCnt, sep);
      }
      return elements->at(VLINE_BASE + vLineCnt);

    case 2: //special case, used for lines in vertical layouts (horizontal lines)
      hLineCnt++;
      if (!elements->at(HLINE_BASE + hLineCnt)) {
        sep=new QFrame(this);
        sep->setFrameStyle(QFrame::Sunken);
        sep->setFrameShape(QFrame::HLine);
        elements->insert(HLINE_BASE + hLineCnt,sep);
      }
      return elements->at(HLINE_BASE + hLineCnt);

    case MVW_MAP:
      if (!elements->at(MVW_MAP)) {
        m=new Map(this, "mapwidget");
        elements->insert(MVW_MAP,m);
        //_theMap=m;
      }
      return elements->at(MVW_MAP);

    default:
      qDebug("Element type not handled!");
  } //switch

  return (QWidget *) 0;
}

/** toggle between distance and eta widget on mouse signal */
void MapView::slot_toggleDistanceEta()
{
  if( _distance->isVisible() )
    {
      _distance->hide();
      _eta->show();
    }
  else
    {
      _distance->show();
      _eta->hide();
    }
}
