#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <string>
#include <cstring>
#include <fmi2Functions.h>
#include <fmi2FunctionTypes.h>
#include <iostream>
#include <jni.h>
#include <fstream> 
#include <ctime>
using namespace std;




int main(int argc, char **argv) {


    /** Start of monitor instantiate **/
    
   /* JavaVM *jvm;       // denotes a Java VM 
    JNIEnv *env;      
    JavaVMInitArgs vm_args; // JDK/JRE VM initialization arguments
    JavaVMOption* options = new JavaVMOption[1];
    options[0].optionString = "-Djava.class.path=/usr/lib/java:/home/siemens/dejavu/dir/dejavu.jar:/usr/share/scala-2.11/lib/scala-library.jar :/home/siemens/dejavu/out/examples/hspeedlimit/TraceMonitor.jar";
    vm_args.version = JNI_VERSION_1_6;
    vm_args.nOptions = 1;
    vm_args.options = options;
    vm_args.ignoreUnrecognized = false;*/

    /* load and initialize a Java VM, return a JNI interface
    * pointer in env */
    //JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);

    // First get the class that contains the method you need to call
    //jclass tracemonitor_class = env->FindClass("TraceMonitor");

    // Get the method that you want to call
    //jmethodID main_method = env->GetStaticMethodID(tracemonitor_class, "eval", "(Ljava/lang/String;)Z");
    
    /** End of monitor instantiate **/

    int ret = EXIT_SUCCESS;
    fmi2Real tstart = 0.0;
    fmi2Real tcur = tstart;
    fmi2Real hstep = 0.1;
    fmi2Real tend = 8.0;
    void *handle_osom, *handle_c;
  
    fmi2InstantiateTYPE* (fmi2InstantiateDPController);
    fmi2EnterInitializationModeTYPE* (fmi2EnterInitializationModeDPController);
    fmi2ExitInitializationModeTYPE* (fmi2ExitInitializationModeDPController);
    fmi2SetRealTYPE* (fmi2SetRealDPController);
    fmi2GetRealTYPE* (fmi2GetRealDPController);
    fmi2DoStepTYPE* (fmi2DoStepDPController);
   

    fmi2InstantiateTYPE* (fmi2InstantiateOSOM);
    fmi2EnterInitializationModeTYPE* (fmi2EnterInitializationModeOSOM);
    fmi2ExitInitializationModeTYPE* (fmi2ExitInitializationModeOSOM);
    fmi2SetRealTYPE* (fmi2SetRealOSOM);
    fmi2GetRealTYPE* (fmi2GetRealOSOM);
    fmi2DoStepTYPE* (fmi2DoStepOSOM);
    

    char *error;
    handle_c = dlopen ("DPController/binaries/linux64/DPController.so", RTLD_LAZY);
    if (!handle_c) {
        fprintf (stderr, "%s\n", dlerror());
        exit(1);
    }
    dlerror();    /* Clear any existing error */
   
    handle_osom = dlopen ("OSOM/binaries/linux64/OSOM.so", RTLD_LAZY);
    if (!handle_osom) {
        fprintf (stderr, "%s\n", dlerror());
        exit(1);
    }
    dlerror();    /* Clear any existing error */
    
    
    fmi2InstantiateDPController = (fmi2InstantiateTYPE*) dlsym(handle_c, "DPController_fmi2Instantiate");
    fmi2EnterInitializationModeDPController = (fmi2EnterInitializationModeTYPE*) dlsym(handle_c, "DPController_fmi2EnterInitializationMode");
    fmi2ExitInitializationModeDPController = (fmi2ExitInitializationModeTYPE*) dlsym(handle_c, "DPController_fmi2ExitInitializationMode");
    fmi2SetRealDPController = (fmi2SetRealTYPE*) dlsym(handle_c, "DPController_fmi2SetReal");
    fmi2GetRealDPController = (fmi2GetRealTYPE*) dlsym(handle_c, "DPController_fmi2GetReal");
    fmi2DoStepDPController = (fmi2DoStepTYPE*) dlsym(handle_c, "DPController_fmi2DoStep");


    fmi2InstantiateOSOM = (fmi2InstantiateTYPE*) dlsym(handle_osom, "OSOM_fmiInstantiateSlave");
    fmi2EnterInitializationModeOSOM = (fmi2EnterInitializationModeTYPE*) dlsym(handle_osom, "OSOM_fmiEnterInitializationMode");
    fmi2ExitInitializationModeOSOM = (fmi2ExitInitializationModeTYPE*) dlsym(handle_osom, "OSOM_fmiExitInitializationMode");
    fmi2SetRealOSOM = (fmi2SetRealTYPE*) dlsym(handle_osom, "OSOM_fmiSetReal");
    fmi2GetRealOSOM = (fmi2GetRealTYPE*) dlsym(handle_osom, "OSOM_fmiGetReal");
    fmi2DoStepOSOM = (fmi2DoStepTYPE*) dlsym(handle_osom, "OSOM_fmiDoStep");
    

    if ((error = dlerror()) != NULL)  {
        fprintf (stderr, "%s\n", error);
        exit(1);
    }
    
    cout<<"CALL fmi2Instantiate...\n";
    
   // fmi2Component c_controller = (*fmi2InstantiateDPController)("OSOM", fmi2CoSimulation , "5b6d9100-b7c6-471d-ae80-c7fac83cf9f8", NULL, NULL, true, true);
    
    fmi2Component c_osom = (*fmi2InstantiateOSOM)("OSOM", fmi2CoSimulation , "{5b6d9100-b7c6-471d-ae80-c7fac83cf9f8}", "OSOM/resources", NULL, true, true);

    
   
    
    /*const unsigned int vr_controller[] = {100,101,102,103,104,105,106,107,108};
    const unsigned int vr_plant[] = {109,110,111,112};

    fmi2Real values_controller[] = {1,0,0,0,0,100,0,0,0};
    fmi2Real values_plant[] = {0,0,98,0};
    
    cout<<"CALL fmi2SetReal of controller...\n";
    (*fmi2SetRealController)(c_controller, vr_controller, 9, values_controller);
    
    cout<<"CALL fmi2SetReal of plant...\n";
    (*fmi2SetRealPlant)(c_plant, vr_plant, 4, values_plant);

    cout<<"CALL fmi2EnterInitializationMode...\n";
    (*fmi2EnterInitializationModeController)(c_controller);
    (*fmi2EnterInitializationModePlant)(c_plant);

    cout<<"CALL fmi2ExitInitializationMode...\n";
    (*fmi2ExitInitializationModeController)(c_controller);
    (*fmi2ExitInitializationModePlant)(c_plant);
    
    tcur = tstart;
    fmi2Real result_buffer = 0;

    std::ofstream cent_times ("centralized_times.txt");
    std::ofstream cent_trace ("centralized_trace.txt");
    std::ofstream data ("centralized_data.txt");

    std::clock_t start_while = std::clock();*/


    while(tcur<tend){
        /*std::clock_t start_iteration = std::clock();
        fmi2Boolean newStep = fmi2True;
        fmi2Real val_controller[9];
        fmi2Real val_plant[4];

        cout<<"Current time step: "<<tcur<<'\n';
         
        //cout<<"CALL fmi2DoStep...\n";
        fmi2DoStepController(c_controller, tcur,hstep, newStep);

        //cout<<"CALL fmi2GetReal...\n";
        fmi2GetRealController(c_controller, vr_controller, 9, val_controller);
      //  cout<<"Speed:"<<val_controller[5]<<endl;
        cout<<"Speed Limit:"<<val_controller[6]<<endl;*/

        /* Synchronization between two fmus - send plant's speed to the controller
        one step after new traffic sign detection
         */

       /* if( val_controller[2] != result_buffer ) {

            fmi2Real val_plant_speed[1];
            const unsigned int vr_plant_speed[] = {111};
            const unsigned int vr_controller_speed[] = {105};
            cout<<"Send plant's speed to controller\n";
            result_buffer = val_controller[2];
            fmi2GetRealPlant(c_plant, vr_plant_speed, 1, val_plant_speed);
            fmi2SetRealController(c_controller, vr_controller_speed, 1, val_plant_speed);
        }*/

        /* Synchronization between two fmus - send deltaSpeed speed to the plant's throttle/break
        two steps after new traffic sign detection
         */
       /* else if ( val_controller[7] > 0) { 
            fmi2Real val_plant_speed[1];
            val_plant_speed[0] = val_controller[7];
            const unsigned int vr_plant_throttle[] = {110};
            fmi2SetRealPlant(c_plant, vr_plant_throttle, 1, val_plant_speed);
             
        }
        else if ( val_controller[7] < 0) {

            fmi2Real val_plant_speed[1];
            val_plant_speed[0] = val_controller[7];
            const unsigned int vr_plant_break[] = {109};
            fmi2SetRealPlant(c_plant, vr_plant_break, 1, val_plant_speed);
           
        }
       
        fmi2DoStepPlant(c_plant, tcur,hstep, newStep);
        fmi2GetRealPlant(c_plant, vr_plant, 4, val_plant);
        cout<<"Speed:"<<val_plant[2]<<endl;
        data<<val_controller[6]<<","<<val_plant[2]<<"\n";
        string nativeString;
*/
        /* Synthesize current event */
       
       /* std::clock_t start_synthesize = std::clock();
        nativeString+="speed";
        nativeString+=",";
        nativeString+=to_string((int) (val_controller[6] * 1000000));
        nativeString+=",";
        nativeString+=to_string((int) (val_plant[2] * 1000000));
        std::clock_t end_synthesize = std::clock();*/

        /* call monitor for evaluation */
        /*std::clock_t start_monitor = std::clock();
        env->CallStaticBooleanMethod(tracemonitor_class, main_method, env->NewStringUTF(nativeString.c_str()));
        std::clock_t end_monitor = std::clock();
        tcur += hstep;
        std::clock_t end_iteration = std::clock();

        cent_times<< (end_iteration - start_iteration) / (double)(CLOCKS_PER_SEC / 1000) << "," 
        << (end_synthesize - start_synthesize)/ (double)(CLOCKS_PER_SEC / 1000) << 
        "," << (end_monitor - start_monitor)/ (double)(CLOCKS_PER_SEC / 1000)<< '\n';*/
     }

   
   /* std::clock_t end_while = std::clock();
    cent_trace<<"Total simulation loop time:" << (end_while - start_while)/ (double)(CLOCKS_PER_SEC / 1000)<< 
        "," << "Number of iterations: " << tend/hstep << '\n';
    */
    cout<<"Simulation finished.\n";

    dlclose(handle_c);
    dlclose(handle_osom);
   
   /* cent_times.close();
    cent_trace.close();
    data.close();*/

    return ret;
}
