/**
 * @file lv_vg_lite_path.c
 *
 */

/*********************
 *      INCLUDES
 *********************/

#include "lv_vg_lite_path.h"

#if LV_USE_DRAW_VG_LITE

#include "lv_draw_vg_lite_type.h"
#include "lv_vg_lite_math.h"
#include <float.h>

/*********************
 *      DEFINES
 *********************/

#define PATH_KAPPA 0.552284f

/* Magic number from https://spencermortensen.com/articles/bezier-circle/ */
#define PATH_ARC_MAGIC 0.55191502449351f

#define PATH_MEM_SIZE_MIN 128

#define SIGN(x) (math_zero(x) ? 0 : ((x) > 0 ? 1 : -1))

#define VLC_OP_ARG_LEN(OP, LEN) \
    case VLC_OP_##OP:           \
    return (LEN)

#define PATH_CURRENT_PTR(PATH) ((uint8_t*)(PATH)->base.path + (PATH)->base.path_length)
#define PATH_LENGTH_INC(PATH, LENGTH) ((PATH)->base.path_length += (LENGTH))

/**********************
 *      TYPEDEFS
 **********************/

struct _lv_vg_lite_path_t {
    vg_lite_path_t base;
    vg_lite_matrix_t matrix;
    size_t mem_size;
    uint8_t format_len;
    bool has_transform;
};

typedef struct {
    float min_x;
    float min_y;
    float max_x;
    float max_y;
} lv_vg_lite_path_bounds_t;

/**********************
 *  STATIC PROTOTYPES
 **********************/

/**********************
 *  STATIC VARIABLES
 **********************/

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void lv_vg_lite_path_init(struct _lv_draw_vg_lite_unit_t * unit)
{
    LV_ASSERT_NULL(unit);
    unit->global_path = lv_vg_lite_path_create(VG_LITE_FP32);
    unit->path_in_use = false;
}

void lv_vg_lite_path_deinit(struct _lv_draw_vg_lite_unit_t * unit)
{
    LV_ASSERT_NULL(unit);
    LV_ASSERT(!unit->path_in_use);
    lv_vg_lite_path_destroy(unit->global_path);
    unit->global_path = NULL;
}

lv_vg_lite_path_t * lv_vg_lite_path_create(vg_lite_format_t data_format)
{
    LV_PROFILER_DRAW_BEGIN;
    lv_vg_lite_path_t * path = lv_malloc_zeroed(sizeof(lv_vg_lite_path_t));
    LV_ASSERT_MALLOC(path);
    path->format_len = lv_vg_lite_path_format_len(data_format);
    LV_ASSERT(vg_lite_init_path(
                  &path->base,
                  data_format,
                  VG_LITE_HIGH,
                  0,
                  NULL,
                  0, 0, 0, 0)
              == VG_LITE_SUCCESS);
    LV_PROFILER_DRAW_END;
    return path;
}

void lv_vg_lite_path_destroy(lv_vg_lite_path_t * path)
{
    LV_PROFILER_DRAW_BEGIN;
    LV_ASSERT_NULL(path);
    if(path->base.path != NULL) {
        lv_free(path->base.path);
        path->base.path = NULL;

        /* clear remaining path data */
        LV_VG_LITE_CHECK_ERROR(vg_lite_clear_path(&path->base), {});
    }
    lv_free(path);
    LV_PROFILER_DRAW_END;
}

lv_vg_lite_path_t * lv_vg_lite_path_get(struct _lv_draw_vg_lite_unit_t * unit, vg_lite_format_t data_format)
{
    LV_ASSERT_NULL(unit);
    LV_ASSERT_NULL(unit->global_path);
    LV_ASSERT(!unit->path_in_use);
    lv_vg_lite_path_reset(unit->global_path, data_format);
    unit->path_in_use = true;
    return unit->global_path;
}

void lv_vg_lite_path_drop(struct _lv_draw_vg_lite_unit_t * unit, lv_vg_lite_path_t * path)
{
    LV_ASSERT_NULL(unit);
    LV_ASSERT_NULL(path);
    LV_ASSERT(unit->global_path == path);
    LV_ASSERT(unit->path_in_use);
    unit->path_in_use = false;
}

void lv_vg_lite_path_reset(lv_vg_lite_path_t * path, vg_lite_format_t data_format)
{
    LV_ASSERT_NULL(path);
    path->base.path_length = 0;
    path->base.format = data_format;
    path->base.quality = VG_LITE_HIGH;
    path->base.path_type = VG_LITE_DRAW_ZERO;
    path->format_len = lv_vg_lite_path_format_len(data_format);
    path->has_transform = false;
}

vg_lite_path_t * lv_vg_lite_path_get_path(lv_vg_lite_path_t * path)
{
    LV_ASSERT_NULL(path);
    return &path->base;
}

void lv_vg_lite_path_set_bounding_box(lv_vg_lite_path_t * path,
                                      float min_x, float min_y,
                                      float max_x, float max_y)
{
    LV_ASSERT_NULL(path);
    path->base.bounding_box[0] = min_x;
    path->base.bounding_box[1] = min_y;
    path->base.bounding_box[2] = max_x;
    path->base.bounding_box[3] = max_y;
}

void lv_vg_lite_path_set_bounding_box_area(lv_vg_lite_path_t * path, const lv_area_t * area)
{
    LV_ASSERT_NULL(path);
    LV_ASSERT_NULL(area);
    lv_vg_lite_path_set_bounding_box(path, area->x1, area->y1, area->x2 + 1, area->y2 + 1);
}

void lv_vg_lite_path_get_bounding_box(lv_vg_lite_path_t * path,
                                      float * min_x, float * min_y,
                                      float * max_x, float * max_y)
{
    LV_ASSERT_NULL(path);
    if(min_x) *min_x = path->base.bounding_box[0];
    if(min_y) *min_y = path->base.bounding_box[1];
    if(max_x) *max_x = path->base.bounding_box[2];
    if(max_y) *max_y = path->base.bounding_box[3];
}

static void path_bounds_iter_cb(void * user_data, uint8_t op_code, const float * data, uint32_t len)
{
    LV_UNUSED(op_code);

    if(len == 0) {
        return;
    }

    typedef struct {
        float x;
        float y;
    } point_t;

    const int pt_len = sizeof(point_t) / sizeof(float);

    LV_ASSERT(len % pt_len == 0);

    const point_t * pt = (point_t *)data;
    len /= pt_len;

    lv_vg_lite_path_bounds_t * bounds = user_data;

    for(uint32_t i = 0; i < len; i++) {
        if(pt[i].x < bounds->min_x) bounds->min_x = pt[i].x;
        if(pt[i].y < bounds->min_y) bounds->min_y = pt[i].y;
        if(pt[i].x > bounds->max_x) bounds->max_x = pt[i].x;
        if(pt[i].y > bounds->max_y) bounds->max_y = pt[i].y;
    }
}

bool lv_vg_lite_path_update_bounding_box(lv_vg_lite_path_t * path)
{
    LV_ASSERT_NULL(path);

    if(!path->format_len) {
        return false;
    }

    LV_PROFILER_DRAW_BEGIN;

    lv_vg_lite_path_bounds_t bounds;

    /* init bounds */
    bounds.min_x = FLT_MAX;
    bounds.min_y = FLT_MAX;
    bounds.max_x = FLT_MIN;
    bounds.max_y = FLT_MIN;

    /* calc bounds */
    lv_vg_lite_path_for_each_data(lv_vg_lite_path_get_path(path), path_bounds_iter_cb, &bounds);

    /* set bounds */
    lv_vg_lite_path_set_bounding_box(path, bounds.min_x, bounds.min_y, bounds.max_x, bounds.max_y);

    LV_PROFILER_DRAW_END;

    return true;
}

void lv_vg_lite_path_set_transform(lv_vg_lite_path_t * path, const vg_lite_matrix_t * matrix)
{
    LV_ASSERT_NULL(path);
    if(matrix) {
        path->matrix = *matrix;
    }

    path->has_transform = matrix ? true : false;
}

void lv_vg_lite_path_set_quality(lv_vg_lite_path_t * path, vg_lite_quality_t quality)
{
    LV_ASSERT_NULL(path);
    path->base.quality = quality;
}

void lv_vg_lite_path_reserve_space(lv_vg_lite_path_t * path, size_t len)
{
    bool need_reallocated = false;

    /*Calculate new mem size until match the contidion*/
    while(path->base.path_length + len > path->mem_size) {
        if(path->mem_size == 0) {
            path->mem_size = LV_MAX(len, PATH_MEM_SIZE_MIN);
        }
        else {
            /* Increase memory size by 1.5 times */
            path->mem_size = path->mem_size * 3 / 2;
        }
        need_reallocated = true;
    }

    if(!need_reallocated) {
        return;
    }

    path->base.path = lv_realloc(path->base.path, path->mem_size);
    LV_ASSERT_MALLOC(path->base.path);
}

static inline void lv_vg_lite_path_append_data(lv_vg_lite_path_t * path, const void * data, size_t len)
{
    LV_ASSERT_NULL(path);
    LV_ASSERT_NULL(data);
    lv_vg_lite_path_reserve_space(path, len);
    lv_memcpy(PATH_CURRENT_PTR(path), data, len);
    PATH_LENGTH_INC(path, len);
}

static inline void lv_vg_lite_path_append_op(lv_vg_lite_path_t * path, uint32_t op)
{
    void * ptr = PATH_CURRENT_PTR(path);
    switch(path->base.format) {
        case VG_LITE_FP32:
        case VG_LITE_S32:
            LV_VG_LITE_PATH_SET_OP_CODE(ptr, uint32_t, op);
            PATH_LENGTH_INC(path, sizeof(uint32_t));
            break;
        case VG_LITE_S16:
            LV_VG_LITE_PATH_SET_OP_CODE(ptr, uint16_t, op);
            PATH_LENGTH_INC(path, sizeof(uint16_t));
            break;
        case VG_LITE_S8:
            LV_VG_LITE_PATH_SET_OP_CODE(ptr, uint8_t, op);
            PATH_LENGTH_INC(path, sizeof(uint8_t));
            break;
        default:
            LV_ASSERT_FORMAT_MSG(false, "Invalid format: %d", path->base.format);
            break;
    }
}

static inline void lv_vg_lite_path_append_point(lv_vg_lite_path_t * path, float x, float y)
{
    if(path->has_transform) {
        LV_VG_LITE_ASSERT_MATRIX(&path->matrix);
        /* transform point */
        float ori_x = x;
        float ori_y = y;
        x = ori_x * path->matrix.m[0][0] + ori_y * path->matrix.m[0][1] + path->matrix.m[0][2];
        y = ori_x * path->matrix.m[1][0] + ori_y * path->matrix.m[1][1] + path->matrix.m[1][2];
    }

#define PATH_APPEND_POINT_DATA(X, Y, TYPE)       \
    do {                                         \
        TYPE * data = ptr;                       \
        *data++ = (TYPE)(X);                     \
        *data++ = (TYPE)(Y);                     \
        PATH_LENGTH_INC(path, sizeof(TYPE) * 2); \
    } while(0)

    void * ptr = PATH_CURRENT_PTR(path);
    switch(path->base.format) {
        case VG_LITE_FP32:
            PATH_APPEND_POINT_DATA(x, y, float);
            break;
        case VG_LITE_S32:
            PATH_APPEND_POINT_DATA(x, y, int32_t);
            break;
        case VG_LITE_S16:
            PATH_APPEND_POINT_DATA(x, y, int16_t);
            break;
        case VG_LITE_S8:
            PATH_APPEND_POINT_DATA(x, y, int8_t);
            break;
        default:
            LV_ASSERT_FORMAT_MSG(false, "Invalid format: %d", path->base.format);
            break;
    }
}

void lv_vg_lite_path_move_to(lv_vg_lite_path_t * path,
                             float x, float y)
{
    LV_ASSERT_NULL(path);
    lv_vg_lite_path_reserve_space(path, (1 + 2) * path->format_len);
    lv_vg_lite_path_append_op(path, VLC_OP_MOVE);
    lv_vg_lite_path_append_point(path, x, y);
}

void lv_vg_lite_path_line_to(lv_vg_lite_path_t * path,
                             float x, float y)
{
    LV_ASSERT_NULL(path);
    lv_vg_lite_path_reserve_space(path, (1 + 2) * path->format_len);
    lv_vg_lite_path_append_op(path, VLC_OP_LINE);
    lv_vg_lite_path_append_point(path, x, y);
}

void lv_vg_lite_path_quad_to(lv_vg_lite_path_t * path,
                             float cx, float cy,
                             float x, float y)
{
    LV_ASSERT_NULL(path);
    lv_vg_lite_path_reserve_space(path, (1 + 4) * path->format_len);
    lv_vg_lite_path_append_op(path, VLC_OP_QUAD);
    lv_vg_lite_path_append_point(path, cx, cy);
    lv_vg_lite_path_append_point(path, x, y);
}

void lv_vg_lite_path_cubic_to(lv_vg_lite_path_t * path,
                              float cx1, float cy1,
                              float cx2, float cy2,
                              float x, float y)
{
    LV_ASSERT_NULL(path);
    lv_vg_lite_path_reserve_space(path, (1 + 6) * path->format_len);
    lv_vg_lite_path_append_op(path, VLC_OP_CUBIC);
    lv_vg_lite_path_append_point(path, cx1, cy1);
    lv_vg_lite_path_append_point(path, cx2, cy2);
    lv_vg_lite_path_append_point(path, x, y);
}

void lv_vg_lite_path_close(lv_vg_lite_path_t * path)
{
    LV_ASSERT_NULL(path);
    lv_vg_lite_path_reserve_space(path, 1 * path->format_len);
    lv_vg_lite_path_append_op(path, VLC_OP_CLOSE);
}

void lv_vg_lite_path_end(lv_vg_lite_path_t * path)
{
    LV_ASSERT_NULL(path);
    lv_vg_lite_path_reserve_space(path, 1 * path->format_len);
    lv_vg_lite_path_append_op(path, VLC_OP_END);
    path->base.add_end = 1;
}

void lv_vg_lite_path_append_rect(
    lv_vg_lite_path_t * path,
    float x, float y,
    float w, float h,
    float r)
{
    LV_PROFILER_DRAW_BEGIN;
    const float half_w = w / 2.0f;
    const float half_h = h / 2.0f;

    /*clamping cornerRadius by minimum size*/
    const float r_max = LV_MIN(half_w, half_h);
    if(r > r_max)
        r = r_max;

    /*rectangle*/
    if(r <= 0) {
        lv_vg_lite_path_move_to(path, x, y);
        lv_vg_lite_path_line_to(path, x + w, y);
        lv_vg_lite_path_line_to(path, x + w, y + h);
        lv_vg_lite_path_line_to(path, x, y + h);
        lv_vg_lite_path_close(path);
        LV_PROFILER_DRAW_END;
        return;
    }

    /*circle*/
    if(math_equal(r, half_w) && math_equal(r, half_h)) {
        lv_vg_lite_path_append_circle(path, x + half_w, y + half_h, r, r);
        LV_PROFILER_DRAW_END;
        return;
    }

    /* Get the control point offset for rounded cases */
    const float offset = r * PATH_ARC_MAGIC;

    /* Rounded rectangle case */
    /* Starting point */
    lv_vg_lite_path_move_to(path, x + r, y);

    /* Top side */
    lv_vg_lite_path_line_to(path, x + w - r, y);

    /* Top-right corner */
    lv_vg_lite_path_cubic_to(path, x + w - r + offset, y, x + w, y + r - offset, x + w, y + r);

    /* Right side */
    lv_vg_lite_path_line_to(path, x + w, y + h - r);

    /* Bottom-right corner*/
    lv_vg_lite_path_cubic_to(path, x + w, y + h - r + offset, x + w - r + offset, y + h, x + w - r, y + h);

    /* Bottom side */
    lv_vg_lite_path_line_to(path, x + r, y + h);

    /* Bottom-left corner */
    lv_vg_lite_path_cubic_to(path, x + r - offset, y + h, x, y + h - r + offset, x, y + h - r);

    /* Left side*/
    lv_vg_lite_path_line_to(path, x, y + r);

    /* Top-left corner */
    lv_vg_lite_path_cubic_to(path, x, y + r - offset, x + r - offset, y, x + r, y);

    /* Ending point */
    lv_vg_lite_path_close(path);
    LV_PROFILER_DRAW_END;
}

void lv_vg_lite_path_append_circle(
    lv_vg_lite_path_t * path,
    float cx, float cy,
    float rx, float ry)
{
    LV_PROFILER_DRAW_BEGIN;
    /* https://learn.microsoft.com/zh-cn/xamarin/xamarin-forms/user-interface/graphics/skiasharp/curves/beziers */
    float rx_kappa = rx * PATH_KAPPA;
    float ry_kappa = ry * PATH_KAPPA;

    lv_vg_lite_path_move_to(path, cx, cy - ry);
    lv_vg_lite_path_cubic_to(path, cx + rx_kappa, cy - ry, cx + rx, cy - ry_kappa, cx + rx, cy);
    lv_vg_lite_path_cubic_to(path, cx + rx, cy + ry_kappa, cx + rx_kappa, cy + ry, cx, cy + ry);
    lv_vg_lite_path_cubic_to(path, cx - rx_kappa, cy + ry, cx - rx, cy + ry_kappa, cx - rx, cy);
    lv_vg_lite_path_cubic_to(path, cx - rx, cy - ry_kappa, cx - rx_kappa, cy - ry, cx, cy - ry);
    lv_vg_lite_path_close(path);
    LV_PROFILER_DRAW_END;
}

void lv_vg_lite_path_append_arc_right_angle(lv_vg_lite_path_t * path,
                                            float start_x, float start_y,
                                            float center_x, float center_y,
                                            float end_x, float end_y)
{
    LV_PROFILER_DRAW_BEGIN;
    float dx1 = center_x - start_x;
    float dy1 = center_y - start_y;
    float dx2 = end_x - center_x;
    float dy2 = end_y - center_y;

    float c = SIGN(dx1 * dy2 - dx2 * dy1) * PATH_ARC_MAGIC;

    lv_vg_lite_path_cubic_to(path,
                             start_x - c * dy1, start_y + c * dx1,
                             end_x - c * dy2, end_y + c * dx2,
                             end_x, end_y);
    LV_PROFILER_DRAW_END;
}

void lv_vg_lite_path_append_arc(lv_vg_lite_path_t * path,
                                float cx, float cy,
                                float radius,
                                float start_angle,
                                float sweep,
                                bool pie)
{
    LV_PROFILER_DRAW_BEGIN;
    /* just circle */
    if(sweep >= 360.0f || sweep <= -360.0f) {
        lv_vg_lite_path_append_circle(path, cx, cy, radius, radius);
        LV_PROFILER_DRAW_END;
        return;
    }

    start_angle = MATH_RADIANS(start_angle);
    sweep = MATH_RADIANS(sweep);

    int n_curves = (int)ceil(MATH_FABSF(sweep / MATH_HALF_PI));
    float sweep_sign = sweep < 0 ? -1.f : 1.f;
    float fract = fmodf(sweep, MATH_HALF_PI);
    fract = (math_zero(fract)) ? MATH_HALF_PI * sweep_sign : fract;

    /* Start from here */
    float start_x = radius * MATH_COSF(start_angle);
    float start_y = radius * MATH_SINF(start_angle);

    if(pie) {
        lv_vg_lite_path_move_to(path, cx, cy);
        lv_vg_lite_path_line_to(path, start_x + cx, start_y + cy);
    }

    for(int i = 0; i < n_curves; ++i) {
        float end_angle = start_angle + ((i != n_curves - 1) ? MATH_HALF_PI * sweep_sign : fract);
        float end_x = radius * MATH_COSF(end_angle);
        float end_y = radius * MATH_SINF(end_angle);

        /* variables needed to calculate bezier control points */

        /** get bezier control points using article:
         * (http://itc.ktu.lt/index.php/ITC/article/view/11812/6479)
         */
        float ax = start_x;
        float ay = start_y;
        float bx = end_x;
        float by = end_y;
        float q1 = ax * ax + ay * ay;
        float q2 = ax * bx + ay * by + q1;
        float k2 = (4.0f / 3.0f) * ((MATH_SQRTF(2 * q1 * q2) - q2) / (ax * by - ay * bx));

        /* Next start point is the current end point */
        start_x = end_x;
        start_y = end_y;

        end_x += cx;
        end_y += cy;

        float ctrl1_x = ax - k2 * ay + cx;
        float ctrl1_y = ay + k2 * ax + cy;
        float ctrl2_x = bx + k2 * by + cx;
        float ctrl2_y = by - k2 * bx + cy;

        lv_vg_lite_path_cubic_to(path, ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y, end_x, end_y);
        start_angle = end_angle;
    }

    if(pie) {
        lv_vg_lite_path_close(path);
    }

    LV_PROFILER_DRAW_END;
}

uint8_t lv_vg_lite_vlc_op_arg_len(uint8_t vlc_op)
{
    switch(vlc_op) {
            VLC_OP_ARG_LEN(END, 0);
            VLC_OP_ARG_LEN(CLOSE, 0);
            VLC_OP_ARG_LEN(MOVE, 2);
            VLC_OP_ARG_LEN(MOVE_REL, 2);
            VLC_OP_ARG_LEN(LINE, 2);
            VLC_OP_ARG_LEN(LINE_REL, 2);
            VLC_OP_ARG_LEN(QUAD, 4);
            VLC_OP_ARG_LEN(QUAD_REL, 4);
            VLC_OP_ARG_LEN(CUBIC, 6);
            VLC_OP_ARG_LEN(CUBIC_REL, 6);
            VLC_OP_ARG_LEN(SCCWARC, 5);
            VLC_OP_ARG_LEN(SCCWARC_REL, 5);
            VLC_OP_ARG_LEN(SCWARC, 5);
            VLC_OP_ARG_LEN(SCWARC_REL, 5);
            VLC_OP_ARG_LEN(LCCWARC, 5);
            VLC_OP_ARG_LEN(LCCWARC_REL, 5);
            VLC_OP_ARG_LEN(LCWARC, 5);
            VLC_OP_ARG_LEN(LCWARC_REL, 5);
        default:
            LV_ASSERT_FORMAT_MSG(false, "Invalid op code: %d", vlc_op);
            break;
    }

    return 0;
}

uint8_t lv_vg_lite_path_format_len(vg_lite_format_t format)
{
    switch(format) {
        case VG_LITE_FP32:
            return sizeof(float);
        case VG_LITE_S32:
            return sizeof(int32_t);
        case VG_LITE_S16:
            return sizeof(int16_t);
        case VG_LITE_S8:
            return sizeof(int8_t);
        default:
            LV_ASSERT_FORMAT_MSG(false, "Invalid format: %d", format);
            break;
    }

    return 0;
}

void lv_vg_lite_path_for_each_data(const vg_lite_path_t * path, lv_vg_lite_path_iter_cb_t cb, void * user_data)
{
    LV_ASSERT_NULL(path);
    LV_ASSERT_NULL(cb);

    uint8_t fmt_len = lv_vg_lite_path_format_len(path->format);
    uint8_t * cur = path->path;
    uint8_t * end = cur + path->path_length;
    float tmp_data[8];

    while(cur < end) {
        /* get op code */
        uint8_t op_code = LV_VG_LITE_PATH_GET_OP_CODE(cur);

        /* get arguments length */
        uint8_t arg_len = lv_vg_lite_vlc_op_arg_len(op_code);

        /* skip op code */
        cur += fmt_len;

        /* print arguments */
        for(uint8_t i = 0; i < arg_len; i++) {
            switch(path->format) {
                case VG_LITE_S8:
                    tmp_data[i] = *((int8_t *)cur);
                    break;
                case VG_LITE_S16:
                    tmp_data[i] = *((int16_t *)cur);
                    break;
                case VG_LITE_S32:
                    tmp_data[i] = *((int32_t *)cur);
                    break;
                case VG_LITE_FP32:
                    tmp_data[i] = *((float *)cur);
                    break;
                default:
                    LV_ASSERT_FORMAT_MSG(false, "Invalid format: %d", path->format);
                    break;
            }

            cur += fmt_len;
        }

        cb(user_data, op_code, tmp_data, arg_len);
    }
}

void lv_vg_lite_path_append_path(lv_vg_lite_path_t * dest, const lv_vg_lite_path_t * src)
{
    LV_ASSERT_NULL(dest);
    LV_ASSERT_NULL(src);

    LV_ASSERT(dest->base.format == dest->base.format);
    lv_vg_lite_path_append_data(dest, src->base.path, src->base.path_length);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

#endif /*LV_USE_DRAW_VG_LITE*/
