#include "enginepreview.h"
#include "ui_enginepreview.h"
#include <iostream>
#include <QTimer>
#include "file.h"
#include <math.h>


EnginePreview::EnginePreview(QWidget *parent) :
    QGLWidget(parent),
    m_ui(new Ui::EnginePreview)
{
    m_ui->setupUi(this);
    frame_delay = 30;
    isAnimationMode = false;
    time.reset();
}

EnginePreview::~EnginePreview()
{
    delete m_ui;
}

void EnginePreview::changeEvent(QEvent *e)
{
    QWidget::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        m_ui->retranslateUi(this);
        break;
    default:
        break;
    }
}
void EnginePreview::initializeGL()
{

    M3D::EngineLimits limits;
    limits.camera_count = 1;
    limits.indeces_count = 1000000;
    limits.mesh_count = 1000;
    limits.model_count = 1000000;
    limits.shader_count = 1000;
    limits.texture_count = 4000;
    limits.texture_size = 4096;
    limits.vertices_count = 1024 * 1024;
    limits.material_count = limits.model_count;
    frame_no = 0;

    engine = M3D::create_engine ( &limits, width(), height(), 60.0);

    camera = engine->create_camera();
    light = engine->create_light();

    camera_params.set_params( 30.0* ( ( float ) height() / ( float ) width()), 0.001, 100.0, ( float ) ( ( float ) width() / ( float ) height()) );
    camera.set_camera_params(camera_params);

    engine->switch_camera(camera);
    engine->set_light(light);

    connect(&timer, SIGNAL(timeout()), this, SLOT(updateGL()));
    timer.start(frame_delay);
}
void EnginePreview::resizeGL(int width, int height)
{
    camera_params.set_params( 30.0/* ( ( float ) height / ( float ) width)*/, 0.001, 100.0, ( float ) ( ( float ) width / ( float ) height) );
    camera.set_camera_params(camera_params);

    engine->_init_(width, height, 60.0);
}
void EnginePreview::glDraw()
{
    float dt = time.get_elapsed_time();
    if(frame_delay!=timer.interval())
    {
        timer.stop();
        timer.start(frame_delay);
    }
    M3D::Coords tmp_c;
    if(isAnimationMode)
    {
        float xs, ys, zs, xas, yas, len;
        CameraPath p0, p1;
        p0 = camera_path[current_point];
        if(current_point+1>=camera_path.size())
            p1 = camera_path[loop_point];
        else
            p1 = camera_path[current_point+1];

        float delta_time = animation_timer.get_elapsed_time_noreset();
        if(delta_time>=p0.length)
        {
            animation_timer.reset();
            current_point++;
            if(current_point>=camera_path.size())current_point = loop_point;
            p0 = camera_path[current_point];
            p1 = camera_path[current_point+1];
        }

        camera_position = p0.position;
        camera_rotation = p0.rotation;
        xs = (p1.position.x_pos - p0.position.x_pos)/p0.length;
        ys = (p1.position.y_pos - p0.position.y_pos)/p0.length;
        zs = (p1.position.z_pos - p0.position.z_pos)/p0.length;
        xas = (p1.rotation.x_angle - p0.rotation.x_angle)/p0.length;
        yas = (p1.rotation.y_angle - p0.rotation.y_angle)/p0.length;
        len = p0.length;
        camera_position.x_pos += xs*delta_time;
        camera_position.y_pos += ys*delta_time;
        camera_position.z_pos += zs*delta_time;
        camera_rotation.x_angle += xas*delta_time;
        camera_rotation.y_angle += yas*delta_time;
        if(p0.is_loop_start)loop_point = current_point;
    }

    tmp_c = camera_position;
    tmp_c.x_pos *= base_scale.x_scale;
    tmp_c.y_pos *= base_scale.y_scale;
    tmp_c.z_pos *= base_scale.z_scale;
    camera.set_location(tmp_c);
    camera.set_rotation(camera_rotation);

    tmp_c = light_position;
    tmp_c.x_pos *= base_scale.x_scale;
    tmp_c.y_pos *= base_scale.y_scale;
    tmp_c.z_pos *= base_scale.z_scale;
    light.set_location(tmp_c);

    if(landscape_model != -1)landscape_model.set_scale(base_scale);

    if(water_model!=-1)
    {
        tmp_c = water_position;
        tmp_c.x_pos *= base_scale.x_scale;
        tmp_c.y_pos *= base_scale.y_scale;
        tmp_c.z_pos *= base_scale.z_scale;
        water_model.set_location(tmp_c);
        water_model.set_scale(base_scale);
    }

    for(std::map<std::string, BaseObject>::iterator base_it=models.begin(); base_it!=models.end(); ++base_it)
    {
        BaseObject base = base_it->second;
        for(std::map<M3D::ModelInterface, ObjectInfo>::iterator it=base.model_list.begin(); it!=base.model_list.end();++it)
        {
            tmp_c = it->second.position;
            tmp_c.x_pos *= base_scale.x_scale;
            tmp_c.y_pos *= base_scale.y_scale;
            tmp_c.z_pos *= base_scale.z_scale;
            M3D::ModelInterface m_int = it->first;
            m_int.set_location(tmp_c);
            m_int.set_rotation(it->second.rotation);
            m_int.set_scale(it->second.scale);
        }
    }
    engine->draw();
    swapBuffers();
    ++frame_no;
}
void EnginePreview::set_landscape(char *model_path)
{
    if(landscape_model != -1 )engine->remove_model(landscape_model);
    landscape_model = engine->create_model();
    landscape_model.load(model_path);
    engine->add_model(landscape_model);
}
void EnginePreview::set_water(char *model_path)
{
    if(water_model != -1 )engine->remove_model(water_model);
    water_model = engine->create_model();
    water_model.load(model_path);
    engine->add_model(water_model);
}
void EnginePreview::load_base_object(const char *model_path, const char *model_name)
{
    if(models.find(model_name)!=models.end())models.erase(model_name);
    BaseObject obj;
    obj.base_model = engine->create_model();
    obj.base_model.load(model_path);
    obj.name = model_name;
    obj.path = strcasestr(model_path, "res/models");
    models.insert(std::make_pair(model_name, obj));
}
bool EnginePreview::check_base_object(const char *model_name)
{
    return models.find(model_name)!=models.end();
}
int EnginePreview::add_object(const char *base_model, float x, float y, float z, float scale)
{
    if(!check_base_object(base_model))return -1;
    M3D::ModelInterface bmodel = models.find(base_model)->second.base_model;
    M3D::ModelInterface model(bmodel.clone());
    ObjectInfo obj;
    obj.position.x_pos = x;
    obj.position.y_pos = y;
    obj.position.z_pos = z;
    obj.scale.x_scale = scale;
    obj.scale.y_scale = scale;
    obj.scale.z_scale = scale;
    engine->add_model(model);
    BaseObject *base = &(models.find(base_model)->second);
    base->model_list.insert(std::make_pair(model, obj));
    return model;
}
void EnginePreview::clear_object_list()
{
    for(std::map<std::string, BaseObject>::iterator base_it=models.begin(); base_it!=models.end(); ++base_it)
    {
        BaseObject base = base_it->second;
        for(std::map<M3D::ModelInterface, ObjectInfo>::iterator it=base.model_list.begin(); it!=base.model_list.end();++it)
        {
            engine->remove_model(it->first);
        }
        base_it->second.model_list.clear();
    }
    models.clear();
}
std::vector<M3D::Coords> EnginePreview::get_object_list()
{
    std::vector<M3D::Coords> vec;
    for(std::map<std::string, BaseObject>::iterator base_it=models.begin(); base_it!=models.end(); ++base_it)
    {
        BaseObject base = base_it->second;
        for(std::map<M3D::ModelInterface, ObjectInfo>::iterator it=base.model_list.begin(); it!=base.model_list.end();++it)
        {
            vec.push_back(it->second.position);
        }
    }
    return vec;
}
ObjectInfo* EnginePreview::get_object_info(const char *base, int oid)
{
    if(!check_base_object(base))return 0;
    BaseObject *obj = &(models.find(base)->second);
    for(std::map<M3D::ModelInterface, ObjectInfo>::iterator it=obj->model_list.begin(); it!=obj->model_list.end();++it)
    {
        if(it->first == oid)return &(it->second);
    }
    return 0;
}
void EnginePreview::save(const std::string &fname)
{
    M3D::File file(fname, true);

    file.write(&base_scale, sizeof(base_scale));
    file.write(&camera_position, sizeof(camera_position));
    file.write(&camera_rotation, sizeof(camera_rotation));
    file.write(&light_position, sizeof(light_position));
    file.write(&water_position, sizeof(water_position));

    short int bo_c = models.size();
    file.write(&bo_c, sizeof(short int));
    for(std::map<std::string, BaseObject>::iterator base_it=models.begin(); base_it!=models.end(); ++base_it)
    {
        BaseObject *base = &(base_it->second);
        short int length = strlen(base->path.c_str())+1;
        file.write(&length, sizeof(short int));
        file.write(base->path.c_str(), length);
        length = strlen(base->name.c_str())+1;
        file.write(&length, sizeof(short int));
        file.write(base->name.c_str(), length);
        short int count = base->model_list.size();
        file.write(&count, sizeof(short int));
        for(std::map<M3D::ModelInterface, ObjectInfo>::iterator it=base->model_list.begin(); it!=base->model_list.end();++it)
        {
            file.write(&(it->second.position), sizeof(M3D::Coords));
            file.write(&(it->second.scale), sizeof(M3D::Scale));
            file.write(&(it->second.rotation), sizeof(M3D::Rotation));
        }
    }
    short int cam_path_len;
    cam_path_len = camera_path.size();
    file.write(&cam_path_len, sizeof(cam_path_len));
    for(std::vector<CameraPath>::iterator it=camera_path.begin(); it!=camera_path.end(); ++it)
    {
        CameraPath p = *it;
        file.write(&p, sizeof(CameraPath));
    }
}
void EnginePreview::load(const std::string &fname)
{
    M3D::File file(fname, false);
    file.read(&base_scale, sizeof(base_scale));
    file.read(&camera_position, sizeof(camera_position));
    file.read(&camera_rotation, sizeof(camera_rotation));
    file.read(&light_position, sizeof(light_position));
    file.read(&water_position, sizeof(water_position));

    short int base_object_count;
    file.read(&base_object_count, sizeof(short int));
    for(int a=0; a<base_object_count;++a)
    {
        BaseObject obj;
        short int length;
        file.read(&length, sizeof(short int));
        char path[length];
        file.read(path, length);
        obj.path = path;

        file.read(&length, sizeof(short int));
        char name[length];
        file.read(name, length);
        obj.name = name;

        short int count;
        file.read(&count, sizeof(short int));


        obj.base_model = engine->create_model();
        obj.base_model.load(obj.path);
        for(int b = 0; b<count; ++b)
        {
            ObjectInfo inf;
            file.read(&inf.position, sizeof(M3D::Coords));
            file.read(&inf.scale, sizeof(M3D::Scale));
            file.read(&inf.rotation, sizeof(M3D::Rotation));

            M3D::ModelInterface model(obj.base_model.clone());
            engine->add_model(model);
            obj.model_list.insert(std::make_pair(model, inf));
        }
        models.insert(std::make_pair(obj.name, obj));
    }
    short int cam_path_len;
    file.read(&cam_path_len, sizeof(cam_path_len));
    camera_path.clear();
    camera_path.reserve(cam_path_len);
    for(int a=0; a<cam_path_len; ++a)
    {
        CameraPath p;
        file.read(&p, sizeof(CameraPath));
        camera_path.push_back(p);
    }
}
bool EnginePreview::remove_object(const char *base_model, int oid)
{
    if(!check_base_object(base_model))return false;
    BaseObject *obj = &(models.find(base_model)->second);
    for(std::map<M3D::ModelInterface, ObjectInfo>::iterator it=obj->model_list.begin(); it!=obj->model_list.end();++it)
    {
        if(it->first == oid)
        {
            engine->remove_model(it->first);
            obj->model_list.erase(it);
            return true;
        }
    }
    return false;
}
int EnginePreview::find_nearest_object(const char *base_model, float x, float z)
{
    if(!check_base_object(base_model))return -1;
    BaseObject *obj = &(models.find(base_model)->second);
    ObjectInfo nearest = obj->model_list.begin()->second;
    M3D::ModelInterface nearest_model= obj->model_list.begin()->first;
    float dx, dz;
    dx = x - nearest.position.x_pos;
    dz = z - nearest.position.z_pos;
    float l;
    l = sqrt(dx*dx+dz*dz);
    for(std::map<M3D::ModelInterface, ObjectInfo>::iterator it=obj->model_list.begin(); it!=obj->model_list.end();++it)
    {
        float dx1, dz1;
        dx1 = x - it->second.position.x_pos;
        dz1 = z - it->second.position.z_pos;
        float l1 = sqrt(dx1*dx1+dz1*dz1);
        if(l1<l)
        {
            nearest_model = it->first;
            l = l1;
        }
    }
    return nearest_model;
}
int EnginePreview::add_cam_path_point(float x, float y, float z, float x_ang, float y_ang, float z_ang, bool is_loop_start, float length)
{
    CameraPath cp;
    cp.position.x_pos = x;
    cp.position.y_pos = y;
    cp.position.z_pos = z;
    cp.rotation.x_angle = x_ang;
    cp.rotation.y_angle = y_ang;
    cp.rotation.z_angle = z_ang;
    cp.is_loop_start = is_loop_start;
    cp.length = length;
    camera_path.push_back(cp);
    return camera_path.size()-1;
}
CameraPath* EnginePreview::get_cam_path_point(int id)
{
    return &(camera_path[id]);
}
