/**
* @file lv_draw_vector.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "lv_draw_vector_private.h"
#include "../misc/lv_area_private.h"
#include "lv_draw_private.h"

#if LV_USE_VECTOR_GRAPHIC

#if !((LV_USE_DRAW_SW && LV_USE_THORVG) || LV_USE_DRAW_VG_LITE)
    #error "LV_USE_VECTOR_GRAPHIC requires either (LV_USE_DRAW_SW and LV_USE_THORVG) or LV_USE_DRAW_VG_LITE"
#endif

#include "../misc/lv_ll.h"
#include "../misc/lv_types.h"
#include "../stdlib/lv_string.h"
#include <math.h>
#include <float.h>

#define EPSILON 1e-6f
#define MATH_PI  3.14159265358979323846f
#define MATH_HALF_PI 1.57079632679489661923f

#define DEG_TO_RAD 0.017453292519943295769236907684886f
#define RAD_TO_DEG 57.295779513082320876798154814105f

#define MATH_RADIANS(deg) ((deg) * DEG_TO_RAD)
#define MATH_DEGREES(rad) ((rad) * RAD_TO_DEG)

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

#ifndef M_PI
    #define M_PI 3.1415926f
#endif

#define CHECK_AND_RESIZE_PATH_CONTAINER(P, N) \
    do { \
        if ((lv_array_size(&(P)->ops) + (N)) > lv_array_capacity(&(P)->ops)) { \
            lv_array_resize(&(P)->ops, ((P)->ops.capacity << 1)); \
        } \
        if ((lv_array_size(&(P)->points) + (N)) > lv_array_capacity(&(P)->points)) { \
            lv_array_resize(&(P)->points, ((P)->points.capacity << 1)); \
        } \
    } while(0)

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

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

static void _copy_draw_dsc(lv_vector_path_ctx_t * dst, const lv_vector_path_ctx_t * src)
{
    lv_memcpy(&(dst->fill_dsc), &(src->fill_dsc), sizeof(lv_vector_fill_dsc_t));

    dst->stroke_dsc.style = src->stroke_dsc.style;
    dst->stroke_dsc.color = src->stroke_dsc.color;
    dst->stroke_dsc.opa = src->stroke_dsc.opa;
    dst->stroke_dsc.width = src->stroke_dsc.width;
    dst->stroke_dsc.cap = src->stroke_dsc.cap;
    dst->stroke_dsc.join = src->stroke_dsc.join;
    dst->stroke_dsc.miter_limit = src->stroke_dsc.miter_limit;
    lv_array_copy(&(dst->stroke_dsc.dash_pattern), &(src->stroke_dsc.dash_pattern));
    lv_memcpy(&(dst->stroke_dsc.gradient), &(src->stroke_dsc.gradient), sizeof(lv_vector_gradient_t));
    lv_memcpy(&(dst->stroke_dsc.matrix), &(src->stroke_dsc.matrix), sizeof(lv_matrix_t));

    dst->blend_mode = src->blend_mode;
    lv_memcpy(&(dst->matrix), &(src->matrix), sizeof(lv_matrix_t));
    lv_area_copy(&(dst->scissor_area), &(src->scissor_area));
}


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

void lv_matrix_transform_point(const lv_matrix_t * matrix, lv_fpoint_t * point)
{
    float x = point->x;
    float y = point->y;

    point->x = x * matrix->m[0][0] + y * matrix->m[0][1] + matrix->m[0][2];
    point->y = x * matrix->m[1][0] + y * matrix->m[1][1] + matrix->m[1][2];
}

void lv_matrix_transform_path(const lv_matrix_t * matrix, lv_vector_path_t * path)
{
    lv_fpoint_t * pt = lv_array_front(&path->points);
    uint32_t size = lv_array_size(&path->points);
    for(uint32_t i = 0; i < size; i++) {
        lv_matrix_transform_point(matrix, &pt[i]);
    }
}

/* path functions */
lv_vector_path_t * lv_vector_path_create(lv_vector_path_quality_t quality)
{
    lv_vector_path_t * path = lv_malloc(sizeof(lv_vector_path_t));
    LV_ASSERT_MALLOC(path);
    lv_memzero(path, sizeof(lv_vector_path_t));
    path->quality = quality;
    lv_array_init(&path->ops, 8, sizeof(lv_vector_path_op_t));
    lv_array_init(&path->points, 8, sizeof(lv_fpoint_t));
    return path;
}

void lv_vector_path_copy(lv_vector_path_t * target_path, const lv_vector_path_t * path)
{
    target_path->quality = path->quality;
    lv_array_copy(&target_path->ops, &path->ops);
    lv_array_copy(&target_path->points, &path->points);
}

void lv_vector_path_clear(lv_vector_path_t * path)
{
    lv_array_clear(&path->ops);
    lv_array_clear(&path->points);
}

void lv_vector_path_delete(lv_vector_path_t * path)
{
    lv_array_deinit(&path->ops);
    lv_array_deinit(&path->points);
    lv_free(path);
}

void lv_vector_path_move_to(lv_vector_path_t * path, const lv_fpoint_t * p)
{
    CHECK_AND_RESIZE_PATH_CONTAINER(path, 1);

    lv_vector_path_op_t op = LV_VECTOR_PATH_OP_MOVE_TO;
    lv_array_push_back(&path->ops, &op);
    lv_array_push_back(&path->points, p);
}

void lv_vector_path_line_to(lv_vector_path_t * path, const lv_fpoint_t * p)
{
    if(lv_array_is_empty(&path->ops)) {
        /*first op must be move_to*/
        return;
    }

    CHECK_AND_RESIZE_PATH_CONTAINER(path, 1);

    lv_vector_path_op_t op = LV_VECTOR_PATH_OP_LINE_TO;
    lv_array_push_back(&path->ops, &op);
    lv_array_push_back(&path->points, p);
}

void lv_vector_path_quad_to(lv_vector_path_t * path, const lv_fpoint_t * p1, const lv_fpoint_t * p2)
{
    if(lv_array_is_empty(&path->ops)) {
        /*first op must be move_to*/
        return;
    }

    CHECK_AND_RESIZE_PATH_CONTAINER(path, 2);

    lv_vector_path_op_t op = LV_VECTOR_PATH_OP_QUAD_TO;
    lv_array_push_back(&path->ops, &op);
    lv_array_push_back(&path->points, p1);
    lv_array_push_back(&path->points, p2);
}

void lv_vector_path_cubic_to(lv_vector_path_t * path, const lv_fpoint_t * p1, const lv_fpoint_t * p2,
                             const lv_fpoint_t * p3)
{
    if(lv_array_is_empty(&path->ops)) {
        /*first op must be move_to*/
        return;
    }

    CHECK_AND_RESIZE_PATH_CONTAINER(path, 3);

    lv_vector_path_op_t op = LV_VECTOR_PATH_OP_CUBIC_TO;
    lv_array_push_back(&path->ops, &op);
    lv_array_push_back(&path->points, p1);
    lv_array_push_back(&path->points, p2);
    lv_array_push_back(&path->points, p3);
}

static lv_fpoint_t _point_on_ellipse(float rx, float ry, float cos_r, float sin_r,
                                     float cx, float cy, float theta, float alpha)
{
    float cos_theta = cosf(theta);
    float sin_theta = sinf(theta);

    float x = rx * cos_theta;
    float y = ry * sin_theta;

    float x_rot = cos_r * x - sin_r * y;
    float y_rot = sin_r * x + cos_r * y;

    if(fabsf(alpha) > EPSILON) {
        float dx = -rx * sin_theta;
        float dy = ry * cos_theta;
        float dx_rot = cos_r * dx - sin_r * dy;
        float dy_rot = sin_r * dx + cos_r * dy;

        x_rot += alpha * dx_rot;
        y_rot += alpha * dy_rot;
    }

    return (lv_fpoint_t) {
        x_rot + cx, y_rot + cy
    };
}

void lv_vector_path_arc_to(lv_vector_path_t * path, float rx, float ry, float rotate_angle, bool large_arc,
                           bool clockwise, const lv_fpoint_t * p)
{
    LV_ASSERT_NULL(path);
    LV_ASSERT_NULL(p);

    if(lv_array_is_empty(&path->ops)) {
        /*first op must be move_to*/
        return;
    }

    if(rx <= 0 || ry <= 0) {
        /*no needed to draw*/
        return;
    }

    /*https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes*/

    lv_fpoint_t * cpt = lv_array_back(&path->points);

    float x0 = cpt->x;
    float y0 = cpt->y;

    /*1. dealing with degradation*/
    if(fabsf(x0 - p->x) < EPSILON && fabsf(y0 - p->y) < EPSILON) {
        /*same point*/
        return;
    }

    float rotate = MATH_RADIANS(rotate_angle);
    float sin_r = sinf(rotate);
    float cos_r = cosf(rotate);

    /*2. transform point*/
    float dx = (x0 - p->x) * 0.5f;
    float dy = (y0 - p->y) * 0.5f;

    float x1 = cos_r * dx + sin_r * dy;
    float y1 = -sin_r * dx + cos_r * dy;

    /*3. adjust radius*/
    float lambda_val = (x1 * x1) / (rx * rx) + (y1 * y1) / (ry * ry);
    if(lambda_val > 1.0f) {
        rx *= sqrtf(lambda_val);
        ry *= sqrtf(lambda_val);
    }

    /*4. calc center point*/
    float rx_sq = rx * rx;
    float ry_sq = ry * ry;
    float x1_sq = x1 * x1;
    float y1_sq = y1 * y1;

    float num = rx_sq * ry_sq - rx_sq * y1_sq - ry_sq * x1_sq;
    float denom = rx_sq * y1_sq + ry_sq * x1_sq;

    float radicand = (denom > EPSILON) ? num / denom : 0.0f;
    if(radicand < 0.0f) radicand = 0.0f;

    float sign = (large_arc == clockwise) ? -1.0f : 1.0f;
    float coef = sign * sqrtf(radicand);

    float cx_prime = (coef * rx * y1) / ry;
    float cy_prime = -(coef * ry * x1) / rx;

    float cx = cos_r * cx_prime - sin_r * cy_prime + (x0 + p->x) * 0.5f;
    float cy = sin_r * cx_prime + cos_r * cy_prime + (y0 + p->y) * 0.5f;

    float ux = (x1 - cx_prime) / rx;
    float uy = (y1 - cy_prime) / ry;

    /*5. calculate the starting angle and ending angle*/
    float n_sq = ux * ux + uy * uy;
    float theta1 = 0.0f;
    if(n_sq > EPSILON) {
        theta1 = atan2f(uy, ux);
    }

    float vx = (-x1 - cx_prime) / rx;
    float vy = (-y1 - cy_prime) / ry;

    float n = sqrtf(n_sq * (vx * vx + vy * vy));
    float delta = 0.0f;
    if(n > EPSILON) {
        float cos_delta = (ux * vx + uy * vy) / n;
        if(cos_delta > 1.0f) cos_delta = 1.0f;
        else if(cos_delta < -1.0f) cos_delta = -1.0f;

        delta = acosf(cos_delta);
        if(ux * vy - uy * vx < 0.0f) delta = -delta;
    }

    if(!clockwise && delta > 0.0f) {
        delta -= 2.0f * MATH_PI;
    }
    else if(clockwise && delta < 0.0f) {
        delta += 2.0f * MATH_PI;
    }

    /*6. split arc into segments within 90 degrees*/
    float angle_left = fabsf(delta);
    int seg_count = (int)ceilf(angle_left / MATH_HALF_PI);
    if(seg_count == 0) seg_count = 1;

    float segment_angle = delta / (float)seg_count;

    float current_angle = theta1;
    for(int i = 0; i < seg_count; i++) {
        float next_angle = current_angle + segment_angle;

        float alpha_val;
        if(fabsf(segment_angle) < 0.1f) {
            alpha_val = segment_angle / 6.0f;
        }
        else {
            float tan_half = tanf(segment_angle * 0.5f);
            alpha_val = sinf(segment_angle) * (sqrtf(4.0f + 3.0f * tan_half * tan_half) - 1.0f) / 3.0f;
        }

        lv_fpoint_t p1 = _point_on_ellipse(rx, ry, cos_r, sin_r, cx, cy, current_angle, alpha_val);
        lv_fpoint_t p2 = _point_on_ellipse(rx, ry, cos_r, sin_r, cx, cy, next_angle, -alpha_val);
        lv_fpoint_t p3 = _point_on_ellipse(rx, ry, cos_r, sin_r, cx, cy, next_angle, 0.0f);

        lv_vector_path_cubic_to(path, &p1, &p2, &p3);

        current_angle = next_angle;
    }
}

void lv_vector_path_close(lv_vector_path_t * path)
{
    if(lv_array_is_empty(&path->ops)) {
        /*first op must be move_to*/
        return;
    }

    CHECK_AND_RESIZE_PATH_CONTAINER(path, 1);

    lv_vector_path_op_t op = LV_VECTOR_PATH_OP_CLOSE;
    lv_array_push_back(&path->ops, &op);
}

void lv_vector_path_get_bounding(const lv_vector_path_t * path, lv_area_t * area)
{
    LV_ASSERT_NULL(path);
    LV_ASSERT_NULL(area);

    uint32_t len = lv_array_size(&path->points);
    if(len == 0) {
        lv_memzero(area, sizeof(lv_area_t));
        return;
    }

    lv_fpoint_t * p = lv_array_front(&path->points);
    float x1 = p[0].x;
    float x2 = p[0].x;
    float y1 = p[0].y;
    float y2 = p[0].y;

    for(uint32_t i = 1; i < len; i++) {
        if(p[i].x < x1) x1 = p[i].x;
        if(p[i].y < y1) y1 = p[i].y;
        if(p[i].x > x2) x2 = p[i].x;
        if(p[i].y > y2) y2 = p[i].y;
    }

    area->x1 = lroundf(x1);
    area->y1 = lroundf(y1);
    area->x2 = lroundf(x2);
    area->y2 = lroundf(y2);
}

void lv_vector_path_append_rectangle(lv_vector_path_t * path, float x, float y, float w, float h, float rx, float ry)
{
    if(w <= 0.0f || h <= 0.0f) return;

    float hw = w * 0.5f;
    float hh = h * 0.5f;

    if(rx > hw) rx = hw;
    if(ry > hh) ry = hh;

    if(rx <= 0.0f && ry <= 0.0f) {
        lv_fpoint_t pt = {x, y};
        lv_vector_path_move_to(path, &pt);
        pt.x += w;
        lv_vector_path_line_to(path, &pt);
        pt.y += h;
        lv_vector_path_line_to(path, &pt);
        pt.x -= w;
        lv_vector_path_line_to(path, &pt);
        lv_vector_path_close(path);
        return;
    }

    if(rx == hw && ry == hh) {
        lv_fpoint_t pt = {x + hw, y + hh};
        lv_vector_path_append_circle(path, &pt, rx, ry);
        return;
    }

    float hrx = rx * 0.5f;
    float hry = ry * 0.5f;
    lv_fpoint_t pt, pt2, pt3;

    pt.x = x + rx;
    pt.y = y;
    lv_vector_path_move_to(path, &pt);

    pt.x = x + w - rx;
    pt.y = y;
    lv_vector_path_line_to(path, &pt);

    pt.x = x + w - rx + hrx;
    pt.y = y;
    pt2.x = x + w;
    pt2.y = y + ry - hry;
    pt3.x = x + w;
    pt3.y = y + ry;
    lv_vector_path_cubic_to(path, &pt, &pt2, &pt3);

    pt.x = x + w;
    pt.y = y + h - ry;
    lv_vector_path_line_to(path, &pt);

    pt.x = x + w;
    pt.y = y + h - ry + hry;
    pt2.x = x + w - rx + hrx;
    pt2.y = y + h;
    pt3.x = x + w - rx;
    pt3.y = y + h;
    lv_vector_path_cubic_to(path, &pt, &pt2, &pt3);

    pt.x = x + rx;
    pt.y = y + h;
    lv_vector_path_line_to(path, &pt);

    pt.x = x + rx - hrx;
    pt.y = y + h;
    pt2.x = x;
    pt2.y = y + h - ry + hry;
    pt3.x = x;
    pt3.y = y + h - ry;
    lv_vector_path_cubic_to(path, &pt, &pt2, &pt3);

    pt.x = x;
    pt.y = y + ry;
    lv_vector_path_line_to(path, &pt);

    pt.x = x;
    pt.y = y + ry - hry;
    pt2.x = x + rx - hrx;
    pt2.y = y;
    pt3.x = x + rx;
    pt3.y = y;
    lv_vector_path_cubic_to(path, &pt, &pt2, &pt3);

    lv_vector_path_close(path);
}

void lv_vector_path_append_circle(lv_vector_path_t * path, const lv_fpoint_t * c, float rx, float ry)
{
    float krx = rx * 0.552284f;
    float kry = ry * 0.552284f;
    float cx = c->x;
    float cy = c->y;

    lv_fpoint_t pt, pt2, pt3;
    pt.x = cx;
    pt.y = cy - ry;
    lv_vector_path_move_to(path, &pt);

    pt.x = cx + krx;
    pt.y = cy - ry;
    pt2.x = cx + rx;
    pt2.y = cy - kry;
    pt3.x = cx + rx;
    pt3.y = cy;
    lv_vector_path_cubic_to(path, &pt, &pt2, &pt3);

    pt.x = cx + rx;
    pt.y = cy + kry;
    pt2.x = cx + krx;
    pt2.y = cy + ry;
    pt3.x = cx;
    pt3.y = cy + ry;
    lv_vector_path_cubic_to(path, &pt, &pt2, &pt3);

    pt.x = cx - krx;
    pt.y = cy + ry;
    pt2.x = cx - rx;
    pt2.y = cy + kry;
    pt3.x = cx - rx;
    pt3.y = cy;
    lv_vector_path_cubic_to(path, &pt, &pt2, &pt3);

    pt.x = cx - rx;
    pt.y = cy - kry;
    pt2.x = cx - krx;
    pt2.y = cy - ry;
    pt3.x = cx;
    pt3.y = cy - ry;
    lv_vector_path_cubic_to(path, &pt, &pt2, &pt3);

    lv_vector_path_close(path);
}

/**
 * Add a arc to the path
 * @param path              pointer to a path
 * @param c                 pointer to a `lv_fpoint_t` variable for center of the circle
 * @param radius            the radius for arc
 * @param start_angle       the start angle for arc
 * @param sweep             the sweep angle for arc, could be negative
 * @param pie               true: draw a pie, false: draw a arc
 */
void lv_vector_path_append_arc(lv_vector_path_t * path, const lv_fpoint_t * c, float radius, float start_angle,
                               float sweep, bool pie)
{
    float cx = c->x;
    float cy = c->y;

    /* just circle */
    if(sweep >= 360.0f || sweep <= -360.0f) {
        lv_vector_path_append_circle(path, c, radius, radius);
        return;
    }

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

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

    /* Start from here */
    lv_fpoint_t start = {
        .x = radius * cosf(start_angle),
        .y = radius * sinf(start_angle),
    };

    if(pie) {
        lv_vector_path_move_to(path, &(lv_fpoint_t) {
            cx, cy
        });
        lv_vector_path_line_to(path, &(lv_fpoint_t) {
            start.x + cx, start.y + cy
        });
    }
    else {
        lv_vector_path_move_to(path, &(lv_fpoint_t) {
            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 * cosf(end_angle);
        float end_y = radius * 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) * ((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;

        lv_fpoint_t ctrl1 = {ax - k2 * ay + cx, ay + k2 * ax + cy};
        lv_fpoint_t ctrl2 = {bx + k2 * by + cx, by - k2 * bx + cy};
        lv_fpoint_t end = {end_x, end_y};
        lv_vector_path_cubic_to(path, &ctrl1, &ctrl2, &end);
        start_angle = end_angle;
    }

    if(pie) {
        lv_vector_path_close(path);
    }
}

void lv_vector_path_append_path(lv_vector_path_t * path, const lv_vector_path_t * subpath)
{
    uint32_t ops_size = lv_array_size(&path->ops);
    uint32_t nops_size = lv_array_size(&subpath->ops);
    uint32_t point_size = lv_array_size(&path->points);
    uint32_t npoint_size = lv_array_size(&subpath->points);

    lv_array_concat(&path->ops, &subpath->ops);
    path->ops.size = ops_size + nops_size;

    lv_array_concat(&path->points, &subpath->points);
    path->points.size = point_size + npoint_size;
}

/* draw dsc functions */

lv_draw_vector_dsc_t * lv_draw_vector_dsc_create(lv_layer_t * layer)
{

    lv_draw_vector_dsc_t * dsc = lv_zalloc(sizeof(lv_draw_vector_dsc_t));
    LV_ASSERT_MALLOC(dsc);
    if(dsc == NULL) {
        LV_LOG_WARN("Couldn't allocate lv_draw_vector_dsc_t");
        return NULL;
    }

    dsc->base.layer = layer;

    dsc->ctx = lv_zalloc(sizeof(lv_vector_path_ctx_t));
    if(dsc->ctx == NULL) {
        LV_LOG_WARN("Couldn't allocate vector path context");
        lv_free(dsc);
        return NULL;
    }

    lv_vector_fill_dsc_t * fill_dsc = &(dsc->ctx->fill_dsc);
    fill_dsc->style = LV_VECTOR_DRAW_STYLE_SOLID;
    fill_dsc->color = lv_color_to_32(lv_color_black(), 0xFF);
    fill_dsc->opa = LV_OPA_COVER;
    fill_dsc->fill_rule = LV_VECTOR_FILL_NONZERO;
    lv_matrix_identity(&(fill_dsc->matrix)); /*identity matrix*/

    lv_vector_stroke_dsc_t * stroke_dsc = &(dsc->ctx->stroke_dsc);
    stroke_dsc->style = LV_VECTOR_DRAW_STYLE_SOLID;
    stroke_dsc->color = lv_color_to_32(lv_color_black(), 0xFF);
    stroke_dsc->opa = LV_OPA_0; /*default no stroke*/
    stroke_dsc->width = 1.0f;
    stroke_dsc->cap = LV_VECTOR_STROKE_CAP_BUTT;
    stroke_dsc->join = LV_VECTOR_STROKE_JOIN_MITER;
    stroke_dsc->miter_limit = 4.0f;
    lv_matrix_identity(&(stroke_dsc->matrix)); /*identity matrix*/

    dsc->ctx->blend_mode = LV_VECTOR_BLEND_SRC_OVER;
    dsc->ctx->scissor_area = layer->_clip_area;
    lv_matrix_identity(&(dsc->ctx->matrix)); /*identity matrix*/
    dsc->task_list = NULL;
    return dsc;
}

void lv_draw_vector_dsc_delete(lv_draw_vector_dsc_t * dsc)
{
    if(dsc->task_list) {
        lv_ll_t * task_list = dsc->task_list;
        lv_vector_for_each_destroy_tasks(task_list, NULL, NULL);
        dsc->task_list = NULL;
    }
    lv_array_deinit(&(dsc->ctx->stroke_dsc.dash_pattern));
    lv_free(dsc->ctx);
    lv_free(dsc);
}

void lv_draw_vector_dsc_set_blend_mode(lv_draw_vector_dsc_t * dsc, lv_vector_blend_t blend)
{
    dsc->ctx->blend_mode = blend;
}

void lv_draw_vector_dsc_set_transform(lv_draw_vector_dsc_t * dsc, const lv_matrix_t * matrix)
{
    lv_memcpy(&(dsc->ctx->matrix), matrix, sizeof(lv_matrix_t));
}

void lv_draw_vector_dsc_set_fill_color(lv_draw_vector_dsc_t * dsc, lv_color_t color)
{
    dsc->ctx->fill_dsc.style = LV_VECTOR_DRAW_STYLE_SOLID;
    dsc->ctx->fill_dsc.color = lv_color_to_32(color, 0xFF);
}

void lv_draw_vector_dsc_set_fill_color32(lv_draw_vector_dsc_t * dsc, lv_color32_t color)
{
    dsc->ctx->fill_dsc.style = LV_VECTOR_DRAW_STYLE_SOLID;
    dsc->ctx->fill_dsc.color = color;
}

void lv_draw_vector_dsc_set_fill_opa(lv_draw_vector_dsc_t * dsc, lv_opa_t opa)
{
    dsc->ctx->fill_dsc.opa = opa;
}

void lv_draw_vector_dsc_set_fill_rule(lv_draw_vector_dsc_t * dsc, lv_vector_fill_t rule)
{
    dsc->ctx->fill_dsc.fill_rule = rule;
}

void lv_draw_vector_dsc_set_fill_units(lv_draw_vector_dsc_t * dsc, const lv_vector_fill_units_t units)
{
    dsc->ctx->fill_dsc.fill_units = units;
}

void lv_draw_vector_dsc_set_fill_image(lv_draw_vector_dsc_t * dsc, const lv_draw_image_dsc_t * img_dsc)
{
    dsc->ctx->fill_dsc.style = LV_VECTOR_DRAW_STYLE_PATTERN;
    lv_memcpy(&(dsc->ctx->fill_dsc.img_dsc), img_dsc, sizeof(lv_draw_image_dsc_t));
}

void lv_draw_vector_dsc_set_fill_linear_gradient(lv_draw_vector_dsc_t * dsc, float x1, float y1, float x2, float y2)
{
    dsc->ctx->fill_dsc.style = LV_VECTOR_DRAW_STYLE_GRADIENT;
    dsc->ctx->fill_dsc.gradient.style = LV_VECTOR_GRADIENT_STYLE_LINEAR;
    dsc->ctx->fill_dsc.gradient.x1 = x1;
    dsc->ctx->fill_dsc.gradient.y1 = y1;
    dsc->ctx->fill_dsc.gradient.x2 = x2;
    dsc->ctx->fill_dsc.gradient.y2 = y2;
}

void lv_draw_vector_dsc_set_fill_radial_gradient(lv_draw_vector_dsc_t * dsc, float cx, float cy, float radius)
{
    dsc->ctx->fill_dsc.style = LV_VECTOR_DRAW_STYLE_GRADIENT;
    dsc->ctx->fill_dsc.gradient.style = LV_VECTOR_GRADIENT_STYLE_RADIAL;
    dsc->ctx->fill_dsc.gradient.cx = cx;
    dsc->ctx->fill_dsc.gradient.cy = cy;
    dsc->ctx->fill_dsc.gradient.cr = radius;
}

void lv_draw_vector_dsc_set_fill_gradient_spread(lv_draw_vector_dsc_t * dsc, lv_vector_gradient_spread_t spread)
{
    dsc->ctx->fill_dsc.gradient.spread = spread;
}

void lv_draw_vector_dsc_set_fill_gradient_color_stops(lv_draw_vector_dsc_t * dsc, const lv_grad_stop_t * stops,
                                                      uint16_t count)
{
    if(count > LV_GRADIENT_MAX_STOPS) {
        LV_LOG_WARN("Gradient stops limited: %d, max: %d", count, LV_GRADIENT_MAX_STOPS);
        count = LV_GRADIENT_MAX_STOPS;
    }

    lv_memcpy(&(dsc->ctx->fill_dsc.gradient.stops), stops, sizeof(lv_grad_stop_t) * count);
    dsc->ctx->fill_dsc.gradient.stops_count = count;
}

void lv_draw_vector_dsc_set_fill_transform(lv_draw_vector_dsc_t * dsc, const lv_matrix_t * matrix)
{
    lv_memcpy(&(dsc->ctx->fill_dsc.matrix), matrix, sizeof(lv_matrix_t));
}

void lv_draw_vector_dsc_set_stroke_transform(lv_draw_vector_dsc_t * dsc, const lv_matrix_t * matrix)
{
    lv_memcpy(&(dsc->ctx->stroke_dsc.matrix), matrix, sizeof(lv_matrix_t));
}

void lv_draw_vector_dsc_set_stroke_color32(lv_draw_vector_dsc_t * dsc, lv_color32_t color)
{
    dsc->ctx->stroke_dsc.style = LV_VECTOR_DRAW_STYLE_SOLID;
    dsc->ctx->stroke_dsc.color = color;
}

void lv_draw_vector_dsc_set_stroke_color(lv_draw_vector_dsc_t * dsc, lv_color_t color)
{
    dsc->ctx->stroke_dsc.style = LV_VECTOR_DRAW_STYLE_SOLID;
    dsc->ctx->stroke_dsc.color = lv_color_to_32(color, 0xFF);
}

void lv_draw_vector_dsc_set_stroke_opa(lv_draw_vector_dsc_t * dsc, lv_opa_t opa)
{
    dsc->ctx->stroke_dsc.opa = opa;
}

void lv_draw_vector_dsc_set_stroke_width(lv_draw_vector_dsc_t * dsc, float width)
{
    dsc->ctx->stroke_dsc.width = width;
}

void lv_draw_vector_dsc_set_stroke_dash(lv_draw_vector_dsc_t * dsc, float * dash_pattern, uint16_t dash_count)
{
    lv_array_t * dash_array = &(dsc->ctx->stroke_dsc.dash_pattern);
    if(dash_pattern) {
        lv_array_clear(dash_array);
        if(lv_array_capacity(dash_array) == 0) {
            lv_array_init(dash_array, dash_count, sizeof(float));
        }
        else {
            lv_array_resize(dash_array, dash_count);
        }
        for(uint16_t i = 0; i < dash_count; i++) {
            lv_array_push_back(dash_array, &dash_pattern[i]);
        }
    }
    else {   /*clear dash*/
        lv_array_clear(dash_array);
    }
}

void lv_draw_vector_dsc_set_stroke_cap(lv_draw_vector_dsc_t * dsc, lv_vector_stroke_cap_t cap)
{
    dsc->ctx->stroke_dsc.cap = cap;
}

void lv_draw_vector_dsc_set_stroke_join(lv_draw_vector_dsc_t * dsc, lv_vector_stroke_join_t join)
{
    dsc->ctx->stroke_dsc.join = join;
}

void lv_draw_vector_dsc_set_stroke_miter_limit(lv_draw_vector_dsc_t * dsc, uint16_t miter_limit)
{
    dsc->ctx->stroke_dsc.miter_limit = miter_limit;
}

void lv_draw_vector_dsc_set_stroke_linear_gradient(lv_draw_vector_dsc_t * dsc, float x1, float y1, float x2, float y2)
{
    dsc->ctx->stroke_dsc.style = LV_VECTOR_DRAW_STYLE_GRADIENT;
    dsc->ctx->stroke_dsc.gradient.style = LV_VECTOR_GRADIENT_STYLE_LINEAR;
    dsc->ctx->stroke_dsc.gradient.x1 = x1;
    dsc->ctx->stroke_dsc.gradient.y1 = y1;
    dsc->ctx->stroke_dsc.gradient.x2 = x2;
    dsc->ctx->stroke_dsc.gradient.y2 = y2;
}

void lv_draw_vector_dsc_set_stroke_radial_gradient(lv_draw_vector_dsc_t * dsc, float cx, float cy, float radius)
{
    dsc->ctx->stroke_dsc.style = LV_VECTOR_DRAW_STYLE_GRADIENT;
    dsc->ctx->stroke_dsc.gradient.style = LV_VECTOR_GRADIENT_STYLE_RADIAL;
    dsc->ctx->stroke_dsc.gradient.cx = cx;
    dsc->ctx->stroke_dsc.gradient.cy = cy;
    dsc->ctx->stroke_dsc.gradient.cr = radius;
}

void lv_draw_vector_dsc_set_stroke_gradient_spread(lv_draw_vector_dsc_t * dsc, lv_vector_gradient_spread_t spread)
{
    dsc->ctx->stroke_dsc.gradient.spread = spread;
}

void lv_draw_vector_dsc_set_stroke_gradient_color_stops(lv_draw_vector_dsc_t * dsc, const lv_grad_stop_t * stops,
                                                        uint16_t count)
{
    if(count > LV_GRADIENT_MAX_STOPS) {
        LV_LOG_WARN("Gradient stops limited: %d, max: %d", count, LV_GRADIENT_MAX_STOPS);
        count = LV_GRADIENT_MAX_STOPS;
    }

    lv_memcpy(&(dsc->ctx->stroke_dsc.gradient.stops), stops, sizeof(lv_grad_stop_t) * count);
    dsc->ctx->stroke_dsc.gradient.stops_count = count;
}

/* draw functions */
void lv_draw_vector_dsc_add_path(lv_draw_vector_dsc_t * dsc, const lv_vector_path_t * path)
{
    lv_area_t rect;
    if(!lv_area_intersect(&rect, &(dsc->base.layer->_clip_area), &(dsc->ctx->scissor_area))) {
        return;
    }

    if(dsc->ctx->fill_dsc.opa == 0
       && dsc->ctx->stroke_dsc.opa == 0) {
        return;
    }

    if(!dsc->task_list) {
        dsc->task_list = lv_malloc(sizeof(lv_ll_t));
        LV_ASSERT_MALLOC(dsc->task_list);
        lv_ll_init(dsc->task_list, sizeof(lv_draw_vector_subtask_t));
    }

    lv_draw_vector_subtask_t * new_task = (lv_draw_vector_subtask_t *)lv_ll_ins_tail(dsc->task_list);
    lv_memset(new_task, 0, sizeof(lv_draw_vector_subtask_t));

    new_task->path = lv_vector_path_create(0);

    _copy_draw_dsc(&(new_task->ctx), dsc->ctx);
    lv_vector_path_copy(new_task->path, path);
    new_task->ctx.scissor_area = rect;
}

void lv_draw_vector_dsc_clear_area(lv_draw_vector_dsc_t * dsc, const lv_area_t * rect)
{
    lv_area_t r;
    if(!lv_area_intersect(&r, &(dsc->base.layer->_clip_area), &(dsc->ctx->scissor_area))) {
        return;
    }

    lv_area_t final_rect;
    if(!lv_area_intersect(&final_rect, &r, rect)) {
        return;
    }

    if(!dsc->task_list) {
        dsc->task_list = lv_malloc(sizeof(lv_ll_t));
        LV_ASSERT_MALLOC(dsc->task_list);
        lv_ll_init(dsc->task_list, sizeof(lv_draw_vector_subtask_t));
    }

    lv_draw_vector_subtask_t * new_task = (lv_draw_vector_subtask_t *)lv_ll_ins_tail(dsc->task_list);
    lv_memset(new_task, 0, sizeof(lv_draw_vector_subtask_t));

    new_task->ctx.fill_dsc.color = dsc->ctx->fill_dsc.color;
    new_task->ctx.fill_dsc.opa = dsc->ctx->fill_dsc.opa;
    lv_area_copy(&(new_task->ctx.scissor_area), &final_rect);
}

void lv_draw_vector(lv_draw_vector_dsc_t * dsc)
{
    if(!dsc->task_list) {
        return;
    }

    lv_layer_t * layer = dsc->base.layer;

    lv_draw_task_t * t = lv_draw_add_task(layer, &(layer->_clip_area), LV_DRAW_TASK_TYPE_VECTOR);
    lv_memcpy(t->draw_dsc, dsc, sizeof(lv_draw_vector_dsc_t));
    lv_draw_finalize_task_creation(layer, t);
    dsc->task_list = NULL;
}

/* draw dsc transform */
void lv_draw_vector_dsc_identity(lv_draw_vector_dsc_t * dsc)
{
    lv_matrix_identity(&(dsc->ctx->matrix)); /*identity matrix*/
}

void lv_draw_vector_dsc_scale(lv_draw_vector_dsc_t * dsc, float scale_x, float scale_y)
{
    lv_matrix_scale(&(dsc->ctx->matrix), scale_x, scale_y);
}

void lv_draw_vector_dsc_rotate(lv_draw_vector_dsc_t * dsc, float degree)
{
    lv_matrix_rotate(&(dsc->ctx->matrix), degree);
}

void lv_draw_vector_dsc_translate(lv_draw_vector_dsc_t * dsc, float tx, float ty)
{
    lv_matrix_translate(&(dsc->ctx->matrix), tx, ty);
}

void lv_draw_vector_dsc_skew(lv_draw_vector_dsc_t * dsc, float skew_x, float skew_y)
{
    lv_matrix_skew(&(dsc->ctx->matrix), skew_x, skew_y);
}

void lv_vector_for_each_destroy_tasks(lv_ll_t * task_list, vector_draw_task_cb cb, void * data)
{
    if(task_list == NULL) return;

    lv_draw_vector_subtask_t * task = lv_ll_get_head(task_list);
    lv_draw_vector_subtask_t * next_task = NULL;

    while(task != NULL) {
        next_task = lv_ll_get_next(task_list, task);
        lv_ll_remove(task_list, task);

        if(cb) {
            cb(data, task->path, &task->ctx);
        }

        if(task->path) {
            lv_vector_path_delete(task->path);
        }
        lv_array_deinit(&(task->ctx.stroke_dsc.dash_pattern));

        lv_free(task);
        task = next_task;
    }
    lv_free(task_list);
}

lv_draw_vector_dsc_t * lv_draw_task_get_vector_dsc(lv_draw_task_t * task)
{
    return task->type == LV_DRAW_TASK_TYPE_VECTOR ? (lv_draw_vector_dsc_t *)task->draw_dsc : NULL;
}

#endif /* LV_USE_VECTOR_GRAPHIC */
