/**
 * @file lv_draw_sw_polygon.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "lv_draw_sw.h"
#if LV_USE_DRAW_SW

#include "../../misc/lv_math.h"
#include "../../misc/lv_mem.h"
#include "../../misc/lv_area.h"
#include "../../misc/lv_color.h"
#include "../lv_draw_rect.h"

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

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

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

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

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

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

/**
 * Draw a polygon. Only convex polygons are supported
 * @param points an array of points
 * @param point_cnt number of points
 * @param clip_area polygon will be drawn only in this area
 * @param draw_dsc pointer to an initialized `lv_draw_rect_dsc_t` variable
 */
void lv_draw_sw_polygon(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * draw_dsc, const lv_point_t points[],
                        uint16_t point_cnt)
{
#if LV_USE_DRAW_MASKS
    if(point_cnt < 3) return;
    if(points == NULL) return;

    /*Join adjacent points if they are on the same coordinate*/
    lv_point_t * p = lv_malloc(point_cnt * sizeof(lv_point_t));
    if(p == NULL) return;
    uint16_t i;
    uint16_t pcnt = 0;
    p[0] = points[0];
    for(i = 0; i < point_cnt - 1; i++) {
        if(points[i].x != points[i + 1].x || points[i].y != points[i + 1].y) {
            p[pcnt] = points[i];
            pcnt++;
        }
    }
    /*The first and the last points are also adjacent*/
    if(points[0].x != points[point_cnt - 1].x || points[0].y != points[point_cnt - 1].y) {
        p[pcnt] = points[point_cnt - 1];
        pcnt++;
    }

    point_cnt = pcnt;
    if(point_cnt < 3) {
        lv_free(p);
        return;
    }

    lv_area_t poly_coords = {.x1 = LV_COORD_MAX, .y1 = LV_COORD_MAX, .x2 = LV_COORD_MIN, .y2 = LV_COORD_MIN};

    for(i = 0; i < point_cnt; i++) {
        poly_coords.x1 = LV_MIN(poly_coords.x1, p[i].x);
        poly_coords.y1 = LV_MIN(poly_coords.y1, p[i].y);
        poly_coords.x2 = LV_MAX(poly_coords.x2, p[i].x);
        poly_coords.y2 = LV_MAX(poly_coords.y2, p[i].y);
    }

    bool is_common;
    lv_area_t clip_area;
    is_common = _lv_area_intersect(&clip_area, &poly_coords, draw_ctx->clip_area);
    if(!is_common) {
        lv_free(p);
        return;
    }

    const lv_area_t * clip_area_ori = draw_ctx->clip_area;
    draw_ctx->clip_area = &clip_area;

    /*Find the lowest point*/
    lv_coord_t y_min = p[0].y;
    int16_t y_min_i = 0;

    for(i = 1; i < point_cnt; i++) {
        if(p[i].y < y_min) {
            y_min = p[i].y;
            y_min_i = i;
        }
    }

    lv_draw_mask_line_param_t * mp = lv_malloc(sizeof(lv_draw_mask_line_param_t) * point_cnt);
    lv_draw_mask_line_param_t * mp_next = mp;

    int32_t i_prev_left = y_min_i;
    int32_t i_prev_right = y_min_i;
    int32_t i_next_left;
    int32_t i_next_right;
    uint32_t mask_cnt = 0;

    /*Get the index of the left and right points*/
    i_next_left = y_min_i - 1;
    if(i_next_left < 0) i_next_left = point_cnt + i_next_left;

    i_next_right = y_min_i + 1;
    if(i_next_right > point_cnt - 1) i_next_right = 0;

    /**
     * Check if the order of points is inverted or not.
     * The normal case is when the left point is on `y_min_i - 1`
     * Explanation:
     *   if angle(p_left) < angle(p_right) -> inverted
     *   dy_left/dx_left < dy_right/dx_right
     *   dy_left * dx_right < dy_right * dx_left
     */
    lv_coord_t dxl = p[i_next_left].x - p[y_min_i].x;
    lv_coord_t dxr = p[i_next_right].x - p[y_min_i].x;
    lv_coord_t dyl = p[i_next_left].y - p[y_min_i].y;
    lv_coord_t dyr = p[i_next_right].y - p[y_min_i].y;

    bool inv = false;
    if(dyl * dxr < dyr * dxl) inv = true;

    do {
        if(!inv) {
            i_next_left = i_prev_left - 1;
            if(i_next_left < 0) i_next_left = point_cnt + i_next_left;

            i_next_right = i_prev_right + 1;
            if(i_next_right > point_cnt - 1) i_next_right = 0;
        }
        else {
            i_next_left = i_prev_left + 1;
            if(i_next_left > point_cnt - 1) i_next_left = 0;

            i_next_right = i_prev_right - 1;
            if(i_next_right < 0) i_next_right = point_cnt + i_next_right;
        }

        if(p[i_next_left].y >= p[i_prev_left].y) {
            if(p[i_next_left].y != p[i_prev_left].y &&
               p[i_next_left].x != p[i_prev_left].x) {
                lv_draw_mask_line_points_init(mp_next, p[i_prev_left].x, p[i_prev_left].y,
                                              p[i_next_left].x, p[i_next_left].y,
                                              LV_DRAW_MASK_LINE_SIDE_RIGHT);
                lv_draw_mask_add(mp_next, mp);
                mp_next++;
            }
            mask_cnt++;
            i_prev_left = i_next_left;
        }

        if(mask_cnt == point_cnt) break;

        if(p[i_next_right].y >= p[i_prev_right].y) {
            if(p[i_next_right].y != p[i_prev_right].y &&
               p[i_next_right].x != p[i_prev_right].x) {

                lv_draw_mask_line_points_init(mp_next, p[i_prev_right].x, p[i_prev_right].y,
                                              p[i_next_right].x, p[i_next_right].y,
                                              LV_DRAW_MASK_LINE_SIDE_LEFT);
                lv_draw_mask_add(mp_next, mp);
                mp_next++;
            }
            mask_cnt++;
            i_prev_right = i_next_right;
        }

    } while(mask_cnt < point_cnt);

    lv_draw_rect(draw_ctx, draw_dsc, &poly_coords);

    lv_draw_mask_remove_custom(mp);

    lv_free(mp);
    lv_free(p);

    draw_ctx->clip_area = clip_area_ori;
#else
    LV_UNUSED(points);
    LV_UNUSED(point_cnt);
    LV_UNUSED(draw_ctx);
    LV_UNUSED(draw_dsc);
    LV_LOG_WARN("Can't draw polygon with LV_USE_DRAW_MASKS == 0");
#endif /*LV_USE_DRAW_MASKS*/
}

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

#endif /*LV_USE_DRAW_SW*/
