#include "UILine.h"
#include "GameEngine.h"
#include "McRFPy_API.h"
#include "PyVector.h"
#include "PyColor.h"
#include "PythonObjectCache.h"
#include <cmath>

UILine::UILine()
    : start_pos(0.0f, 0.0f),
      end_pos(0.0f, 0.0f),
      color(sf::Color::White),
      thickness(1.0f),
      vertices(sf::TriangleStrip, 4),
      vertices_dirty(true)
{
    position = sf::Vector2f(0.0f, 0.0f);
}

UILine::UILine(sf::Vector2f start, sf::Vector2f end, float thickness, sf::Color color)
    : start_pos(start),
      end_pos(end),
      color(color),
      thickness(thickness),
      vertices(sf::TriangleStrip, 4),
      vertices_dirty(true)
{
    // Set position to the midpoint for consistency with other UIDrawables
    position = (start + end) / 2.0f;
}

UILine::UILine(const UILine& other)
    : UIDrawable(other),
      start_pos(other.start_pos),
      end_pos(other.end_pos),
      color(other.color),
      thickness(other.thickness),
      vertices(sf::TriangleStrip, 4),
      vertices_dirty(true)
{
}

UILine& UILine::operator=(const UILine& other) {
    if (this != &other) {
        UIDrawable::operator=(other);
        start_pos = other.start_pos;
        end_pos = other.end_pos;
        color = other.color;
        thickness = other.thickness;
        vertices_dirty = true;
    }
    return *this;
}

UILine::UILine(UILine&& other) noexcept
    : UIDrawable(std::move(other)),
      start_pos(other.start_pos),
      end_pos(other.end_pos),
      color(other.color),
      thickness(other.thickness),
      vertices(std::move(other.vertices)),
      vertices_dirty(other.vertices_dirty)
{
}

UILine& UILine::operator=(UILine&& other) noexcept {
    if (this != &other) {
        UIDrawable::operator=(std::move(other));
        start_pos = other.start_pos;
        end_pos = other.end_pos;
        color = other.color;
        thickness = other.thickness;
        vertices = std::move(other.vertices);
        vertices_dirty = other.vertices_dirty;
    }
    return *this;
}

void UILine::updateVertices() const {
    if (!vertices_dirty) return;

    // Calculate direction and perpendicular
    sf::Vector2f direction = end_pos - start_pos;
    float length = std::sqrt(direction.x * direction.x + direction.y * direction.y);

    if (length < 0.0001f) {
        // Zero-length line - make a small dot
        float half = thickness / 2.0f;
        vertices[0].position = start_pos + sf::Vector2f(-half, -half);
        vertices[1].position = start_pos + sf::Vector2f(half, -half);
        vertices[2].position = start_pos + sf::Vector2f(-half, half);
        vertices[3].position = start_pos + sf::Vector2f(half, half);
    } else {
        // Normalize direction
        direction /= length;

        // Perpendicular vector
        sf::Vector2f perpendicular(-direction.y, direction.x);
        perpendicular *= thickness / 2.0f;

        // Create a quad (triangle strip) for the thick line
        vertices[0].position = start_pos + perpendicular;
        vertices[1].position = start_pos - perpendicular;
        vertices[2].position = end_pos + perpendicular;
        vertices[3].position = end_pos - perpendicular;
    }

    // Set colors
    for (int i = 0; i < 4; ++i) {
        vertices[i].color = color;
    }

    vertices_dirty = false;
}

void UILine::render(sf::Vector2f offset, sf::RenderTarget& target) {
    if (!visible) return;

    updateVertices();

    // Apply opacity to color
    sf::Color render_color = color;
    render_color.a = static_cast<sf::Uint8>(color.a * opacity);

    // Use ConvexShape to draw the line as a quad
    sf::ConvexShape line_shape(4);
    // Vertices are: 0=start+perp, 1=start-perp, 2=end+perp, 3=end-perp
    // ConvexShape needs points in clockwise/counter-clockwise order
    line_shape.setPoint(0, vertices[0].position + offset);  // start + perp
    line_shape.setPoint(1, vertices[2].position + offset);  // end + perp
    line_shape.setPoint(2, vertices[3].position + offset);  // end - perp
    line_shape.setPoint(3, vertices[1].position + offset);  // start - perp
    line_shape.setFillColor(render_color);
    line_shape.setOutlineThickness(0);

    target.draw(line_shape);
}

UIDrawable* UILine::click_at(sf::Vector2f point) {
    if (!click_callable) return nullptr;

    // Check if point is close enough to the line
    // Using a simple bounding box check plus distance-to-line calculation
    sf::FloatRect bounds = get_bounds();
    bounds.left -= thickness;
    bounds.top -= thickness;
    bounds.width += thickness * 2;
    bounds.height += thickness * 2;

    if (!bounds.contains(point)) return nullptr;

    // Calculate distance from point to line segment
    sf::Vector2f line_vec = end_pos - start_pos;
    sf::Vector2f point_vec = point - start_pos;

    float line_len_sq = line_vec.x * line_vec.x + line_vec.y * line_vec.y;
    float t = 0.0f;

    if (line_len_sq > 0.0001f) {
        t = std::max(0.0f, std::min(1.0f,
            (point_vec.x * line_vec.x + point_vec.y * line_vec.y) / line_len_sq));
    }

    sf::Vector2f closest = start_pos + t * line_vec;
    sf::Vector2f diff = point - closest;
    float distance = std::sqrt(diff.x * diff.x + diff.y * diff.y);

    // Click is valid if within thickness + some margin
    if (distance <= thickness / 2.0f + 2.0f) {
        return this;
    }

    return nullptr;
}

PyObjectsEnum UILine::derived_type() {
    return PyObjectsEnum::UILINE;
}

sf::FloatRect UILine::get_bounds() const {
    float min_x = std::min(start_pos.x, end_pos.x);
    float min_y = std::min(start_pos.y, end_pos.y);
    float max_x = std::max(start_pos.x, end_pos.x);
    float max_y = std::max(start_pos.y, end_pos.y);

    return sf::FloatRect(min_x, min_y, max_x - min_x, max_y - min_y);
}

void UILine::move(float dx, float dy) {
    start_pos.x += dx;
    start_pos.y += dy;
    end_pos.x += dx;
    end_pos.y += dy;
    position.x += dx;
    position.y += dy;
    vertices_dirty = true;
}

void UILine::resize(float w, float h) {
    // For a line, resize adjusts the end point relative to start
    end_pos = start_pos + sf::Vector2f(w, h);
    vertices_dirty = true;
}

// Animation property system
bool UILine::setProperty(const std::string& name, float value) {
    if (name == "thickness") {
        thickness = value;
        vertices_dirty = true;
        markDirty();  // #144 - Content change
        return true;
    }
    else if (name == "x") {
        float dx = value - position.x;
        move(dx, 0);
        markCompositeDirty();  // #144 - Position change, texture still valid
        return true;
    }
    else if (name == "y") {
        float dy = value - position.y;
        move(0, dy);
        markCompositeDirty();  // #144 - Position change, texture still valid
        return true;
    }
    else if (name == "start_x") {
        start_pos.x = value;
        vertices_dirty = true;
        markDirty();  // #144 - Content change
        return true;
    }
    else if (name == "start_y") {
        start_pos.y = value;
        vertices_dirty = true;
        markDirty();  // #144 - Content change
        return true;
    }
    else if (name == "end_x") {
        end_pos.x = value;
        vertices_dirty = true;
        markDirty();  // #144 - Content change
        return true;
    }
    else if (name == "end_y") {
        end_pos.y = value;
        vertices_dirty = true;
        markDirty();  // #144 - Content change
        return true;
    }
    return false;
}

bool UILine::setProperty(const std::string& name, const sf::Color& value) {
    if (name == "color") {
        color = value;
        vertices_dirty = true;
        markDirty();  // #144 - Content change
        return true;
    }
    return false;
}

bool UILine::setProperty(const std::string& name, const sf::Vector2f& value) {
    if (name == "start") {
        start_pos = value;
        vertices_dirty = true;
        markDirty();  // #144 - Content change
        return true;
    }
    else if (name == "end") {
        end_pos = value;
        vertices_dirty = true;
        markDirty();  // #144 - Content change
        return true;
    }
    return false;
}

bool UILine::getProperty(const std::string& name, float& value) const {
    if (name == "thickness") {
        value = thickness;
        return true;
    }
    else if (name == "x") {
        value = position.x;
        return true;
    }
    else if (name == "y") {
        value = position.y;
        return true;
    }
    else if (name == "start_x") {
        value = start_pos.x;
        return true;
    }
    else if (name == "start_y") {
        value = start_pos.y;
        return true;
    }
    else if (name == "end_x") {
        value = end_pos.x;
        return true;
    }
    else if (name == "end_y") {
        value = end_pos.y;
        return true;
    }
    return false;
}

bool UILine::getProperty(const std::string& name, sf::Color& value) const {
    if (name == "color") {
        value = color;
        return true;
    }
    return false;
}

bool UILine::getProperty(const std::string& name, sf::Vector2f& value) const {
    if (name == "start") {
        value = start_pos;
        return true;
    }
    else if (name == "end") {
        value = end_pos;
        return true;
    }
    return false;
}

// Python API implementation
PyObject* UILine::get_start(PyUILineObject* self, void* closure) {
    auto vec = self->data->getStart();
    auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
    auto obj = (PyVectorObject*)type->tp_alloc(type, 0);
    Py_DECREF(type);
    if (obj) {
        obj->data = vec;
    }
    return (PyObject*)obj;
}

int UILine::set_start(PyUILineObject* self, PyObject* value, void* closure) {
    PyVectorObject* vec = PyVector::from_arg(value);
    if (!vec) {
        PyErr_SetString(PyExc_TypeError, "start must be a Vector or tuple (x, y)");
        return -1;
    }
    self->data->setStart(vec->data);
    return 0;
}

PyObject* UILine::get_end(PyUILineObject* self, void* closure) {
    auto vec = self->data->getEnd();
    auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
    auto obj = (PyVectorObject*)type->tp_alloc(type, 0);
    Py_DECREF(type);
    if (obj) {
        obj->data = vec;
    }
    return (PyObject*)obj;
}

int UILine::set_end(PyUILineObject* self, PyObject* value, void* closure) {
    PyVectorObject* vec = PyVector::from_arg(value);
    if (!vec) {
        PyErr_SetString(PyExc_TypeError, "end must be a Vector or tuple (x, y)");
        return -1;
    }
    self->data->setEnd(vec->data);
    return 0;
}

PyObject* UILine::get_color(PyUILineObject* self, void* closure) {
    auto color = self->data->getColor();
    auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color");
    PyObject* args = Py_BuildValue("(iiii)", color.r, color.g, color.b, color.a);
    PyObject* obj = PyObject_CallObject((PyObject*)type, args);
    Py_DECREF(args);
    Py_DECREF(type);
    return obj;
}

int UILine::set_color(PyUILineObject* self, PyObject* value, void* closure) {
    auto color = PyColor::from_arg(value);
    if (!color) {
        PyErr_SetString(PyExc_TypeError, "color must be a Color or tuple (r, g, b) or (r, g, b, a)");
        return -1;
    }
    self->data->setColor(color->data);
    return 0;
}

PyObject* UILine::get_thickness(PyUILineObject* self, void* closure) {
    return PyFloat_FromDouble(self->data->getThickness());
}

int UILine::set_thickness(PyUILineObject* self, PyObject* value, void* closure) {
    float thickness;
    if (PyFloat_Check(value)) {
        thickness = PyFloat_AsDouble(value);
    } else if (PyLong_Check(value)) {
        thickness = PyLong_AsLong(value);
    } else {
        PyErr_SetString(PyExc_TypeError, "thickness must be a number");
        return -1;
    }

    if (thickness < 0.0f) {
        PyErr_SetString(PyExc_ValueError, "thickness must be non-negative");
        return -1;
    }

    self->data->setThickness(thickness);
    return 0;
}

// Define the Python type alias for macros
typedef PyUILineObject PyObjectType;

// Method definitions
PyMethodDef UILine_methods[] = {
    UIDRAWABLE_METHODS,
    {NULL}  // Sentinel
};

PyGetSetDef UILine::getsetters[] = {
    {"start", (getter)UILine::get_start, (setter)UILine::set_start,
     MCRF_PROPERTY(start, "Starting point of the line as a Vector."), NULL},
    {"end", (getter)UILine::get_end, (setter)UILine::set_end,
     MCRF_PROPERTY(end, "Ending point of the line as a Vector."), NULL},
    {"color", (getter)UILine::get_color, (setter)UILine::set_color,
     MCRF_PROPERTY(color, "Line color as a Color object."), NULL},
    {"thickness", (getter)UILine::get_thickness, (setter)UILine::set_thickness,
     MCRF_PROPERTY(thickness, "Line thickness in pixels."), NULL},
    {"on_click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
     MCRF_PROPERTY(on_click, "Callable executed when line is clicked."),
     (void*)PyObjectsEnum::UILINE},
    {"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int,
     MCRF_PROPERTY(z_index, "Z-order for rendering (lower values rendered first)."),
     (void*)PyObjectsEnum::UILINE},
    {"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name,
     MCRF_PROPERTY(name, "Name for finding this element."),
     (void*)PyObjectsEnum::UILINE},
    {"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos,
     MCRF_PROPERTY(pos, "Position as a Vector (midpoint of line)."),
     (void*)PyObjectsEnum::UILINE},
    UIDRAWABLE_GETSETTERS,
    UIDRAWABLE_PARENT_GETSETTERS(PyObjectsEnum::UILINE),
    {NULL}
};

PyObject* UILine::repr(PyUILineObject* self) {
    std::ostringstream ss;
    if (!self->data) {
        ss << "<Line (invalid internal object)>";
    } else {
        auto start = self->data->getStart();
        auto end = self->data->getEnd();
        auto color = self->data->getColor();
        ss << "<Line start=(" << start.x << ", " << start.y << ") "
           << "end=(" << end.x << ", " << end.y << ") "
           << "thickness=" << self->data->getThickness() << " "
           << "color=(" << (int)color.r << ", " << (int)color.g << ", "
           << (int)color.b << ", " << (int)color.a << ")>";
    }
    std::string repr_str = ss.str();
    return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}

int UILine::init(PyUILineObject* self, PyObject* args, PyObject* kwds) {
    // Arguments
    PyObject* start_obj = nullptr;
    PyObject* end_obj = nullptr;
    float thickness = 1.0f;
    PyObject* color_obj = nullptr;
    PyObject* click_handler = nullptr;
    int visible = 1;
    float opacity = 1.0f;
    int z_index = 0;
    const char* name = nullptr;

    static const char* kwlist[] = {
        "start", "end", "thickness", "color",
        "click", "visible", "opacity", "z_index", "name",
        nullptr
    };

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOfOOifiz", const_cast<char**>(kwlist),
                                     &start_obj, &end_obj, &thickness, &color_obj,
                                     &click_handler, &visible, &opacity, &z_index, &name)) {
        return -1;
    }

    // Parse start position
    sf::Vector2f start(0.0f, 0.0f);
    if (start_obj) {
        PyVectorObject* vec = PyVector::from_arg(start_obj);
        if (vec) {
            start = vec->data;
        } else {
            PyErr_Clear();
            PyErr_SetString(PyExc_TypeError, "start must be a Vector or tuple (x, y)");
            return -1;
        }
    }

    // Parse end position
    sf::Vector2f end(0.0f, 0.0f);
    if (end_obj) {
        PyVectorObject* vec = PyVector::from_arg(end_obj);
        if (vec) {
            end = vec->data;
        } else {
            PyErr_Clear();
            PyErr_SetString(PyExc_TypeError, "end must be a Vector or tuple (x, y)");
            return -1;
        }
    }

    // Parse color
    sf::Color color = sf::Color::White;
    if (color_obj && color_obj != Py_None) {
        auto pycolor = PyColor::from_arg(color_obj);
        if (pycolor) {
            color = pycolor->data;
        } else {
            PyErr_Clear();
            PyErr_SetString(PyExc_TypeError, "color must be a Color or tuple");
            return -1;
        }
    }

    // Validate thickness
    if (thickness < 0.0f) {
        PyErr_SetString(PyExc_ValueError, "thickness must be non-negative");
        return -1;
    }

    // Create the line
    self->data = std::make_shared<UILine>(start, end, thickness, color);
    self->data->visible = visible;
    self->data->opacity = opacity;
    self->data->z_index = z_index;

    if (name) {
        self->data->name = std::string(name);
    }

    // Handle click handler
    if (click_handler && click_handler != Py_None) {
        if (!PyCallable_Check(click_handler)) {
            PyErr_SetString(PyExc_TypeError, "click must be callable");
            return -1;
        }
        self->data->click_register(click_handler);
    }

    // Initialize weak reference list
    self->weakreflist = NULL;

    // Register in Python object cache
    if (self->data->serial_number == 0) {
        self->data->serial_number = PythonObjectCache::getInstance().assignSerial();
        PyObject* weakref = PyWeakref_NewRef((PyObject*)self, NULL);
        if (weakref) {
            PythonObjectCache::getInstance().registerObject(self->data->serial_number, weakref);
            Py_DECREF(weakref);
        }
    }

    return 0;
}
