/**
 * @file
 * $Id$
 * $Revision$
 * $Author$
 * $Date$
 *
 * This file is part of The iWear Framework.
 * In particular this file is part of the (WLAN) Location package
 *
 * The iWear Framework 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 as in version 2 of the License.

 * 
 * The iWear Framework is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * The iWear Framework; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifndef __IWLOCATOR_LOCATION_H
#define __IWLOCATOR_LOCATION_H


#include "iwear/db/dbstorable.h"
#include "iwear/polygon.h"

extern "C" {
#include <sys/time.h>
}

#include <map>
#include <string>
using namespace std;

namespace iwear 
{
    namespace sensor
    {
	namespace location
	{
struct LocationSymbol
{
public:
    string city;
    string area;
    string building;
    string level;
    string room;
//    string comment;
    double pos_x;
    double pos_y;
    double pos_z;
//    double pos_rms;
    LocationSymbol& City( const string& c )     { city = c; return *this; }
    LocationSymbol& Area( const string& a )	{ area = a; return *this; }
    LocationSymbol& Building( const string& b )	{ building = b; return *this; }
    LocationSymbol& Level( const string& l )	{ level = l; return *this; }
    LocationSymbol& Room( const string& r )	{ room = r; return *this; }

    LocationSymbol& PosX( double x )	{ pos_x = x; return *this; }
    LocationSymbol& PosY( double y )	{ pos_y = y; return *this; }
    LocationSymbol& PosZ( double z )	{ pos_z = z; return *this; }

    LocationSymbol( ) : pos_x(0), pos_y(0), pos_z(0) {}
    LocationSymbol( const string& c, const string& a,
		    const string& b, const string& l, const string& r,
		    double x, double y, double z ) :
		    city(c), area(a), building(b),
		    level(l), room(r),
		    pos_x(x), pos_y(y), pos_z(z) {}
protected:
private:
};    

struct LocationCoordinate
{
public:
    double pos_lon;
    double pos_lat;
    double pos_elev;
    LocationCoordinate& PosLon( double l  ) { pos_lon = l; return *this; }
    LocationCoordinate& PosLat( double l  ) { pos_lat = l; return *this; }
    LocationCoordinate& PosElev( double e ) { pos_elev= e; return *this; }

    LocationCoordinate() : pos_lon(0.0), pos_lat(0.0), pos_elev(0.0) { }
protected:
private:
};

struct LocationArea
{
public:
    math::Polygon2D<double> poly;
    LocationArea( const math::Polygon2D<double>& p ) : poly(p) { }
};

typedef math::Point2D<double> double_point; // Needed to work with the remote stuff

/**
 * This class represents a Basic Object for a Position. Classes that need to
 * store a bit more information (like WLocation) derive from this class.
 */
class Location : public db::dbstorable
{
public:    
   /**
    * Standard empty Constructor. You will have to fill everything in by your
    * own.
    */
    Location();

   /**
    * Constructor for all main information a GPS would give. 
    * If the when argument is 0 (default) then the current time is inserted
    */
    Location( double lon, double lat, double elev, double heading, double spd, double pos_rms, const timeval* when = NULL );

   /**
    * This constructs a location with all the necessary information that a
    * human would give to identify its position within a building.
    */
    Location( const string& _city, const string& _area, const string& _building, const string& _level, const string& _room, const string& _comment, double rms, const timeval* when = NULL );
    
    Location( const LocationSymbol& ls );
    Location( const LocationCoordinate& lc );
    Location( const LocationArea& la );
   /**
    * Virtual destructor since we will most probably work with derived objects
    */
    virtual ~Location();

   /**
    * The City of position
    */
    string city;

    string area;
   /**
    * The Building were we are, either an identifiable string, or the adress.
    */
    string building;
   /**
    * The Level in the Building
    */
    string level;
   /**
    * Room identifier for this location.
    */
    string room;
   /**
    * Comment to this special location.
    * E.g. "At the Table" "in front of" etc.
    */
    string comment;

   /*@{*/
   /**
    * Position in a Building/Room/Sensor specific coordinate System
    */
    double pos_x;
    double pos_y;
    double pos_z;
   /*@}*/
    
   /*@{*/
   /**
    * World Coordinates of the Position
    */
    double pos_lon;
    double pos_lat;

    /**
     * This might be the area that this position information covers. It has a
     * size() thats 0 when it doesnt contain any usefull area information.
     *
     * @note To have a resolution of smaller than a few meteres we need to use
     * doubles for all coordinate stuff we do.
     */
    math::Polygon2D<double> poly;

    /**
     * This is a rectangular box around the polygon, the .first of the pair
     * <b>must</b> be the lower left corner, while the .second of the pair
     * <b>must</b> be the upper right corner.
     */
    pair<math::Point2D<double>,math::Point2D<double> > polybox;

   /**
    * Height in meters
    */
    float pos_elev;
   /*@}*/
   /**
    * Direction of Movement. 
    * @note This information is in Radians, rather than degrees to avoid
    * unnecessary conversions during calculations.
    * So the value you get is from the interval \f$ ] -\pi, \pi [ \f$, while any
    * other value means that the current heading is not determinable, e.g.
    * because there is no movement when measuring with GPS, or because a Module
    * like WLAN cannot determine your actual heading.
    */
    float heading;

   /**
    * This is the current speed determined at this location in meters per
    * second
    */
    float speed;

   /**
    * RMS (Root Mean Squar Error) value of the GPS Position. 1 Means that this Position is unusable,
    * while 0 means that the Generator was absolutely sure that this is the
    * Correct Position.
    */
    float pos_rms;
   /**
    * The RMS value of the Room/Building Information. @see pos_rms
    */
    float room_rms;
    /**
     * The RMS value of the Heading information
     */
    float head_rms;

    /**
     * The RMS value for the speed
     */
    float speed_rms;
   /**
    * Seconds since Epoch when this Information was created
    */
    timeval created;

    double distance_to( const Location& ) const;
   /**
    * Primitive to store this object into a database table. The table must have
    * the same (or at least aliased) fields that this object has, plus the ID
    * Field it has inherited from IWdbstorable @see IWdbstorable for details
    * @param tablename The Name of the Table to store the Object in
    * @param dbref The referenc to the Database Connection which should be used
    * to store
    * @warning This may Throw an exception in case of failure. Any transaction
    * mechanism must be done outside, for performance and reliability reasons
    */
    virtual bool SaveToDatabase( const string& tablename, db::db& dbref );

   /**
    * Primitive to Load an Object from the Database. 
    * @param ID The ID that was used to store the object previously
    * @param tablename is the Table where the object is stored in
    * @param dbref The database where we want to load the stuff from
    * @note This can also be used to update the information from the database
    */
    virtual bool LoadFromDatabase( uint32_t ID, const string& tablename, db::db& dbref );

   /**
    * Update the Object to the Database. This assumes the Object was previously
    * saved to the Database and has a Valid ID assigned. 
    * If unsure you should check with get_ID() != 0
    */
    virtual bool UpdateToDatabase( const string& tablename, db::db& dbref );

    bool operator==( const Location& );
    friend ostream& operator<<( ostream& o, const Location& l );
protected:
    double calc_sphere_dist (double longi, double lati, double current_lat, double current_long) const;
    double calc_earth_radius (double lat) const;
};

ostream& operator<<( ostream& o, const Location& l );

// Inline implementation, since its much faster
inline Location::Location( )
    :city("Bremen"),
    building("MZH"),
    level("?"),
    room("???"),
    comment("unknown location"),
    pos_x(0.0),
    pos_y(0.0),
    pos_z(0.0),
    pos_lon(0.0),
    pos_lat(0.0),
    pos_elev(0.0),
    heading(0.0),
    speed(0.0),
    pos_rms(0.0),
    room_rms(0.0)
{
    gettimeofday(&created,NULL);
}

inline Location::Location( const string& _city, const string& _area, const string& _building, const string& _level, const string& _room, const string& _comment, double rms, const timeval* when )
    :city(_city),
    area(_area),
    building(_building),
    level(_level),
    room(_room),
    comment(_comment),
    pos_x(0.0),
    pos_y(0.0),
    pos_z(0.0),
    pos_lon(0.0),
    pos_lat(0.0),
    pos_elev(0.0),
    heading(0.0),
    speed(0.0),
    pos_rms(0.0),
    room_rms(rms)
{    
    if ( when == NULL )
    {
	gettimeofday(&created,NULL);
    }
    else
    {
	created = *when;
    }

}

inline Location::Location( double lon, double lat, double elev, double _heading, double spd, double rms, const timeval* when )
    :city("Bremen"),
    building("MZH"),
    level("?"),
    room("???"),
    comment("unknown location"),
    pos_x(0.0),
    pos_y(0.0),
    pos_z(0.0),
    pos_lon(lon),
    pos_lat(lat),
    pos_elev(elev),
    heading(_heading),
    speed(spd),
    pos_rms(rms),
    room_rms(0.0)
{
    if ( when == NULL )
    {
	gettimeofday(&created,NULL);
    }
    else
    {
	created = *when;
    }
}

inline Location::Location( const LocationSymbol& ls )
    : city(ls.city),
    area(ls.area),
    building(ls.building),
    level(ls.level),
    room(ls.room),
    pos_x(ls.pos_x),
    pos_y(ls.pos_y),
    pos_z(ls.pos_z),



    pos_lon(0.0),
    pos_lat(0.0),
    pos_elev(0.0),
    heading(0.0),
    speed(0.0),
    pos_rms(0.0),
    room_rms(0.0)

{
}

inline Location::Location( const LocationCoordinate& lc )
    : 
    pos_x(0.0),
    pos_y(0.0),
    pos_z(0.0),
    pos_lon(lc.pos_lon),
    pos_lat(lc.pos_lat),
    pos_elev(lc.pos_elev),
    heading(0.0),
    speed(0.0),
    pos_rms(0.0),
    room_rms(1.0),
    head_rms(1.0),
    speed_rms(1.0)
{
    
}

inline Location::Location( const LocationArea& la )
    :city("Bremen"),
    building("MZH"),
    level("?"),
    room("???"),
    comment("unknown location"),
    pos_x(0.0),
    pos_y(0.0),
    pos_z(0.0),
    pos_lon(0.0),
    pos_lat(0.0),
    poly(la.poly),
    pos_elev(0.0),
    heading(0.0),
    speed(0.0),
    pos_rms(0.0),
    room_rms(0.0)
{
    polybox = poly.box();
}

} //namespace location
} //namespace
} //namespace iwear
#endif
