//---------------------------------------------------------------------------
//Controling Multiple Cameras
//NOTE: The CImg library will not compile with Borland or Embarcadero RAD Studio
//---------------------------------------------------------------------------
#include "CameraFunctions.h"
#include <atomic>
#include <vector>
#include <thread>
//---------------------------------------------------------------------------
#pragma comment( lib, "atmcd32m.lib" )

int main()
{

    unsigned int ui_error;
    long i_numCams;
    long CameraHandle;

    //--------------------------------------------------------------------------------------
    //--------------------------------------------------------------------------------------
    //Connection and setup procedure for the Andor iVac 316 camera 
    //--------------------------------------------------------------------------------------
    //--------------------------------------------------------------------------------------
    //Check if there are cameras availble 
    ui_error = GetAvailableCameras(&i_numCams);
    //Check if an error is thrown.
    //If it is, print that is was thrown at GetAvailbleCameras, if not continue the program.
    CheckError(ui_error, "GetAvailableCameras");
    if (i_numCams == 0) {
        printf("NO spectrometer camera found!\n\n");
        return 0;
    }

    //Get a Camera handle for 1 camera
    ui_error = GetCameraHandle(0, &CameraHandle);
    CheckError(ui_error, "GetCameraHandle");
    printf("Number of Camera Handles Acquired: %i \n\n", i_numCams);

    ui_error = SetCurrentCamera(CameraHandle);
    CheckError(ui_error, "SetCurrentCamera");

    char temp = ' ';
    ui_error = Initialize(&temp);
    CheckError(ui_error, "Initialize");

    //0 = Full vertical binning
    //1 = Single-Track
    //2 = Multi-Track
    //3 = Random-Track
    //4 = Image
    //5 = Cropped
    acqParams.i_readMode = 4;                           //Image
    //1 = Single scan
    //2 = Accumulate
    //3 = Kinetics
    //4 = Fast Kinetrics
    //5 = Run til Abort
    acqParams.i_acquisitionMode = 1;                    //Single Scan
    //Integration time for the next acquistion
    acqParams.f_exposureTime = 0.35f;                   //Float value int time in seconds

    //Use the SetUpCamera function to set up necessesary parameters for the camera. 
    //The acquisition parameters (acqParams) is defined in the Header file.
    SetUpCamera(CameraHandle, acqParams);
    
    //--------------------------------------------------------------------------------------
    //--------------------------------------------------------------------------------------
    // Read different values 
    //--------------------------------------------------------------------------------------
    //--------------------------------------------------------------------------------------
    //Read detector serial number
    int i_serial;
    GetCameraSerialNumber(&i_serial);
    printf("\nSerialNumber of Camera: %i", i_serial);

    //Get number of pixels on the x- and y-axis for the detector
    int xpixels, ypixels;
    GetDetector(&xpixels, &ypixels);
    printf("\nCamera detector with size of %i x %i pixels",xpixels,ypixels);

    //Use Andor Capibilities to test if camera has a shutter
    AndorCapabilities* caps = new AndorCapabilities();
    caps->ulSize = sizeof(AndorCapabilities);

    ui_error = GetCapabilities(caps);

    CheckError(ui_error, "GetCapabilities");
    if (caps->ulFeatures & AC_FEATURES_SHUTTER) {

        //If a shutter is present set is to always open
        ui_error = SetShutter(1, 1, 10, 10);
        CheckError(ui_error, "SetShutter");
    }
    //--------------------------------------------------------------------------------------
    //--------------------------------------------------------------------------------------
    //Acquire camera image
    //--------------------------------------------------------------------------------------
    //--------------------------------------------------------------------------------------
    
    //Ask for integration time;
    char i_input;
    float int_time;

    printf("\n\nEnter the integration time for the spectrometer i seconds: ");
    std::cin >> int_time;
    //Exposure time
    ui_error = SetExposureTime(int_time);
    CheckError(ui_error, "SetExposureTime");


    //Get the current temperature of the CCD
    float temperature;
    GetTemperatureF(&temperature);
    printf("\n\nTemperature of detector: %f", temperature);

    //Set a new temperature target for the CCD
    int set_temperature;
    printf("\n\nEnter the desired temperature of the detector: ");
    std::cin >> set_temperature;
    SetTemperature(set_temperature);

    //Turn the cooling on
    CoolerON();

    //Start a new thread monitoring the temperature of the CCD
    std::thread t1(TemperatureGraph);


    //Start loop asking what kind of spectrum the user wants to aquire.
    while (true) {

        
        printf("\n\ni) Capture single image: \nm) Continously capture images: \nc) Capture a single spectrum: \ns) Continously capture spectra: \n\nx) To Shutdown camera: \n");

        //Ask which action the camera should perform
        std::cin >> i_input;
        if (i_input == 'i') {
            AcquireSingleImage(CameraHandle);
        }
        if (i_input == 'm') {
            AcquireImagesContinue(CameraHandle);
        }
        if (i_input == 'c') {
           AcquireSingleSpectum(CameraHandle);
        }
        if (i_input == 's') {
           AcquireSpectumContinue(CameraHandle);
        }
        if (i_input == 'k') {
            printf("\n\nEnter the desired temperature of the detector: ");
            std::cin >> set_temperature;
            SetTemperature(set_temperature);
        }

        //Break loop
        if (i_input == 'x') {
            break;
        }
    }

     


    //Shut down camera.
    ShutDown(); 
    printf("Cameras Shutdown\n\n");
    
    system("PAUSE");
    return 0;
}


void TemperatureGraph(){
    //Create a new graph for monitoring the CCD temperature

    //Create image of 1024x655 pixels
    CImg<unsigned char> LiveTemperatureGraph(1024, 655, 1, 3, 0);
    //For holding temperature data
    CImg<float> TemperatureGraph = LiveTemperatureGraph.get_crop(0, 0, 0, 0, 199, 0, 0, 0);

    //Display the LiveTemperatureGraph image 
    CImgDisplay disp(LiveTemperatureGraph, "Detector Temperature");
    //Define Colors black, white and red.
    const unsigned char black[] = { 0,0,0 }, white[] = { 255,255,255 }, red[] = { 255,0,0 };

    //Set up array holding last 200 data point of temperature
    float* temperature = new float[200];
    //Newest measured temperature
    float newTemp;

    //Take a new temperature measurement
    GetTemperatureF(&newTemp);
    //Fill the array with the same recently measured temperature, to create a starting graph for the temperature plot.
    for (int i = 0; i < 200; i++) {
        temperature[i] = newTemp;
    }
    
    //Continously measure the CCD temperature, and update the data array with the newest temperature, shifting all previous measurements.
    while (true) {

        //100 ms between measurements
        Sleep(100);

        //Shift the positions of the data array one down.
        for (int i = 0; i < 199; i++) {
            newTemp = temperature[i+1];
            temperature[i] = newTemp;
        }

        //Enter the newly measured temperature in the last space of the data array.
        GetTemperatureF(&newTemp);
        temperature[199] = newTemp;
        
        //write the temperature data to the Graph data. 
        for (int i = 0; i < 200; i++) {
           TemperatureGraph(i, 0) = temperature[i];
        }

        //Plot the new temperature graph and update
        LiveTemperatureGraph.fill(0).draw_graph(TemperatureGraph, red, 1, 1, 0, 30, -70);
        LiveTemperatureGraph.draw_grid(10, 10, 0, 0, false, false, white, 0.1f, 0x55555555, 0x55555555)
            .draw_grid(0, 10, 0, 0, false, false, white, 0.2f, 0x55555555, 0x55555555)
            .draw_axes(0, 20, 30, -70, white, 0.8f);
        LiveTemperatureGraph.resize(disp.resize(true)).display(disp.wait(1));

        //If the window is closed break the loop.
        if (disp.is_closed())
        {
            printf("\n");
            break;
        }
    }

}


//Returns True if function call was successful
bool CheckError(unsigned int _ui_err, const char* _cp_func)
{
    bool b_ret;

    if (_ui_err == DRV_SUCCESS) {
        b_ret = true;
    }
    else {
        printf("ERROR - %s -- %i\n\n", _cp_func, _ui_err);
        errorFlag = true;
        b_ret = false;
    }
    return b_ret;
}

//Set Acquisition Paramaters
void SetUpCamera(long _l_handle, AcqParams params)
{
    unsigned int ui_error;

    ui_error = SetCurrentCamera(_l_handle);
    CheckError(ui_error, "SetCurrentCamera");

    //0 = Full vertical binning
    //1 = Single-Track
    //2 = Multi-Track
    //3 = Random-Track
    //4 = Image
    //5 = Cropped
    ui_error = SetReadMode(params.i_readMode);
    CheckError(ui_error, "SetReadMode");

    //Set number of pixels binned horizontally and vertically. (1,1 = no binning)
    ui_error = SetFullImage(1, 1);
    CheckError(ui_error, "SetFullImage");

    //1 = Single scan
    //2 = Accumulate
    //3 = Kinetics
    //4 = Fast Kinetrics
    //5 = Run til Abort
    ui_error = SetAcquisitionMode(params.i_acquisitionMode);
    CheckError(ui_error, "SetAcquisitionMode");

    //Exposure time
    ui_error = SetExposureTime(params.f_exposureTime);
    CheckError(ui_error, "SetExposureTime");

}

//Take a single Acquisition (Image)
void AcquireSingleImage(long _l_handle)
{
    unsigned int ui_error;
    int i_x, i_y;
    unsigned long ul_size;

    //Set the currently used camera and choose read mode 4.
    ui_error = SetCurrentCamera(_l_handle);
    CheckError(ui_error, "SetCurrentCamera");

    ui_error = SetReadMode(4);
    CheckError(ui_error, "SetReadMode");

    //Get the pixels number of the detector to know the size of the data array to be constructed.
    ui_error = GetDetector(&i_x, &i_y);
    CheckError(ui_error, "GetDetector");
    ul_size = i_x * i_y;
    long* lp_data = new long[ul_size];

    //Start and acquisition and wait for it to complete, then read the data.
    ui_error = StartAcquisition();
    CheckError(ui_error, "StartAcquisition");

    ui_error = WaitForAcquisition();
    CheckError(ui_error, "WaitForAcquisition");

    ui_error = GetMostRecentImage(lp_data, ul_size);
    CheckError(ui_error, "GetMostRecentImage");


    //Find the maximum possible counts per pixel, i.e. the saturation level
    int i_depth(0);
    ui_error = GetBitDepth(0, &i_depth);
    CheckError(ui_error, "GetBitDepth");
    f_saturationLevel = 1;
    for (int i = 0; i < i_depth; ++i) {
        f_saturationLevel *= 2;
    }

    //Call a new function the create and image and display the data captured.
    DisplayImage(lp_data, i_x, i_y);

    if (lp_data) delete[] lp_data;
}

//Continuosly capture images
void AcquireImagesContinue(long _l_handle)
{
    unsigned int ui_error;
    int i_x, i_y;
    unsigned long ul_size;

    //Set the currently used camera and choose read mode 4.
    ui_error = SetCurrentCamera(_l_handle);
    CheckError(ui_error, "SetCurrentCamera");

    ui_error = SetReadMode(4);
    CheckError(ui_error, "SetReadMode");
    //Get the pixels number of the detector to know the size of the data array to be constructed.
    ui_error = GetDetector(&i_x, &i_y);
    CheckError(ui_error, "GetDetector");
    ul_size = i_x * i_y;
    long* lp_data = new long[ul_size];


    //Start and acquisition and wait for it to complete, then read the data.
    ui_error = StartAcquisition();
    CheckError(ui_error, "StartAcquisition");

    ui_error = WaitForAcquisition();
    CheckError(ui_error, "WaitForAcquisition");

    ui_error = GetMostRecentImage(lp_data, ul_size);
    CheckError(ui_error, "GetMostRecentImage");

    //Find the maximum possible counts per pixel, i.e. the saturation level
    int i_depth(0);
    ui_error = GetBitDepth(0, &i_depth);
    CheckError(ui_error, "GetBitDepth");
    f_saturationLevel = 1;
    for (int i = 0; i < i_depth; ++i) {
        f_saturationLevel *= 2;
    }

    //Call a new function the create and image and display the data captured.
    //The function will continously capture new images and update itself.
    DisplayImageupdating(lp_data, i_x, i_y);

    if (lp_data) delete[] lp_data;
}


void AcquireSingleSpectum(long _l_handle)
{
    unsigned int ui_error;
    int i_x, i_y;
    unsigned long ul_size;

    //Set the currently used camera and choose read mode 3.
    ui_error = SetCurrentCamera(_l_handle);
    CheckError(ui_error, "SetCurrentCamera");

    ui_error = SetReadMode(3);
    CheckError(ui_error, "SetReadMode");

    //Pixels to be used - center line, 128 of 256 pixels 20 pixels wide.
    ui_error = SetSingleTrack(128, 20);
    CheckError(ui_error, "SetSingleTrack");



    //Get the pixels number of the detector to know the length of the data array to be constructed.
    ui_error = GetDetector(&i_x, &i_y);
    CheckError(ui_error, "GetDetector");
    ul_size = i_x;
    WORD* lp_data = new WORD[ul_size];

    //Create image of 1024x655 pixels
    CImg<unsigned char> LiveSpectrumGraph(1024, 655, 1, 3, 0);
    //For holding spectrum data
    CImg<UINT16> SpectrumData = LiveSpectrumGraph.get_crop(0, 0, 0, 0, i_x - 1, 0, 0, 0);

    //Display the image 
    CImgDisplay disp(LiveSpectrumGraph, "Static Spectrum Plot");
    const unsigned char black[] = { 0,0,0 }, white[] = { 255,255,255 }, green[] = { 0,255,0 };

    //Start an acqustition and wait for it to complete.
    ui_error = StartAcquisition();
        CheckError(ui_error, "StartAcquisition");

        ui_error = WaitForAcquisition();
        CheckError(ui_error, "WaitForAcquisition");

        ui_error = GetMostRecentImage16(lp_data, ul_size);
        CheckError(ui_error, "GetMostRecentImage");

        //Transfor the captured spectrum data to the graph data array.
        for (int i = 0; i < i_x; i++) {
            SpectrumData(i, 0) = lp_data[i];
        }
        //Plot the captured spectrum
        LiveSpectrumGraph.fill(0).draw_graph(SpectrumData, green, 1, 1, 0, 70000, 0);
        LiveSpectrumGraph.draw_grid(10, 10, 0, 0, false, false, white, 0.1f, 0x55555555, 0x55555555)
            .draw_grid(50, 50, 0, 0, false, false, white, 0.2f, 0x55555555, 0x55555555)
            .draw_axes(0, 2000, 70000, 0, white, 0.8f);
        LiveSpectrumGraph.resize(disp.resize(false)).display(disp.wait(1));

    printf("\n");


    
    system("PAUSE");

}

void AcquireSpectumContinue(long _l_handle)
{
    unsigned int ui_error;
    int i_x, i_y;
    unsigned long ul_size;

    //Set the currently used camera and choose read mode 3.
    ui_error = SetCurrentCamera(_l_handle);
    CheckError(ui_error, "SetCurrentCamera");

    ui_error = SetReadMode(3);
    CheckError(ui_error, "SetReadMode");

    //Pixels to be used - center line, 128 of 256 pixels 20 pixels wide.
    ui_error = SetSingleTrack(128, 20);
    CheckError(ui_error, "SetSingleTrack");



    //Get the pixels number of the detector to know the length of the data array to be constructed.
    ui_error = GetDetector(&i_x, &i_y);
    CheckError(ui_error, "GetDetector");
    ul_size = i_x;
    WORD* lp_data = new WORD[ul_size];

    //Create image of 1024x655 pixels
    CImg<unsigned char> LiveSpectrumGraph(1024, 655, 1, 3, 0);
    //For holding spectrum data
    CImg<UINT16> SpectrumData = LiveSpectrumGraph.get_crop(0, 0, 0, 0, i_x - 1 , 0, 0, 0);

    //Display the image 
    CImgDisplay disp(LiveSpectrumGraph, "Spectrum Plot");
    const unsigned char black[] = { 0,0,0 }, white[] = { 255,255,255 }, green[] = { 0,255,0 };

    //Until the window is closed, continously capture spectra and update the graph
    while (true) {


        //Start an acqustition and wait for it to complete.
        ui_error = StartAcquisition();
        CheckError(ui_error, "StartAcquisition");

        ui_error = WaitForAcquisition();
        CheckError(ui_error, "WaitForAcquisition");

        ui_error = GetMostRecentImage16(lp_data, ul_size);
        CheckError(ui_error, "GetMostRecentImage");

        //Transfor the captured spectrum data to the graph data array.
        for (int i = 0; i < i_x ; i++) {
            SpectrumData(i, 0) = lp_data[i];
        }

        //Plot the captured spectrum
        LiveSpectrumGraph.fill(0).draw_graph(SpectrumData, green, 1, 1, 0, 70000, 0);
        LiveSpectrumGraph.draw_grid(10, 10, 0, 0, false, false, white, 0.1f, 0x55555555, 0x55555555)
            .draw_grid(50, 50, 0, 0, false, false, white, 0.2f, 0x55555555, 0x55555555)
            .draw_axes(0, 2000, 70000, 0, white, 0.8f);
        //Update the graph with the newly plotted data.
        LiveSpectrumGraph.resize(disp.resize(false)).display(disp.wait(1));
        //Break the loop if the window is closed.
        if (disp.is_closed())
        {
            printf("\n");
            break;
        }
    }
    system("PAUSE");

}



void DisplayImage(long* _lp_data, int _i_xPx, int _i_yPx)
{
    //Using third party Package 'CImg'

    //Create a CImg Object the width and height of the sensor, 2 Dimensional, one color chanel per pixel, pixels defaulted to 0(black)
    CImg<float> img(_i_xPx, _i_yPx, 1, 1, 0);

    //fill the img window in black
    img.fill(0);

    int i_counter = 0;
    for (int y = 0; y < _i_yPx; ++y)
    {
        for (int x = 0; x < _i_xPx; x++)
        {
            unsigned char _ucp_grayScale[] = { 255,255,255 };         //define the colour white
            float f_intensity = _lp_data[i_counter] / f_saturationLevel;   //Calculate pixel intensity
            img.draw_point(x, y, _ucp_grayScale, f_intensity);           //Draw Pixel
            i_counter++;
        }
    }
    img.display("Image");

    printf("\n");
    system("PAUSE");
}

//Draw Image using the CImg library
void DisplayImageupdating(long* _lp_data, int _i_xPx, int _i_yPx)
{
    //Using third party Package 'CImg'

    //Create a CImg Object the width and height of the sensor, 2 Dimensional, one color chanel per pixel, pixels defaulted to 0(black)
    CImg<float> img(_i_xPx, _i_yPx, 1, 1, 0);
    CImgDisplay disp(img, "LiveStream");
    //fill the img window in black
    img.fill(0);        
    unsigned int ui_error;
    unsigned long ul_size;
    ul_size = _i_xPx * _i_yPx;

    while (true)
    {

        ui_error = StartAcquisition();
        CheckError(ui_error, "StartAcquisition");

        ui_error = WaitForAcquisition();
        CheckError(ui_error, "WaitForAcquisition");

        ui_error = GetMostRecentImage(_lp_data, ul_size);
        CheckError(ui_error, "GetMostRecentImage");
        //Find the maximum possible counts per pixel, i.e. the saturation level
        int i_depth(0);
        ui_error = GetBitDepth(0, &i_depth);
        CheckError(ui_error, "GetBitDepth");
        f_saturationLevel = 1;
        for (int i = 0; i < i_depth; ++i) {
            f_saturationLevel *= 2;
        }
        int i_counter = 0;
        img.fill(0);
        for (int y = 0; y < _i_yPx; ++y)
        {
            for (int x = 0; x < _i_xPx; x++)
            {
                unsigned char _ucp_grayScale[] = { 255,255,255 };               //define the colour white
                float f_intensity = _lp_data[i_counter] / f_saturationLevel;    //Calculate pixel intensity
                img.draw_point(x, y, _ucp_grayScale, f_intensity);              //Draw Pixel
                i_counter++;
            }
        }

        //img.display("Image");
        img.resize(disp.resize(false)).display(disp.wait(1));
        if (disp.is_closed()) 
        {
            printf("\n");
            break;
        }
    }

    system("PAUSE");

}


//---------------------------------------------------------------------------