/*
 open source routing machine
 Copyright (C) Dennis Luxen, others 2010

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU AFFERO General Public License as published by
 the Free Software Foundation; either version 3 of the License, or
 any later version.

 This program 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 Affero General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 or see http://www.gnu.org/licenses/agpl.txt.
 */

#include "DescriptionFactory.h"

DescriptionFactory::DescriptionFactory() : entireLength(0) { }

DescriptionFactory::~DescriptionFactory() { }

inline double DescriptionFactory::DegreeToRadian(const double degree) const {
        return degree * (M_PI/180);
}

inline double DescriptionFactory::RadianToDegree(const double radian) const {
        return radian * (180/M_PI);
}

double DescriptionFactory::GetBearing(const _Coordinate& A, const _Coordinate& B) const {
    double deltaLong = DegreeToRadian(B.lon/100000. - A.lon/100000.);

    double lat1 = DegreeToRadian(A.lat/100000.);
    double lat2 = DegreeToRadian(B.lat/100000.);

    double y = sin(deltaLong) * cos(lat2);
    double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(deltaLong);
    double result = RadianToDegree(atan2(y, x));
    while(result <= 0.)
        result += 360.;
    while(result >= 360.)
        result -= 360.;

    return result;
}

void DescriptionFactory::SetStartSegment(const PhantomNode & _startPhantom) {
    startPhantom = _startPhantom;
    AppendSegment(_startPhantom.location, _PathData(0, _startPhantom.nodeBasedEdgeNameID, 10, _startPhantom.weight1));
}

void DescriptionFactory::SetEndSegment(const PhantomNode & _targetPhantom) {
    targetPhantom = _targetPhantom;
    pathDescription.push_back(SegmentInformation(_targetPhantom.location, _targetPhantom.nodeBasedEdgeNameID, 0, _targetPhantom.weight1, 0, true) );
}

void DescriptionFactory::AppendSegment(const _Coordinate & coordinate, const _PathData & data ) {
    if(1 == pathDescription.size() && pathDescription.back().location == coordinate) {
        pathDescription.back().nameID = data.nameID;
    } else {
        pathDescription.push_back(SegmentInformation(coordinate, data.nameID, 0, data.durationOfSegment, data.turnInstruction) );
    }
}

void DescriptionFactory::AppendEncodedPolylineString(std::string & output, bool isEncoded) {
    if(isEncoded)
        pc.printEncodedString(pathDescription, output);
    else
        pc.printUnencodedString(pathDescription, output);
}

void DescriptionFactory::AppendEncodedPolylineString(std::string &output) {
    pc.printEncodedString(pathDescription, output);
}

void DescriptionFactory::AppendUnencodedPolylineString(std::string &output) {
    pc.printUnencodedString(pathDescription, output);
}

void DescriptionFactory::Run(const SearchEngineT &sEngine, const unsigned zoomLevel) {

    if(0 == pathDescription.size())
        return;

//    unsigned entireLength = 0;
    /** starts at index 1 */
    pathDescription[0].length = 0;
    for(unsigned i = 1; i < pathDescription.size(); ++i) {
        pathDescription[i].length = ApproximateDistanceByEuclid(pathDescription[i-1].location, pathDescription[i].location);
    }

    double lengthOfSegment = 0;
    unsigned durationOfSegment = 0;
    unsigned indexOfSegmentBegin = 0;

    std::string string0 = sEngine.GetEscapedNameForNameID(pathDescription[0].nameID);
    std::string string1;


    /*Simplify turn instructions
    Input :
    10. Turn left on B 36 for 20 km
    11. Continue on B 35; B 36 for 2 km
    12. Continue on B 36 for 13 km

    becomes:
    10. Turn left on B 36 for 35 km
    */
//TODO: rework to check only end and start of string.
//		stl string is way to expensive

//    unsigned lastTurn = 0;
//    for(unsigned i = 1; i < pathDescription.size(); ++i) {
//        string1 = sEngine.GetEscapedNameForNameID(pathDescription[i].nameID);
//        if(TurnInstructionsClass::GoStraight == pathDescription[i].turnInstruction) {
//            if(std::string::npos != string0.find(string1+";")
//            		|| std::string::npos != string0.find(";"+string1)
//            		|| std::string::npos != string0.find(string1+" ;")
//                    || std::string::npos != string0.find("; "+string1)
//                    ){
//                INFO("->next correct: " << string0 << " contains " << string1);
//                for(; lastTurn != i; ++lastTurn)
//                    pathDescription[lastTurn].nameID = pathDescription[i].nameID;
//                pathDescription[i].turnInstruction = TurnInstructionsClass::NoTurn;
//            } else if(std::string::npos != string1.find(string0+";")
//            		|| std::string::npos != string1.find(";"+string0)
//                    || std::string::npos != string1.find(string0+" ;")
//                    || std::string::npos != string1.find("; "+string0)
//                    ){
//                INFO("->prev correct: " << string1 << " contains " << string0);
//                pathDescription[i].nameID = pathDescription[i-1].nameID;
//                pathDescription[i].turnInstruction = TurnInstructionsClass::NoTurn;
//            }
//        }
//        if (TurnInstructionsClass::NoTurn != pathDescription[i].turnInstruction) {
//            lastTurn = i;
//        }
//        string0 = string1;
//    }


    for(unsigned i = 1; i < pathDescription.size(); ++i) {
        entireLength += pathDescription[i].length;
        lengthOfSegment += pathDescription[i].length;
        durationOfSegment += pathDescription[i].duration;
        pathDescription[indexOfSegmentBegin].length = lengthOfSegment;
        pathDescription[indexOfSegmentBegin].duration = durationOfSegment;


        if(TurnInstructionsClass::NoTurn != pathDescription[i].turnInstruction) {
            //INFO("Turn after " << lengthOfSegment << "m into way with name id " << segment.nameID);
            assert(pathDescription[i].necessary);
            lengthOfSegment = 0;
            durationOfSegment = 0;
            indexOfSegmentBegin = i;
        }
    }
    //    INFO("#segs: " << pathDescription.size());

    //Post-processing to remove empty or nearly empty path segments
    if(FLT_EPSILON > pathDescription.back().length) {
        //        INFO("#segs: " << pathDescription.size() << ", last ratio: " << targetPhantom.ratio << ", length: " << pathDescription.back().length);
        if(pathDescription.size() > 2){
            pathDescription.pop_back();
            pathDescription.back().necessary = true;
            pathDescription.back().turnInstruction = TurnInstructions.NoTurn;
            targetPhantom.nodeBasedEdgeNameID = (pathDescription.end()-2)->nameID;
            //            INFO("Deleting last turn instruction");
        }
    } else {
        pathDescription[indexOfSegmentBegin].duration *= (1.-targetPhantom.ratio);
    }
    if(FLT_EPSILON > pathDescription[0].length) {
        //TODO: this is never called actually?
        if(pathDescription.size() > 2) {
            pathDescription.erase(pathDescription.begin());
            pathDescription[0].turnInstruction = TurnInstructions.HeadOn;
            pathDescription[0].necessary = true;
            startPhantom.nodeBasedEdgeNameID = pathDescription[0].nameID;
            //            INFO("Deleting first turn instruction, ratio: " << startPhantom.ratio << ", length: " << pathDescription[0].length);
        }
    } else {
        pathDescription[0].duration *= startPhantom.ratio;
    }

    //Generalize poly line
    dp.Run(pathDescription, zoomLevel);

    //fix what needs to be fixed else
    for(unsigned i = 0; i < pathDescription.size()-1 && pathDescription.size() >= 2; ++i){
        if(pathDescription[i].necessary) {
            double angle = GetBearing(pathDescription[i].location, pathDescription[i+1].location);
            pathDescription[i].bearing = angle;
        }
    }

//    BuildRouteSummary(entireLength, duration);
    return;
}

void DescriptionFactory::BuildRouteSummary(const double distance, const unsigned time) {
    summary.startName = startPhantom.nodeBasedEdgeNameID;
    summary.destName = targetPhantom.nodeBasedEdgeNameID;
    summary.BuildDurationAndLengthStrings(distance, time);
}