/***********************************************************************
**
**   reachablelist.cpp
**
**   This file is part of Cumulus
**
************************************************************************
**
**   Copyright (c):  2004 by Eckhard Völlm
**
**   This file is distributed under the terms of the General Public
**   Licence. See the file COPYING for more information.
**
**   $Id: reachablelist.cpp,v 1.43 2005/02/23 13:55:38 andre Exp $
**
***********************************************************************/
#include <qobject.h>
#include <qfile.h>
#include <qtextstream.h>
#include <qstring.h>
#include <qglobal.h>
#include <qsortedlist.h>

#include "reachablelist.h"
#include "cucalc.h"
#include "mapcontents.h"
#include "glidersite.h"
#include "mapcalc.h"
#include "polar.h"
#include "wp.h"
#include "qdatetime.h"
#include "airport.h"
#include "generalconfig.h"


extern MapContents * _globalMapContents;

int ReachableList::safetyAlt = 0;

// construct from airfield database
ReachablePoint::ReachablePoint(QString name,
                               QString icao,
                               QString description,
                               bool orignAfl,
                               int type,
                               double frequency,
                               WGSPoint pos,
                               QPoint   ppos,
                               int elevation,
                               Distance distance,
                               int bearing,
                               Altitude arrivAlt,
                               int rwDir,
                               int rwLen,
                               int rwSurf,
                               bool rwOpen )
{
    _wp = new wayPoint;
    _wp->name = name;
    _wp->icao = icao;
    _wp->description = description;
    _wp->frequency = frequency;
    _wp->elevation = elevation;
    _wp->importance = 2; // high to make sure it is visible
    _wp->isLandable = rwOpen;
    _wp->surface = rwSurf;
    _wp->runway = rwDir;
    _wp->length = rwLen;
    _wp->sectorFAI = 0;
    _wp->sector1 = 0;
    _wp->sector2 = 0;
    _wp->origP = pos;
    _wp->projP = ppos;
    _wp->type = type;

    _orignAfl = orignAfl;
    _distance = distance;
    _arrivalAlt=arrivAlt;
    _bearing = bearing;
};

// Construct from another WP
ReachablePoint::ReachablePoint(wayPoint *wp,
                               bool orignAfl,
                               Distance distance,
                               int bearing,
                               Altitude arrivAlt )
{
    _wp = new wayPoint(*wp); //copy
    _orignAfl = orignAfl;
    _distance = distance;
    _arrivalAlt=arrivAlt;
    _bearing = bearing;
};

ReachablePoint::~ReachablePoint()
{
    delete _wp;
}


reachable ReachablePoint::getReachable()
{
      if( _arrivalAlt.getMeters() > 0)
         return yes;
      else if( _arrivalAlt.getMeters() > -ReachableList::getSafetyAltititude()  )
         return belowSavety;
      else
         return no;
}

// ReachableList * ReachableList::theInstance = 0;


ReachableList::ReachableList(QObject *parent):  QObject(parent)
{
    GeneralConfig *conf = GeneralConfig::instance();
    safetyAlt = (int) conf->getSafetyAltitude().getMeters();

    setAutoDelete( TRUE );
    lastAltitude = 0.0;
    _maxReach = 0.0;
    tick = 0;
    modeAltitude = false;
    initValuesOK = false;

    _maxNumberOfSites = conf->getMaxNearestSiteCalculatorSites();
    isOn = conf->getNearestSiteCalculatorSwitch();
}

int ReachableList::compareItems (QCollection::Item i1, QCollection::Item i2)
{
    int ret;
    if( modeAltitude )
    {
        if(      ((ReachablePoint *)i1)->getArrivalAlt().getMeters() <
                 ((ReachablePoint *)i2)->getArrivalAlt().getMeters()
          )
            ret = 1;
        else
            ret = -1;
    }
    else
    {
        if(        ((ReachablePoint *)i1)->getDistance().getKilometers() >
                   ((ReachablePoint *)i2)->getDistance().getKilometers()
          )
            ret = 1;
        else
            ret = -1;
    }
    return ( ret );
}

void ReachableList::calculate(bool always)
{
    if( !isOn )
      {
        qDebug("ReachableList::calculate is off");
        return;
      }
    tick++;
    // qDebug("tick %d %d",tick, always );
    // in manual Mode calculate more often full list from big distance changes
    if( (tick %64) == 0 || (always &&((tick %8) == 0)) )
        calculateFullList();
    else if( ((tick %8) == 0) || always )
        calculateGlidePath();
}

void ReachableList::addItemsToList(enum MapContents::MapContentsListID item)
{
    Distance distance;
    QRect bbox;
    bbox=areaBox(lastPosition,_maxReach);
    int r=0, a=0;
    //qDebug("bounding box: (%d, %d), (%d, %d) (%d x %d km)", bbox.left(),bbox.top(),bbox.right(),bbox.bottom(),0,0);

    if( item == MapContents::WaypointList )
    {   // Waypoints have differnet structure treat them here
        QList<wayPoint> *pWPL = _globalMapContents->getWaypointList();
        // qDebug("Nr of Waypoints: %d", pWPL->count() );
        for(unsigned int i=0; i<pWPL->count(); i++ )
        {
            WGSPoint pt = pWPL->at(i)->origP;
            if (!bbox.contains(pt)) {
                //qDebug("Not in bounding box, so ignore! (distance: %d, (%d, %d), %s)", (int)distance.getKilometers(), pt.x(),pt.y(), pWPL->at(i)->name.latin1());
                r++;
                continue;
            }  else {
                a++;
                //qDebug("In bounding box, so accept! (distance: %d, %s)", (int)distance.getKilometers(), pWPL->at(i)->name.latin1());
            }
            distance.setKilometers(dist(&lastPosition,&pt));
            // check if point is a potential rechable candidate at best LD
            if( (distance.getKilometers() > _maxReach ) ||
                    !(pWPL->at(i)->isLandable || (pWPL->at(i)->type == OUTLANDING) )  )
                continue;
            // calculate bearing
            double result=getBearing(lastPosition, pt);
            int bearing =int(result * 57.2958);
            ReachablePoint *rp = new ReachablePoint( pWPL->at(i),
                                                     false,
                                                     distance,
                                                     bearing,
                                                     Altitude( 0 )  );
            append(rp);
        }
    }
    else
    {
        // get number of elements in the list
        int nr = _globalMapContents->getListLength(item);
        // qDebug("Nr of sites: %d type %d", nr, item );
        for(int i=0; i<nr; i++ )
        {
            // get specific site via base class, that contains the needed infos
            Airport* site = (Airport*)_globalMapContents->getElement( item, i );
            WGSPoint pt  = site->getWGSPosition();
            //           distance.setKilometers(dist(&lastPosition,&pt));
            if (!bbox.contains(pt)) {
                //qDebug("Not in bounding box, so ignore! (distance: %d, (%d, %d), %s)", (int)distance.getKilometers(), pt.x(),pt.y(), site->getName().latin1());
                r++;
                continue;
            }  else {
                a++;
                //qDebug("In bounding box, so accept! (distance: %d, %s)", (int)distance.getKilometers(), site->getName().latin1());
            }
            distance.setKilometers(dist(&lastPosition,&pt));
            // qDebug("%d  %f %f", i, (float)distance.getKilometers(),_maxReach );
            // check if point is a potential rechable candidate at best LD
            if( distance.getKilometers() > _maxReach )
                continue;
            // calculate bearing
            double result=getBearing(lastPosition, pt);
            int bearing =int(result * 57.2958);
            // add all potential reachable points to the list, altitude is calculated later
            ReachablePoint *rp = new ReachablePoint( site->getWPName(),
						     site->getICAO(),
						     site->getName(),
						     true,
						     site->getTypeID(),
						     site->getFrequency().toDouble(),
						     site->getWGSPosition(),
						     site->getPosition(),
						     site->getElevation(),
						     distance,
						     bearing,
						     Altitude( 0 ),
						     site->getRunway(0).direction,
						     site->getRunway(0).length,
						     site->getRunway(0).surface,
						     site->getRunway(0).isOpen );
            append(rp);
            // qDebug("%s(%d) %f %d° %d", (const char *)rp->getName(), rp->getElevation(),  rp->getDistance().getKilometers(), rp->getBearing(), (int)rp->getArrivalAlt().getMeters() );

        }
    }
    // qDebug("accepted: %d, rejected: %d. Percent reject: %f",a,r,(100.0*r)/(a+r));
}

QMap<QString, int> ReachableList::arrivalAltMap;
QMap<QString, Distance> ReachableList::distanceMap;

//QColor ReachableList::getReachColor( QString name )
QColor ReachableList::getReachColor( QPoint position )
{
    // qDebug("name: %s: %d", (const char *)name, arrivalAltMap[name] );
    //if( arrivalAltMap.contains( name ) )
    if( arrivalAltMap.contains( ReachableList::coordinateString( position ) ) )
    {
      if( arrivalAltMap[ ReachableList::coordinateString ( position ) ] > safetyAlt )
         {  return( green );  } 
      else if( arrivalAltMap[ ReachableList::coordinateString ( position ) ] > 0 )
         {  return( magenta );   } 
      else
        {   return( red );  }
    }
    return( red );
}

//int ReachableList::getArrivalAlt( QString name )
int ReachableList::getArrivalAlt( QPoint position )
{
    // qDebug("name: %s: %d", (const char *)name, arrivalAltMap[name] );
    //if( arrivalAltMap.contains( name ) )
    if( arrivalAltMap.contains( coordinateString ( position ) ) )
    {
      return( arrivalAltMap[ coordinateString ( position ) ] - safetyAlt );
    }
    return( -9999 );
}

//Altitude ReachableList::getArrivalAltitude( QString name )
Altitude ReachableList::getArrivalAltitude( QPoint position )
{
    // qDebug("name: %s: %d", (const char *)name, arrivalAltMap[name] );
    //if( arrivalAltMap.contains( name ) )
    if( arrivalAltMap.contains( coordinateString ( position ) ) )
    {
      return (Altitude( arrivalAltMap[ coordinateString ( position ) ]) - safetyAlt) ;
    }
    return Altitude(); //return an invalid altitude
}

//Distance ReachableList::getDistance( QString name )
Distance ReachableList::getDistance( QPoint position )
{
    // qDebug("name: %s: %d", (const char *)name, arrivalAltMap[name] );
    //if( distanceMap.contains( name ) )
    if( distanceMap.contains( coordinateString ( position ) ) )
    {
      return( distanceMap[ coordinateString ( position ) ] );
    }
    return Distance();    //return an invalid distance
}

//reachable ReachableList::getReachable( QString name )
reachable ReachableList::getReachable( QPoint position )
{
    // qDebug("name: %s: %d", (const char *)name, arrivalAltMap[name] );
    //if( arrivalAltMap.contains( name ) )
    if( arrivalAltMap.contains( coordinateString ( position ) ) )
    {
      if( arrivalAltMap[ coordinateString ( position ) ] > safetyAlt )
         return yes;
      else if( arrivalAltMap[ coordinateString ( position ) ] > 0 )
         return belowSavety;
      else
         return no;
    }
    return no;
}


// calculate glide path, based on the estimated limited list
void ReachableList::calculateGlidePath()
{
    QTime t;
    t.start();
    int count = 0;
    calcInitValues();
    Distance distance;
    ReachablePoint *p;
    arrivalAltMap.clear();
    distanceMap.clear();
    for( p=first(); p!=0; p=next() )
    {
        // recalculate Distance
        WGSPoint pt = p->getWaypoint()->origP;
        distance.setKilometers(dist(&lastPosition,&pt));
        p->setDistance( distance );
        // recalculate Bearing
        p->setBearing( int(getBearing(lastPosition, pt) * 57.2958) );
        // glide path
        Altitude arrivalAlt;
        Speed bestSpeed;
        if( !calculator->glidePath(p->getBearing(), p->getDistance(),
                                   Altitude(p->getElevation()),
                                   arrivalAlt, bestSpeed ) )
            return;

        p->setArrivalAlt( arrivalAlt );
        //arrivalAltMap[p->getName()] = (int) arrivalAlt.getMeters()+safetyAlt;
        //distanceMap[p->getName()] = distance;
        arrivalAltMap[ coordinateString ( pt ) ] = (int) arrivalAlt.getMeters()+safetyAlt;
        distanceMap[ coordinateString ( pt ) ] = distance;
        if( arrivalAlt.getMeters() > 0 )
            count++;
    }
    modeAltitude = true;
    sort();
    // qDebug("Number of reachable sites (arriv >0): %d", count );
    // qDebug("Time for glide path calculation: %d msec", t.restart() );
    emit newReachList();
    // show();
}

void ReachableList::calcInitValues()
{
    // This info we need from calclulator
    lastPosition = calculator->getlastPosition();
    lastAltitude = calculator->getlastAltitude().getMeters();
    Polar * polar = calculator->getPolar();
    if(!polar)
        return;  // if no glider selected exit !
    Speed speed = polar->bestSpeed(0.0, 0.0, Speed(0));
    // qDebug("speed for best LD= %f", speed.getKph() );
    double ld = polar->bestLD( speed, speed, 0.0 );  // for coarse estimation (no wind)
    _maxReach = MAX( (lastAltitude/1000)*ld, 50.0 ); // look at least within 50km
    // Thats the maximum range we can reach
    // it in hard to get under sea level :-)
    // qDebug("maxReach: %f %f", _maxReach, (float)ld );
}


void ReachableList::calculateFullList()
{
    QTime t;    // timer for performace measurement
    t.start();
    // This info we need from calclulator
    calcInitValues();
    clear();  // clear list
    // Now add items of different type to the list
    addItemsToList(MapContents::AirportList);
    addItemsToList(MapContents::GliderList);
    addItemsToList(MapContents::AddSitesList);
    addItemsToList(MapContents::WaypointList);
    qDebug("Number of potential reachable sites: %d", count() );
    modeAltitude = false;
    sort();
    removeDoubles();
    qDebug("Number of potential reachable sites (after pruning): %d", count() );
    int size = count();
    int nr;
    // Keep the coarse list maximum of double of size as the precise list
    for( nr= getMaxNrOfSites()*2; nr<size; nr++ )
    {
        removeLast();  // remove elements > max number of sites
    }
    // qDebug("Limited Number of potential reachable sites: %d", count() );
    calculateGlidePath();
    // qDebug("Time for full calculation: %d msec", t.restart() );
}

// prints list to qDebug interface
void ReachableList::show()
{
    ReachablePoint *p;
    for( p=first(); p!=0; p=next() )
    {
        qDebug("%s(%d) %f %d° %d", (const char *)p->getName(), p->getElevation(),  p->getDistance().getKilometers(), p->getBearing(), (int)p->getArrivalAlt().getMeters() );

    }
}


ReachableList::~ReachableList()
{
}

/*
ReachableList * ReachableList::instance( void )
{
    // First user will create it
    if( theInstance == 0 )
        theInstance = new ReachableList;
    return( theInstance );
};
*/

// returns number of sites
int ReachableList::getNumberSites()
{
    return count();
}
// returns site at index (max =  getNumberSites()-1)
ReachablePoint *ReachableList::getSite( int index )
{
    ReachablePoint * p = at(index);
    return( p );
}

void ReachableList::removeDoubles()
{
    ReachablePoint * p1;
    ReachablePoint * p2;
    QSortedList<int> removeList;
    
    for (int i=count()-1; i>=1; i--) {
      p1=at(i);
      for (int j=0; j<i; j++) {
        p2=at(j);
        //qDebug("i=%d j=%d, P1=%s, P2=%s,", i, j, p1->getWaypoint()->name.latin1(), p2->getWaypoint()->name.latin1());

        if (p2->getWaypoint()->origP==p1->getWaypoint()->origP) {
            //qDebug("  points are the same");
            //ok, the points are the same. Now we choose wich one to remove
            if (p2->getWaypoint()->name.length()==p1->getWaypoint()->name.length()) {
                //the lenghts of the names are the same, remove the one with the lowest alfabetical value (remember that A<a)
                if (p2->getWaypoint()->name > p1->getWaypoint()->name) {
                    removeList.append(new int(i));
                } else {
                    removeList.append(new int(j));
                }
                //if the names are not of equal length, remove the shortest.
            } else if (p2->getWaypoint()->name.length() > p1->getWaypoint()->name.length()) {
              removeList.append(new int(i));
            } else {
              removeList.append(new int(j));
            }
        } else {
//            qDebug("  points are NOT the same: (%d, %d) vs (%d, %d)",p1->getWaypoint()->origP.x(),p1->getWaypoint()->origP.y(),p2->getWaypoint()->origP.x(),p2->getWaypoint()->origP.y());
            //p1=at(i);
        }
      }
    }
    
    removeList.sort();
    for (int i=removeList.count()-1; i>=0; i--)
    {
      //qDebug("Removing point %d (%s)",*(removeList.at(i)),at(*(removeList.at(i)))->getWaypoint()->name.latin1());
      remove(*(removeList.at(i)));
    }
    /*
    ReachablePoint * p1 = at(count()-1);
    ReachablePoint * p2;

    for (int i=count()-2; i>=0; i--) {
        p2=at(i);

	 qDebug("i=%d, P1=%s, P2=%s,", i, p1->getWaypoint()->name.latin1(), p2->getWaypoint()->name.latin1());

        if (p2->getWaypoint()->origP==p1->getWaypoint()->origP) {
            qDebug("  points are the same");
            //ok, the points are the same. Now we choose wich one to remove
            if (p2->getWaypoint()->name.length()==p1->getWaypoint()->name.length()) {
                //the lenghts of the names are the same, remove the one with the lowest alfabetical value (remember that A<a)
                if (p2->getWaypoint()->name > p1->getWaypoint()->name) {
                    remove(i+1);
                    p1=at(i);
                } else {
                    remove(i);
                }
                //if the names are not of equal length, remove the shortest.
            } else if (p2->getWaypoint()->name.length() > p1->getWaypoint()->name.length()) {
                remove(i+1);
                p1=at(i);
            } else {
                remove(i);
            }
        } else {
            qDebug("  points are NOT the same: (%d, %d) vs (%d, %d)",p1->getWaypoint()->origP.x(),p1->getWaypoint()->origP.y(),p2->getWaypoint()->origP.x(),p2->getWaypoint()->origP.y());
            p1=at(i);
        }
    }*/

}


