/**
 * @file lv_draw_rect.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "lv_draw_rect_private.h"
#include "lv_draw_private.h"
#include "../core/lv_obj.h"
#include "../misc/lv_assert.h"
#include "../misc/lv_text_private.h"
#include "../core/lv_obj_event.h"
#include "../stdlib/lv_string.h"

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

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

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

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

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

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

void LV_ATTRIBUTE_FAST_MEM lv_draw_rect_dsc_init(lv_draw_rect_dsc_t * dsc)
{
    lv_memzero(dsc, sizeof(lv_draw_rect_dsc_t));
    dsc->bg_color = lv_color_white();
    dsc->bg_grad.stops[0].color = lv_color_white();
    dsc->bg_grad.stops[1].color = lv_color_black();
    dsc->bg_grad.stops[1].frac = 0xFF;
    dsc->bg_grad.stops_count = 2;
    dsc->border_color = lv_color_black();
    dsc->shadow_color = lv_color_black();
    dsc->bg_image_symbol_font = LV_FONT_DEFAULT;
    dsc->bg_opa = LV_OPA_COVER;
    dsc->bg_image_opa = LV_OPA_COVER;
    dsc->outline_opa = LV_OPA_COVER;
    dsc->border_opa = LV_OPA_COVER;
    dsc->shadow_opa = LV_OPA_COVER;
    dsc->border_side = LV_BORDER_SIDE_FULL;
}

void lv_draw_fill_dsc_init(lv_draw_fill_dsc_t * dsc)
{
    lv_memzero(dsc, sizeof(*dsc));
    dsc->opa = LV_OPA_COVER;
    dsc->base.dsc_size = sizeof(lv_draw_fill_dsc_t);
}

lv_draw_fill_dsc_t * lv_draw_task_get_fill_dsc(lv_draw_task_t * task)
{
    return task->type == LV_DRAW_TASK_TYPE_FILL ? (lv_draw_fill_dsc_t *)task->draw_dsc : NULL;
}

void lv_draw_fill(lv_layer_t * layer, const lv_draw_fill_dsc_t * dsc, const lv_area_t * coords)
{
    if(dsc->opa <= LV_OPA_MIN) return;

    LV_PROFILER_DRAW_BEGIN;

    if(dsc->base.drop_shadow_opa) {
        lv_layer_t * ds_layer = lv_draw_layer_create_drop_shadow(layer, &dsc->base, coords);
        LV_ASSERT_NULL(ds_layer);
        lv_draw_fill_dsc_t ds_dsc = *dsc;
        ds_dsc.base.drop_shadow_opa = 0; /*Disable drop shadow so rendering below will render plain fill*/
        lv_draw_fill(ds_layer, &ds_dsc, coords);
        lv_draw_layer_finish_drop_shadow(ds_layer, &dsc->base);
    }

    lv_draw_task_t * t = lv_draw_add_task(layer, coords, LV_DRAW_TASK_TYPE_FILL);

    lv_memcpy(t->draw_dsc, dsc, sizeof(*dsc));

    lv_draw_finalize_task_creation(layer, t);
    LV_PROFILER_DRAW_END;
}

void lv_draw_border_dsc_init(lv_draw_border_dsc_t * dsc)
{
    lv_memzero(dsc, sizeof(*dsc));
    dsc->opa = LV_OPA_COVER;
    dsc->side = LV_BORDER_SIDE_FULL;
    dsc->base.dsc_size = sizeof(lv_draw_border_dsc_t);
}

lv_draw_border_dsc_t * lv_draw_task_get_border_dsc(lv_draw_task_t * task)
{
    return task->type == LV_DRAW_TASK_TYPE_BORDER ? (lv_draw_border_dsc_t *)task->draw_dsc : NULL;
}

void lv_draw_border(lv_layer_t * layer, const lv_draw_border_dsc_t * dsc, const lv_area_t * coords)
{
    if(dsc->opa <= LV_OPA_MIN) return;

    LV_PROFILER_DRAW_BEGIN;

    if(dsc->base.drop_shadow_opa) {
        lv_layer_t * ds_layer = lv_draw_layer_create_drop_shadow(layer, &dsc->base, coords);
        LV_ASSERT_NULL(ds_layer);
        lv_draw_border_dsc_t ds_dsc = *dsc;
        ds_dsc.base.drop_shadow_opa = 0; /*Disable drop shadow so rendering below will render plain border*/
        lv_draw_border(ds_layer, &ds_dsc, coords);
        lv_draw_layer_finish_drop_shadow(ds_layer, &dsc->base);
    }

    lv_draw_task_t * t = lv_draw_add_task(layer, coords, LV_DRAW_TASK_TYPE_BORDER);

    lv_memcpy(t->draw_dsc, dsc, sizeof(*dsc));

    lv_draw_finalize_task_creation(layer, t);
    LV_PROFILER_DRAW_END;
}

void lv_draw_box_shadow_dsc_init(lv_draw_box_shadow_dsc_t * dsc)
{
    lv_memzero(dsc, sizeof(*dsc));
    dsc->opa = LV_OPA_COVER;
    dsc->base.dsc_size = sizeof(lv_draw_box_shadow_dsc_t);
}

lv_draw_box_shadow_dsc_t * lv_draw_task_get_box_shadow_dsc(lv_draw_task_t * task)
{
    return task->type == LV_DRAW_TASK_TYPE_BOX_SHADOW ? (lv_draw_box_shadow_dsc_t *)task->draw_dsc : NULL;
}

void lv_draw_box_shadow(lv_layer_t * layer, const lv_draw_box_shadow_dsc_t * dsc, const lv_area_t * coords)
{
    if(dsc->opa <= LV_OPA_MIN) return;

    LV_PROFILER_DRAW_BEGIN;
    lv_draw_task_t * t = lv_draw_add_task(layer, coords, LV_DRAW_TASK_TYPE_BOX_SHADOW);

    lv_memcpy(t->draw_dsc, dsc, sizeof(*dsc));

    lv_draw_finalize_task_creation(layer, t);
    LV_PROFILER_DRAW_END;
}

void lv_draw_rect(lv_layer_t * layer, const lv_draw_rect_dsc_t * dsc, const lv_area_t * coords)
{

    LV_PROFILER_DRAW_BEGIN;

    bool has_shadow;
    bool has_fill;
    bool has_border;
    bool has_outline;
    bool has_bg_img;

    if(dsc->shadow_width == 0 ||
       dsc->shadow_opa <= LV_OPA_MIN ||
       (dsc->shadow_width == 1 && dsc->shadow_spread <= 0 &&
        dsc->shadow_offset_x == 0 && dsc->shadow_offset_y == 0)) {
        has_shadow = false;
    }
    else {
        has_shadow = true;
    }

    if(dsc->bg_opa <= LV_OPA_MIN) has_fill = false;
    else has_fill = true;

    if(dsc->bg_image_opa <= LV_OPA_MIN || dsc->bg_image_src == NULL) has_bg_img = false;
    else has_bg_img = true;

    if(dsc->border_opa <= LV_OPA_MIN
       || dsc->border_width == 0
       || dsc->border_post == true
       || dsc->border_side == LV_BORDER_SIDE_NONE) has_border = false;
    else has_border = true;

    if(dsc->outline_opa <= LV_OPA_MIN || dsc->outline_width == 0) has_outline = false;
    else has_outline = true;

    bool bg_cover = true;
    if(dsc->bg_opa < LV_OPA_COVER) bg_cover = false;
    else if(dsc->bg_grad.dir != LV_GRAD_DIR_NONE) {
        uint32_t s;
        for(s = 0; s < dsc->bg_grad.stops_count; s++) {
            if(dsc->bg_grad.stops[s].opa != LV_OPA_COVER) {
                bg_cover = false;
                break;
            }
        }
    }

    if(dsc->base.drop_shadow_opa && (has_fill || has_outline)) {
        lv_layer_t * ds_layer = lv_draw_layer_create_drop_shadow(layer, &dsc->base, coords);
        LV_ASSERT_NULL(ds_layer);
        lv_draw_rect_dsc_t ds_dsc = *dsc;
        ds_dsc.base.drop_shadow_opa = 0; /*Disable drop shadow so rendering below will render plain shadow*/
        ds_dsc.shadow_opa = 0;
        lv_draw_rect(ds_layer, &ds_dsc, coords);
        lv_draw_layer_finish_drop_shadow(ds_layer, &dsc->base);
    }

    lv_draw_task_t * t;

    /*Shadow*/
    if(has_shadow) {
        /*Check whether the shadow is visible*/
        t = lv_draw_add_task(layer, coords, LV_DRAW_TASK_TYPE_BOX_SHADOW);
        lv_draw_box_shadow_dsc_t * shadow_dsc = t->draw_dsc;

        lv_area_increase(&t->_real_area, dsc->shadow_spread, dsc->shadow_spread);
        lv_area_increase(&t->_real_area, dsc->shadow_width, dsc->shadow_width);
        lv_area_move(&t->_real_area, dsc->shadow_offset_x, dsc->shadow_offset_y);
        shadow_dsc->base = dsc->base;
        shadow_dsc->base.dsc_size = sizeof(lv_draw_box_shadow_dsc_t);
        shadow_dsc->radius = dsc->radius;
        shadow_dsc->color = dsc->shadow_color;
        shadow_dsc->width = dsc->shadow_width;
        shadow_dsc->spread = dsc->shadow_spread;
        shadow_dsc->opa = dsc->shadow_opa;
        shadow_dsc->ofs_x = dsc->shadow_offset_x;
        shadow_dsc->ofs_y = dsc->shadow_offset_y;
        shadow_dsc->bg_cover = bg_cover;
        lv_draw_finalize_task_creation(layer, t);
    }

    /*Background*/
    if(has_fill) {
        lv_area_t bg_coords = *coords;
        /*If the border fully covers make the bg area 1px smaller to avoid artifacts on the corners*/
        if(dsc->border_width > 1 && dsc->border_opa >= LV_OPA_MAX && dsc->radius != 0) {
            bg_coords.x1 += (dsc->border_side & LV_BORDER_SIDE_LEFT) ? 1 : 0;
            bg_coords.y1 += (dsc->border_side & LV_BORDER_SIDE_TOP) ? 1 : 0;
            bg_coords.x2 -= (dsc->border_side & LV_BORDER_SIDE_RIGHT) ? 1 : 0;
            bg_coords.y2 -= (dsc->border_side & LV_BORDER_SIDE_BOTTOM) ? 1 : 0;
        }

        t = lv_draw_add_task(layer, &bg_coords, LV_DRAW_TASK_TYPE_FILL);
        lv_draw_fill_dsc_t * bg_dsc = t->draw_dsc;

        lv_draw_fill_dsc_init(bg_dsc);
        bg_dsc->base = dsc->base;
        bg_dsc->base.dsc_size = sizeof(lv_draw_fill_dsc_t);
        bg_dsc->radius = dsc->radius;
        bg_dsc->color = dsc->bg_color;
        bg_dsc->grad = dsc->bg_grad;
        bg_dsc->opa = dsc->bg_opa;

        lv_draw_finalize_task_creation(layer, t);
    }

    /*Background image*/
    if(has_bg_img) {
        lv_image_src_t src_type = lv_image_src_get_type(dsc->bg_image_src);
        lv_result_t res = LV_RESULT_OK;
        lv_image_header_t header;
        if(src_type == LV_IMAGE_SRC_VARIABLE || src_type == LV_IMAGE_SRC_FILE) {
            res  = lv_image_decoder_get_info(dsc->bg_image_src, &header);
        }
        else {
            lv_memzero(&header, sizeof(header));

            if(src_type == LV_IMAGE_SRC_UNKNOWN) {
                res = LV_RESULT_INVALID;
            }
        }

        if(res == LV_RESULT_OK) {
            if(src_type == LV_IMAGE_SRC_VARIABLE || src_type == LV_IMAGE_SRC_FILE) {

                if(dsc->bg_image_tiled) {
                    t = lv_draw_add_task(layer, coords, LV_DRAW_TASK_TYPE_IMAGE);
                }
                else {
                    lv_area_t a = {0, 0, header.w - 1, header.h - 1};
                    lv_area_align(coords, &a, LV_ALIGN_CENTER, 0, 0);
                    t = lv_draw_add_task(layer, &a, LV_DRAW_TASK_TYPE_IMAGE);
                }

                lv_draw_image_dsc_t * bg_image_dsc = t->draw_dsc;

                lv_draw_image_dsc_init(bg_image_dsc);
                bg_image_dsc->base = dsc->base;
                bg_image_dsc->base.dsc_size = sizeof(lv_draw_image_dsc_t);
                bg_image_dsc->src = dsc->bg_image_src;
                bg_image_dsc->opa = dsc->bg_image_opa;
                bg_image_dsc->recolor = dsc->bg_image_recolor;
                bg_image_dsc->recolor_opa = dsc->bg_image_recolor_opa;
                bg_image_dsc->tile = dsc->bg_image_tiled;
                bg_image_dsc->colorkey = dsc->bg_image_colorkey;
                bg_image_dsc->header = header;
                bg_image_dsc->clip_radius = dsc->radius;
                bg_image_dsc->image_area = *coords;
                lv_draw_finalize_task_creation(layer, t);
            }
            else {
                lv_point_t s;

                lv_text_attributes_t attributes = {0};
                attributes.text_flags = LV_TEXT_FLAG_NONE;
                attributes.max_width = LV_COORD_MAX;
                attributes.line_space = 0;
                attributes.letter_space = 0;

                lv_text_get_size_attributes(&s, dsc->bg_image_src, dsc->bg_image_symbol_font, &attributes);

                lv_area_t a = {0, 0, s.x - 1, s.y - 1};
                lv_area_align(coords, &a, LV_ALIGN_CENTER, 0, 0);
                t = lv_draw_add_task(layer, &a, LV_DRAW_TASK_TYPE_LABEL);

                lv_draw_label_dsc_t * bg_label_dsc = t->draw_dsc;

                lv_draw_label_dsc_init(bg_label_dsc);
                bg_label_dsc->base = dsc->base;
                bg_label_dsc->base.dsc_size = sizeof(lv_draw_label_dsc_t);
                bg_label_dsc->color = dsc->bg_image_recolor;
                bg_label_dsc->font = dsc->bg_image_symbol_font;
                bg_label_dsc->text = dsc->bg_image_src;
                t->type = LV_DRAW_TASK_TYPE_LABEL;
                lv_draw_finalize_task_creation(layer, t);
            }
        }
    }

    /*Border*/
    if(has_border) {
        t = lv_draw_add_task(layer, coords, LV_DRAW_TASK_TYPE_BORDER);
        lv_draw_border_dsc_t * border_dsc = t->draw_dsc;

        border_dsc->base = dsc->base;
        border_dsc->base.dsc_size = sizeof(lv_draw_border_dsc_t);
        border_dsc->radius = dsc->radius;
        border_dsc->color = dsc->border_color;
        border_dsc->opa = dsc->border_opa;
        border_dsc->width = dsc->border_width;
        border_dsc->side = dsc->border_side;
        lv_draw_finalize_task_creation(layer, t);
    }

    /*Outline*/
    if(has_outline) {
        lv_area_t outline_coords = *coords;
        lv_area_increase(&outline_coords, dsc->outline_width + dsc->outline_pad, dsc->outline_width + dsc->outline_pad);
        t = lv_draw_add_task(layer, &outline_coords, LV_DRAW_TASK_TYPE_BORDER);
        lv_draw_border_dsc_t * outline_dsc = t->draw_dsc;
        lv_area_increase(&t->_real_area, dsc->outline_width, dsc->outline_width);
        lv_area_increase(&t->_real_area, dsc->outline_pad, dsc->outline_pad);
        outline_dsc->base = dsc->base;
        outline_dsc->base.dsc_size = sizeof(lv_draw_border_dsc_t);
        outline_dsc->radius = dsc->radius == LV_RADIUS_CIRCLE ? LV_RADIUS_CIRCLE : dsc->radius + dsc->outline_width +
                              dsc->outline_pad;
        outline_dsc->color = dsc->outline_color;
        outline_dsc->opa = dsc->outline_opa;
        outline_dsc->width = dsc->outline_width;
        outline_dsc->side = LV_BORDER_SIDE_FULL;
        lv_draw_finalize_task_creation(layer, t);
    }

    LV_ASSERT_MEM_INTEGRITY();

    LV_PROFILER_DRAW_END;
}

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