//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2015, University of Osnabrueck                                                                         //
//   All rights reserved.                                                                                               //
//                                                                                                                      //
//   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the   //
//   following conditions are met:                                                                                      //
//                                                                                                                      //
//   1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following//
//       disclaimer.                                                                                                    //
//                                                                                                                      //
//   2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the       //
//       following disclaimer in the documentation and/or other materials provided with the distribution.               //
//                                                                                                                      //
//   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, //
//   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE  //
//   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, //
//   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR    //
//   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  //
//   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE   //
//   USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                                           //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <ctime>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <fstream>

#include "Launcher.hpp"
#include "Globals.hpp"
#include "easylogging++.h"
#include "ExtractEdges.hpp"
#include "GeometryHelper.hpp"

_INITIALIZE_EASYLOGGINGPP

using namespace launcher;

int main (int argc, char* argv[]) {
    
    Vector<double> transmitter;
    char buildingsPolygons[BUFFLEN];
    char terrainPolygons[BUFFLEN];
    char buildingsEdges[BUFFLEN];
    char terrainEdges[BUFFLEN];
    char rayfolder[BUFFLEN];
    char ident[BUFFLEN];
    char typeString[BUFFLEN];
    char transmitterFile[BUFFLEN];
    char receiverFile[BUFFLEN];
    
    srand(42);
    double rxmin = 0, rxmax = 0, rymin = 0, rymax = 0, rzmin = 0, rzmax = 0;
    double lx    = 0, ly    = 0, lz    = 0;
    double stepwidth = 0;
    double sx = 0, sy = 0, sz = 0;
    int type = 0;
    std::vector<Vector<double> > receivers;
    //parse commandline arguments
    if (argc == 29) {

        //files for input and output data
        strncpy(buildingsPolygons,argv[1],BUFFLEN);
        strncpy(terrainPolygons,argv[2],BUFFLEN);
        strncpy(buildingsEdges,argv[3],BUFFLEN);
        strncpy(terrainEdges,argv[4],BUFFLEN);
        strncpy(rayfolder,argv[5],BUFFLEN);
        strncpy(typeString, argv[6], BUFFLEN);
        strncpy(receiverFile, argv[7], BUFFLEN);
        

        //placement of receivers
        /*
        rxmin = atof(argv[6]);
        rxmax = atof(argv[7]);
        rymin = atof(argv[8]);
        rymax = atof(argv[9]);
        rzmin = atof(argv[10]);
        rzmax = atof(argv[11]);
        stepwidth = atof(argv[12]);
         */

        //get simulation type and coordinates
        int e = parseType(typeString, rxmin, rymin, rzmin, rxmax, rymax, rzmax, sx, sy, sz, type);
        if(e != 0){
            exit(EXIT_FAILURE);
        }
        
        // Store receivers if Point or List
        if(type == POINT){
            receivers.push_back(Vector<double>(rxmin, rymin, rzmin));
        }
        
        if(type == LIST){
            receivers = parseReceiverListFile(receiverFile);
        }
        
        if(type == LINE){
            receivers = parseReceiverLineFile(receiverFile);
        }

        //transmitter position
        double x = atof(argv[13]);
        double y = atof(argv[14]);
        double z = atof(argv[15]);
        transmitter = Vector<double>(x,y,z);
        
        

        //parameters
        RAY_NUM = atoi(argv[16]);					//number of launched rays at the beginning
        MAX_ITERATIONS = atoi(argv[17]);			//maximum number of iteration = maximum number of events ray (useful to abort "ping-pong"-effect of a ray from one wall to an other)
        RECEIVE_THRESHOLD = atof(argv[18]);			//"size" of the atenna
        DIFFRACTION_THRESHOLD = atof(argv[19]);		//area around edge, in which diffraction events happen (suggested value: 7*lambda)
        REFLECTION_PART = atof(argv[20]);			//when a ray hits the wall this part of the energy (0..1) will contribute to the reflected ray
        WAVELENGTH = atof(argv[21]);				//wavelength is just required for interference
        INTERFERENCE = (atoi(argv[22])==1);			//let rays interfere instead of just adding the energies (= realistic small scale fading effects)
        MAX_RANGE = (atof(argv[23]));
        //RAY_NUM_DIFFRACTION = atoi(argv[23]);		//number of rays launched in diffraction events
        //RAY_NUM_SCATTERING = atof(argv[24]);		//number of rays launched in scattering events
        SAVERAYS = (atoi(argv[24])==1);
        DEBUG = (atoi(argv[25])>4);				//debugging information on numbers of rays and construction of the rayfile for the 3d viewer
        DEAD_DISTANCE = atof(argv[26]);				//after an event no new events happen for this long
        SCATTERING_PART = atof(argv[27]);			//this part of the energy (0..1) will be scattered when a ray hits a wall
        TIMEOUT = atof(argv[28]);					//calculation will be aborted after this time (useful to guarantee execution in a given time, this results in a calculation of less iterations than specified by  MAX_ITERATIONS)
        //strncpy(ident,argv[29],BUFFLEN);
        
    } else {
        std::cout << "wrong number of arguments, requires exactly 28" << std::endl;
        exit(42);
    }
    
    std::string identifier = getIdentifier(buildingsPolygons);
    easyloggingpp::Configurations conf;
    conf.setAll(easyloggingpp::ConfigurationType::ToFile, "true");
    conf.setAll(easyloggingpp::ConfigurationType::ToStandardOutput, "false");
    conf.setAll(easyloggingpp::ConfigurationType::Filename, "tmp/"+identifier+"/logs/"+identifier+"_launcher.log");
    conf.setAll(easyloggingpp::ConfigurationType::Format, "%datetime %level " + transmitter.toString() + " %log");
    if(!DEBUG) conf.set(easyloggingpp::Level::Debug, easyloggingpp::ConfigurationType::Enabled, "false");
    conf.set(easyloggingpp::Level::Warning, easyloggingpp::ConfigurationType::ToStandardOutput, "true");
    conf.set(easyloggingpp::Level::Warning, easyloggingpp::ConfigurationType::Format, "%datetime %level " + transmitter.toString() + " %log [%func]");
    easyloggingpp::Loggers::reconfigureLogger("default", conf);
    easyloggingpp::Loggers::reconfigureAllLoggers(conf);
    

    LOG(DEBUG) << buildingsPolygons;
    LOG(DEBUG)<< buildingsEdges;
    LOG(DEBUG) << terrainPolygons;
    LOG(DEBUG) << terrainEdges;
    LOG(DEBUG) << getWorkFolder(buildingsPolygons);
    LOG(DEBUG) << "parseTypeResult: " << type << " " << rxmin << " " << rymin << " " << rzmin << " " << rxmax << " " << rymax << " " << rzmax << " " << lx << " " << ly << " " << lz;
    LOG(DEBUG) << "Setpsizes: " << sx << " " << sy << " " << sz;
    
    std::time_t starttime, currenttime;
    double runtime;
    std::time(&starttime);

    //launch intial rays
    std::vector<Ray> rays;
    launch_rays(&rays, transmitter, 1.0, false, Vector<double>(0,0,0), false, Vector<double>(0,0,0),0,0,-1,RAY_NUM);
    bool done = true;

    //load polygons and edges
    std::vector<std::vector<double> > polygons =parseFile(buildingsPolygons);
    std::vector<std::vector<double> > polygons2 =parseFile(terrainPolygons);
    for (int i=0; i < polygons2.size(); i++) {
        polygons.push_back(polygons2[i]);
    }
    
    std::vector<std::vector<double> > edges, edges2;
    edges  = parseFile(buildingsEdges);
    edges2 = parseFile(terrainEdges);   
    
    for (int i=0; i < edges2.size(); i++) {
        edges.push_back(edges2[i]);
    }

    int iteration=0;
    for (iteration=0; iteration < MAX_ITERATIONS; iteration++) {
        done = true;
        
        LOG(DEBUG) << "iteration:" << iteration << "  #rays:" << rays.size();
        
        std::vector<Ray> newrays;
        for (int rayi = 0; rayi < rays.size(); rayi++) {
            if (rayi > 1000000000) exit(-42);
            std::time(&currenttime);
            if (difftime(currenttime, starttime) > TIMEOUT) {
                    break;
            }
            Ray *ray = &rays[rayi];
            if (ray->status == ACTIVE) {
                done = false;
                int eventtype = NOTHING;
                double eventdistance = 1000000;
                Vector<double> eventnormal;
                Vector<double> eventnormal2;
                Vector<double> eventconnection;
                Vector<double> eventedge;
                double eventslitwidth;

                // second check: ray reflected?
                for (int polyi=0; polyi<polygons.size(); polyi++) {

                    std::vector<double> poly = polygons[polyi];
                    Vector<double> location;
                    Vector<double> normal;
                    int tmp = getIntersection(ray->origin, ray->direction, poly, location, normal);
                    if (tmp > 0) {

                        double dst = (ray->origin - location).length();

                        if (normal * ray->direction > 0) { //avoid indoor reflections
                        //	normal*=-1; //make sure, normal points in right direction
                        //}
                            if (dst < eventdistance /*and dst > DEAD_DISTANCE*/) {

                                eventtype = REFLECTION;
                                eventdistance = dst;
                                eventnormal = normal;
                            }
                        }
                    }
                }

                // third check: ray diffracted?
                for (int edgei=0; edgei<edges.size(); edgei++) {
                    std::vector<double> edge = edges[edgei];
                    checkDiffraction(*ray,eventtype,eventdistance,eventconnection,eventedge,eventnormal, eventnormal2,eventslitwidth,edge,WAVELENGTH,DIFFRACTION_THRESHOLD, DEAD_DISTANCE);

                }




                switch (eventtype) {

                    case REFLECTION:
                        if (SCATTERING_PART > 0 || REFLECTION_PART > 0) {
                            double r = ((double)rand())/((double)RAND_MAX);

                            //the ray is always either reflected or diffracted in order to avoid a reduction of the sample size
                            //absorption & transmission loss is instead modeled by means of an energy reduction
                            double srthreshold = SCATTERING_PART / (SCATTERING_PART + REFLECTION_PART);
                            double energy = (REFLECTION_PART + SCATTERING_PART) * ray->energy;
                            if (r < srthreshold) {
                                doScattering(	&newrays,
                                                ray->origin + ray->direction * eventdistance,
                                                energy,
                                                eventnormal,
                                                ray->totalDistance + eventdistance,
                                                ray->numReflections + 1,
                                                rayi);
                            } else {
                                Vector<double> newdirection = ray->direction - (eventnormal * 2 * (ray->direction * eventnormal));
                                newdirection /= newdirection.length();
                                Vector<double> neworigin = ray->origin + ray->direction * eventdistance;
                                Ray newray(	neworigin[0],neworigin[1],neworigin[2],
                                                newdirection[0],newdirection[1],newdirection[2],
                                                energy,
                                                ACTIVE,
                                                ray->totalDistance + eventdistance,
                                                ray->numReflections + 1,
                                                rayi);
                                newrays.push_back(newray);
                            }
                        }
                        ray->status = SUCCEEDED;
                        ray->direction = ray->direction * eventdistance;
                    break;

                    case DIFFRACTION: 
                        doDiffraction(eventconnection, eventedge, eventslitwidth, eventdistance, eventnormal, eventnormal2, *ray, RAY_NUM_DIFFRACTION, newrays, rayi);
                        //newrays[newrays.size()-1].print();
                        ray->status = SUCCEEDED;
                        ray->direction = ray->direction * eventdistance;
                    break;

                    case NOTHING :
                        ray->status = ABORTED;
                }
            }
        }
        for (int rayi = 0; rayi < newrays.size(); rayi++) {
            if (rayi > 1000000000) exit(-42);
            Ray newray = newrays[rayi];
            rays.push_back(newray);
        }
        if (done) break;
    }
    std::time(&currenttime);
    runtime = std::difftime(currenttime,starttime);

    LOG(DEBUG) << "Calculating signal strength";
    
    std::stringstream res;
    res << std::scientific;
    //res << typeString << std::endl;
   
    
    //do for every receiver:
    if( type == POINT || type == LIST || type == LINE){        
        for(int ri = 0; ri < receivers.size(); ri++){
            Vector<double> point = receivers[ri];
            double energy = getEnergy(point, rays, transmitter);
            res << energy << " ";
        }
        res << std::endl;
    }
    
    else if( type == AREA ){
        double rz = rzmin;
        for (double ry = rymin; ry <= rymax; ry+=sy) {
            for (double rx = rxmin; rx <= rxmax; rx+=sx) {
                double energy = getEnergy(Vector<double>(rx,ry,rz), rays, transmitter);
                res << energy << " ";
            }
            res << std::endl;
        }
    }
    
    else if( type == CUBIG){
        for (double rz = rzmin; rz <= rzmax; rz+=sz) {
            for (double ry = rymin; ry <= rymax; ry+=sy) {
                for (double rx = rxmin; rx <= rxmax; rx+=sx) {
                    double energy = getEnergy(Vector<double>(rx,ry,rz), rays, transmitter);
                    res << energy << " ";
                    
                }
                res << std::endl;
            }
        }
    }
    
    std::ofstream myfile;
    std::stringstream coveragename;
    coveragename << getWorkFolder(buildingsPolygons) << "coverage/" << transmitter << ".txt";
    LOG(DEBUG) << "coveragename" << coveragename.str() << "\n" << transmitter;
    myfile.open (coveragename.str(), std::ios::out | std::ios::trunc);
    myfile << res.str();
    myfile.close();


    LOG(DEBUG) << "runtime (just ray launching): " << runtime;
    
    std::time(&currenttime);
    runtime = std::difftime(currenttime,starttime);
    
    LOG(INFO) << "total runtime: " << runtime;
        //LOG(DEBUG) << "link strength: " << 10.0*log10(energy) << "dB";



    /*
    if (!INTERFERENCE) {
        LOG(INFO) << "iterations: " << iteration;
        LOG(INFO) << "received energy: " << energy;
    } else {
        double energy_interfere = sqrt(received_field_im * received_field_im + received_field_re * received_field_re); 
        LOG(INFO) << "received energy: " << energy_interfere;
    } */
    if(SAVERAYS) {
        save_rays(rays, rayfolder, transmitter);
    }

    return 0;
}

