//  Copyright (C) 2001-2003, Evolution Robotics, Inc.
//  Any reproduction is strictly prohibited without the explicit
//  written permission of Evolution Robotics, Inc.  All rights reserved.


/**
 * @file    bump_sensor.cpp
 *
 * Test utility for cameras
 */

#pragma warning( disable : 4267 )

//#include <cstdio>

#include <conio.h>
#include <stdlib.h>
#include <signal.h>
#include <math.h>
#include <float.h>
#include <evolution/Resource.hpp>
#include <atltime.h>

// #include "..\\matrix.cpp"

// Globals used by signal handler
#define SENSOR_NAME         "Camera"
#define SENSOR_INTERFACE    "Evolution.ICamera"

// Convenient typedefs for Evolution types
typedef Evolution::Result              Result;
typedef Evolution::ResourceManager     ResourceManager;
typedef Evolution::ResourceConfigParser ResourceConfigParser;
typedef Evolution::IResourceContainer  IResourceContainer;
typedef Evolution::IResource           IResource;
typedef Evolution::ICamera             ICamera;
typedef Evolution::DeviceBusConfig     DeviceBusConfig;
typedef Evolution::DeviceList          DeviceList;
typedef Evolution::String              String;
typedef Evolution::Image               Image;
typedef Evolution::IDriveSystem        IDriveSystem;


typedef Evolution::uchar               uchar;

typedef ICamera Sensor;
typedef std::vector<Sensor*> SensorList;
typedef std::vector<const char*> StringList;

String* sensor_id = new String;

SensorList* sensors = NULL;
StringList* names = NULL;
IResourceContainer* resource_container = NULL;
ResourceManager* g_manager = NULL;
std::auto_ptr<ResourceManager> g_manager_cleanup;

void shutdown_interrupt(int signum)
{
    static bool shutting_down = false;
    signal(signum, shutdown_interrupt);
    if (signum == SIGINT)
    {
        if (shutting_down)
        {
            return;
        }
        shutting_down = true;
    }
}

void exit_handler (void)
{
    /**if (g_manager && (g_manager->is_active ()))
    {
        g_manager->deactivate ();
    }**/
}

Sensor* get_sensor(const char* sensor_id, bool print_error)
{
    // Get sensor interface
    Sensor* sensor = NULL;
    if ( resource_container->obtain_interface (0, sensor_id, 
          SENSOR_INTERFACE, (void**) &sensor) != Evolution::RESULT_SUCCESS )
    {
        if (print_error)
            std::cout << "ERROR: Resource manager failed to obtain " SENSOR_NAME 
                      << " '" << sensor_id << "' (maybe it's not setup as a " 
                      << SENSOR_NAME "?)\n";
        return NULL;
    }

    if (sensor == NULL && print_error)
        std::cout << "ERROR: " SENSOR_NAME " '" << sensor_id << "' not found.\n";

    return sensor;
}

bool get_sensor_list(SensorList* sensors, StringList* names)
{
    std::cout << "Accessing all " SENSOR_NAME "s in system.\n";

    const ResourceConfigParser* r_config = NULL;
    if (resource_container->get_configuration(0, &r_config) != Evolution::RESULT_SUCCESS)
    {
        std::cout << "ERROR: Could not load Resource Configuration parser.\n";
        return false;
    }

    const ResourceConfigParser::BusList& bus_list = r_config->get_bus_list(); 
    

    for (ResourceConfigParser::BusList::const_iterator bus_iter = bus_list.begin();
         bus_iter != bus_list.end(); ++bus_iter)
    {
        //DeviceBusConfig* device_list = bus_iter.get_data();
        const DeviceBusConfig* device_bus = (*bus_iter);//.devices.begin(); 
        
        //device_list iterator.
        for (DeviceList::const_iterator device_iter = device_bus->devices.begin();
             device_iter != device_bus->devices.end(); ++device_iter) 
        {
            // Get the sensor's name
            
            if ((*device_iter)->get_id(sensor_id) != Evolution::RESULT_SUCCESS)
                continue;

            // Get the sensor interface
            Sensor* sensor = get_sensor(sensor_id->c_str(), 0);
            if (sensor == NULL)
                continue;

            // Its valid-- release the interface for now
            resource_container->release_interface (0, sensor);  

            // Track sensor in list
            const char *name = sensor_id->c_str();
            char *copy = new char[strlen(name)+1];
            strcpy(copy, name);
            sensors->push_back(sensor);
            names->push_back(copy);
            std::cout << "Camera found: " << sensor_id->c_str() << "\n";
        }
    }

    std::cout << "\n";

    return true;
}

void test_sensors(SensorList* sensors, StringList* names,
                  int frames, double quality)
{
    std::cout << "Reading " SENSOR_NAME "s " << frames << " times.\n";

    for (int frame = 1; frame <= frames; frame++)
    {
        std::cout << "Frame " << frame << ": ";

        for (unsigned int i = 0; i < sensors->size() && names->size(); i++)
        {
            std::cout << "  " << (*names)[i] << ": ";

            const Image* image;
            if ((*sensors)[i]->get_image(0, &image) != Evolution::RESULT_SUCCESS)
            {
                std::cout << "Could not get image it took\n";
                continue;
            }

            char filename[80];
            std::sprintf(filename, "%s_%.4i.jpg", (*names)[i], frame);
            if (image->write_file(filename, quality) != Evolution::RESULT_SUCCESS) {
                std::cout << "Failed to write image\n";
                continue;
            } 

            std::cout << "Wrote image\n";
        }

        if (frame < frames) {
            std::cout << "\n";
            Evolution::Platform::millisecond_sleep(500);
        }
    }
}



bool isRed(uchar r, uchar g, uchar b)
{
    return r > 50 && g < 20 && b < 30;
}


void printRow(const Image* i, int row, int width)
{
    uchar r, g, b;

    for (int x = 0; x < width; x++)
    {
        i->get_pixel_rgb(x, row, &r, &g, &b);
        printf("%s", isRed(r, g, b) ? "#" : " ");
    }
}

void printRowChar(const Image* i, int row, int width, int step)
{
    uchar r, g, b;
    printf("Row %d :",row);
    for (int x = 0; x < width; x+=step)
    {
        i->get_pixel_rgb(x, row, &r, &g, &b);
        printf("%s", isRed(r, g, b) ? "#" : " ");
    }
}

void printRow(const Image* i, int row, int width, int step)
{
    uchar r, g, b;
    printf("Row %d :",row);
    for (int x = 0; x < width; x+=step)
    {
        i->get_pixel_rgb(x, row, &r, &g, &b);
        printf("(%d,%d,%d) ", r, g, b);
    }
}

void printImage(const Image* i, int step)
{
    int width  = i->get_width();
    int height = i->get_height();

    for (int y = 0; y < height; y+=step)
    {
        printRow(i, y, width,step);
        printf("\n");
        printRowChar(i, y, width,2);
    }
}

void printImage(const Image* i)
{
    int width  = i->get_width();
    int height = i->get_height();

    for (int y = 0; y < height; y++)
    {
        printRow(i, y, width);
        printf("\n");
    }
}



int get_median(const Image* i, int row)
{
    int w = i->get_width();

    int result;

    int* found_pos = new int[w];
    int  num_found = 0;

    uchar r, g, b;


    for (int x = 0; x < w; x++)
    {
        i->get_pixel_rgb(x, row, &r, &g, &b);
        if (isRed(r, g, b))
            found_pos[num_found++] = x;
    }

    int median = num_found / 2;

    if (num_found % 2)
        result = found_pos[median];
    else
        result = (found_pos[median - 1] + found_pos[median]) / 2;

//  printf("For row %d:\n", row);
//  printRow(i, row, w);

//  printf("Array looks like: ");
//  for (int foo = 0; foo < num_found; foo++)
//      printf("%d ", found_pos[foo]);
//  printf("\n");


    delete[] found_pos;

    if (num_found)
    {
//      printf("result is %d\n", result);
        return result;
    }
    else
    {
//      printf("no result found\n");
        return -1;
    }
}


bool slope(const Image* i, double& slope)
{
    int height = i->get_height();

    int top_line    = 10;
    int bottom_line = height - 10;

    if (slope < -0.1)
        top_line = 100;
    if (slope < -0.2)
        top_line = 160;
        
    int top_1, top_2;
    int bot_1, bot_2;

    do {
        top_line += 4;
        if (top_line >= bottom_line)
            return false;
        top_1 = get_median(i, top_line);
        top_2 = get_median(i, top_line - 1);
    } while (top_1 == -1 || top_2 == -1 || abs(top_1 - top_2) > 20);

    do {
        bottom_line -= 4;
        if (bottom_line <= top_line)
            return false;
        bot_1 = get_median(i, bottom_line);
        bot_2 = get_median(i, bottom_line + 1);
    } while (bot_1 == -1 || bot_2 == -1 || abs(bot_1 - bot_2) > 20);


//  printf("%d %d %d %d\n", top_1, top_2, bot_1, bot_2);

    double dx = ((top_1 + top_2) / 2.0) - ((bot_1 + bot_2) / 2.0);
    double dy = bottom_line - top_line;

    slope = atan2(dx, dy);

//  printf("%.4f\t%.4f\t%.4f\n", dx, dy, slope);

    return true;
}



void accelerate(IDriveSystem* drive, double accel)
{
    drive->move_delta(0, 0, 0, 200, 100, accel*100);
}

IDriveSystem* init_drive()
{
    IDriveSystem* drive;
    std::cout << "Obtaining the drive system interface\n";
    char* interface_name = "drive";
    if (resource_container->obtain_interface (0, interface_name,
                                     IDriveSystem::INTERFACE_ID,
                                     (void**) &drive) != Evolution::RESULT_SUCCESS)
    {
        std::cout << "ERROR: Resource manager failed to obtain Drived System"
                  << " '" << interface_name << "' (maybe it's not setup as a " 
                  << "Drive system?)\n";
        return NULL;
    }

    return drive;
}



const double BAD_CALIBRATION = 1024.0;

double calibrate_vertical(Sensor* camera)
{
    printf("Ready to calibrate.  Press any key to continue...\n");
    getch();

    double result = BAD_CALIBRATION;

    const Image* image;

    if (camera->get_image(0, &image) != Evolution::RESULT_SUCCESS)
    {
        printf("Couldn't get calibration image!\n");
        return BAD_CALIBRATION;
    }

    if (! slope(image, result))
    {
        printf("Found no red in calibration image!\n");
        return BAD_CALIBRATION;
    }

    printf("Calibration successful!  Offset is %.3f\n", result);


    return result;
}

double calibrate_delay(Sensor* camera)
{
    double result = BAD_CALIBRATION;

    const Image* image;

    int delay_count = 20;

    printf("Calibrating delay with %d pictures...", delay_count);

    DWORD start = GetTickCount();

    for (int i = 0; i < delay_count; i++)
    {
        if (camera->get_image(0, &image) != Evolution::RESULT_SUCCESS)
        {
            printf("Error in getting delay loop image number %d!\n", i);
            return BAD_CALIBRATION;
        }
    }

    DWORD end = GetTickCount();

    DWORD msec = end - start;

    double sec = msec / 1000.0;

    result = sec / (double)delay_count;

    printf("\n...done.  %d pictures taken in %f seconds -- rate of %.3f.\n", delay_count, sec, result);

    return result;
}


void control_loop(IDriveSystem* drive, Sensor* camera, double offset, double delay)
{
    const Image* image;
    double s = 0.0;

    Evolution::Matrix<double> accel(1, 1);
    accel[0][0] = 0;


    Evolution::Matrix<double> A(4, 4);
    A.zeros(4, 4);
    A[0][0] = 3.2868;
    A[0][1] = .7747;
    A[1][0] = 12.6537;
    A[1][1] = 3.2868;
    A[2][2] = 1;
    A[2][3] = 0.46;
    A[3][3] = 1;

    Evolution::Matrix<double> B(4, 1);
    B[0][0] = -0.2333;
    B[1][0] = -1.2912;
    B[2][0] = 0.1058;
    B[3][0] = 0.46;

    Evolution::Matrix<double> C(2, 4);
    C.zeros(2, 4);
    C[0][0] = 1;
    C[1][2] = 1;

    Evolution::Matrix<double> K(1, 4);
    K[0][0] = -14.1838;
    K[0][1] = -3.5052;
    K[0][2] = -.2443;
    K[0][3] = -.5274;


    /*
        Evolution::Matrix<double> A(4, 4);
    A.zeros(4, 4);
    A[0][0] = 1.9094;
    A[0][1] = 0.5924;
    A[1][0] = 4.466;
    A[1][1] = 1.9094;
    A[2][2] = 1;
    A[2][3] = 0.46;
    A[3][3] = 1;

    Evolution::Matrix<double> B(4, 1);
    B[0][0] = -0.0928;
    B[1][0] = -0.4557;
    B[2][0] = 0.1058;
    B[3][0] = 0.46;

    Evolution::Matrix<double> C(2, 4);
    C.zeros(2, 4);
    C[0][0] = 1;
    C[1][2] = 1;

    Evolution::Matrix<double> K(1, 4);
    K[0][0] = -19.8527;
    K[0][1] = -7.2125;
    K[0][2] = -0.4619;
    K[0][3] = -1.0774;
    */
/*

    // Non-Kalman

    Evolution::Matrix<double> L(4, 2);
    L[0][0] = 3.3949;
    L[0][1] = -0.1415;
    L[1][0] = 9.3272;
    L[1][1] = -0.3949;
    L[2][0] = 0.1306;
    L[2][1] = 1.8068;
    L[3][0] = 0.2008;
    L[3][1] = 1.6916;
*/
    // End Non-Kalman

    /*
    // Start Kalman

    Evolution::Matrix<double> L(4, 2);
    L[0][0] = 0.92;
    L[0][1] = -0.0006;
    L[1][0] = 2.5263;
    L[1][1] = -0.0047;
    L[2][0] = -0.0006;
    L[2][1] = 0.3060;
    L[3][0] = -0.0011;
    L[3][1] = 0.1211;

    // End Kalman
*/

    // Kalman 2

    Evolution::Matrix<double> L(4, 2);
    L[0][0] = .9757;
    L[0][1] = -.0003;
    L[1][0] = 3.9436;
    L[1][1] = -.0049;
    L[2][0] = -.0003;
    L[2][1] = .3060;
    L[3][0] = -.0007;
    L[3][1] = .1211;


    Evolution::Matrix<double> status(2, 1);
    status[0][0] = 0;
    status[1][0] = 0;

    Evolution::Matrix<double> tmp1, tmp2, tmp3, tmp4;
    tmp4.zeros(4, 1);

    double a;
    double max = 80.0;

    DWORD start = GetTickCount();
    DWORD image_t = 0;
    DWORD slope_t = 0;
    DWORD drive_t = 0;

    int i = 0;

    while (camera->get_image(0, &image) == Evolution::RESULT_SUCCESS)
    {
        i++;

//      image_t += (GetTickCount()) - start;
//      start = GetTickCount();

//      printf("Image:\t%u\n", GetTickCount() - start);


        if (! slope(image, s))
        {
            printf("Didn't find any red!\n");
            break;
        }

//      slope_t += (GetTickCount()) - start;
//      start = GetTickCount();

//      printf("Slope:\t%u\n", GetTickCount() - start);

        s -= offset;

//      printf("Angle is %.3f\n", s);

        status[0][0] = s;                                  // [0][0] is angle
        status[1][0] = status[1][0] + accel[0][0] * delay; // [1][0] is velocity
/*
        // Without Kalman
        tmp1 = B * accel;
        tmp2 = L * status;
        tmp3 = (A - (L * C)) * tmp4;
        tmp4 = tmp1 + tmp2 + tmp3;

        accel = (K * -1) * tmp4;
*/
        // With Kalman

        tmp1 = A * tmp4;
        tmp2 = B * accel;
        tmp3 = L * (status - (C * tmp4));
        tmp4 = tmp1 + tmp2 + tmp3;

        accel = (K * -1) * tmp4;

        a = accel[0][0] * 100;


//      a = s * 100;

        if (a > max)
            a = max;
        else if (a < -max)
            a = -max;

        printf("Accel: %.3f\n", a);

        drive->move_delta(0, 0, 0, a >= 0 ? 20 : -20, a >= 0 ? 80 : -80, a);
//      drive->move_and_turn(0, a >= 0 ? 40 : -40, a, 0, 0);


//      drive_t += (GetTickCount()) - start;
//      start = GetTickCount();

//      printf("Drive:\t%u\n", GetTickCount() - start);
    }

//  printf("Iterations: %d\nDrive:\t%u\nImage:\t%u\nAngle:\t%u\n", i, drive_t, image_t, slope_t);
}


void cameraTest(SensorList* sensors, StringList* names)
{
    IDriveSystem* drive = init_drive();
    if (drive == NULL)
    {
        Sleep(10000);
        return;
    }
    std::cout << "Reading " SENSOR_NAME "s\n";

    double delay = 0.45; //calibrate_delay((*sensors)[0]);
/*
    if (delay == BAD_CALIBRATION)
    {
        printf("Error in calculating the delay, bailing out.\n");
        return;
    }
*/
    double offset = calibrate_vertical((*sensors)[0]);

    if (offset == BAD_CALIBRATION)
    {
        printf("Error in calibrating the offset, bailing out.\n");
        return;
    }

    printf("Press any key to go...\n");
    getch();

    control_loop(drive, (*sensors)[0], offset, delay);
}

int main (int argc, char** argv)
{
    // Signal Handling
    signal(SIGINT, shutdown_interrupt);
#if !defined(EVOLUTION_PLATFORM_WIN32)
    signal(SIGCHLD, shutdown_interrupt);
    signal(SIGPIPE, shutdown_interrupt);
#endif

    // Exit handling
    std::atexit (exit_handler);

    // Set up logging.
    ERSP_LOG_SET_ROOT_PRIORITY(LOG_INFO);

    //set up containers
    Result result = Evolution::RESULT_SUCCESS;
    ResourceManager* manager;
    manager = new ResourceManager (NULL, result);
    g_manager = manager;
    g_manager_cleanup.reset (g_manager);
    if (result != Evolution::RESULT_SUCCESS)
    {
        std::cout << "ERROR: Failed to load resource manager.\n";
        return 1;
    }

    if (manager->get_resource_container (0, &resource_container) !=Evolution:: RESULT_SUCCESS)
    {
        std::cout << "ERROR: Failed to get resource container from resource manager.\n";
        return 1;
    }

    if (resource_container == NULL)
    {
        std::cout << "ERROR: Got NULL resource container from resource manager.\n";
        return 1;
    }

    // Create list of sensors to test
    SensorList sensor_list;
    StringList name_list;
    sensors = &sensor_list; // Globalized for shutdown_interrupt()
    names = &name_list;

    double quality = .90;
    int frames = 5;


   if (!get_sensor_list(&sensor_list, &name_list))
         return (1);


    cameraTest(&sensor_list, &name_list);

    printf("\n\nPress any key to quit...\n");
    getch();

    
    /*
    if (argc <= 1)
    {
        std::cout << "Usage: [camera name]... [options].  Use for " SENSOR_NAME "s only.\n"
                  << "Options:\n"
                  << "  --quality <num>    jpeg quality\n"
                  << "  --frames  <num>    number of frames to output\n" 
                  << "\nUsing all cameras found in system at " << quality
                  << "quality for " << frames << " frames\n";

//FIXME: We want to do this even when they specity frames and quality
//but no camera names...  How do we merge that together?
        // Use all sensors in system
        if (!get_sensor_list(&sensor_list, &name_list))
            return (1);
    }

    else
    {
        // Use requested sensors
        for (int i = 1; i < argc; i++)
        {
            if (!strcmp(argv[i], "--frames"))
            {
                // Make sure the array contains another entry
                if (++i < argc)
                    frames = atoi(argv[i]);
                continue;
            }

            if (!strcmp(argv[i], "--quality"))
            {
                // Make sure the array contains another entry
                if (++i < argc)
                    quality = atoi(argv[i]);
                continue;
            }

            Sensor* sensor = get_sensor(argv[i], true);
            if (sensor) {
                const char *name = argv[i];
                char *copy = new char[strlen(name)+1];
                strcpy(copy, name);
                name_list.push_back(copy);
                sensor_list.push_back(sensor);
            }
        }
    }

    if (!sensors->size()) {
        std::cout << "ERROR: No valid " SENSOR_NAME "s found.\n";
        return (1);
    }

    test_sensors(&sensor_list, &name_list, frames, quality);

    std::cout << "Deactivating resources\n";

    // Release allocated interfaces
    if (resource_container && sensors) {
        for (unsigned int i = 0; i < sensors->size(); i++)
            resource_container->release_interface (0, (*sensors)[i]);
        sensors = NULL;
    }

    // Delete string arrays
    if (names) {
        for (unsigned int i = 0; i < names->size(); i++)
            delete [] (*names)[i];
    }
    return (0);

    */
}


syntax highlighted by Code2HTML, v. 0.9.1