/*****************************************************************************
 * format - A library for creating Excel XLSX format files.
 *
 * Used in conjunction with the libxlsxwriter library.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org.
 *
 */

#include "xlsxwriter/xmlwriter.h"
#include "xlsxwriter/format.h"
#include "xlsxwriter/utility.h"

/*****************************************************************************
 *
 * Private functions.
 *
 ****************************************************************************/

/*
 * Create a new format object.
 */
lxw_format *
lxw_format_new(void)
{
    lxw_format *format = calloc(1, sizeof(lxw_format));
    GOTO_LABEL_ON_MEM_ERROR(format, mem_error);

    format->xf_format_indices = NULL;
    format->dxf_format_indices = NULL;

    format->xf_index = LXW_PROPERTY_UNSET;
    format->dxf_index = LXW_PROPERTY_UNSET;
    format->xf_id = 0;

    format->font_name[0] = '\0';
    format->font_scheme[0] = '\0';
    format->num_format[0] = '\0';
    format->num_format_index = 0;
    format->font_index = 0;
    format->has_font = LXW_FALSE;
    format->has_dxf_font = LXW_FALSE;
    format->font_size = 11.0;
    format->bold = LXW_FALSE;
    format->italic = LXW_FALSE;
    format->font_color = LXW_COLOR_UNSET;
    format->underline = LXW_UNDERLINE_NONE;
    format->font_strikeout = LXW_FALSE;
    format->font_outline = LXW_FALSE;
    format->font_shadow = LXW_FALSE;
    format->font_script = LXW_FALSE;
    format->font_family = LXW_DEFAULT_FONT_FAMILY;
    format->font_charset = LXW_FALSE;
    format->font_condense = LXW_FALSE;
    format->font_extend = LXW_FALSE;
    format->theme = 0;
    format->hyperlink = LXW_FALSE;

    format->hidden = LXW_FALSE;
    format->locked = LXW_TRUE;

    format->text_h_align = LXW_ALIGN_NONE;
    format->text_wrap = LXW_FALSE;
    format->text_v_align = LXW_ALIGN_NONE;
    format->text_justlast = LXW_FALSE;
    format->rotation = 0;

    format->fg_color = LXW_COLOR_UNSET;
    format->bg_color = LXW_COLOR_UNSET;
    format->pattern = LXW_PATTERN_NONE;
    format->has_fill = LXW_FALSE;
    format->has_dxf_fill = LXW_FALSE;
    format->fill_index = 0;
    format->fill_count = 0;

    format->border_index = 0;
    format->has_border = LXW_FALSE;
    format->has_dxf_border = LXW_FALSE;
    format->border_count = 0;

    format->bottom = LXW_BORDER_NONE;
    format->left = LXW_BORDER_NONE;
    format->right = LXW_BORDER_NONE;
    format->top = LXW_BORDER_NONE;
    format->diag_border = LXW_BORDER_NONE;
    format->diag_type = LXW_BORDER_NONE;
    format->bottom_color = LXW_COLOR_UNSET;
    format->left_color = LXW_COLOR_UNSET;
    format->right_color = LXW_COLOR_UNSET;
    format->top_color = LXW_COLOR_UNSET;
    format->diag_color = LXW_COLOR_UNSET;

    format->indent = 0;
    format->shrink = LXW_FALSE;
    format->merge_range = LXW_FALSE;
    format->reading_order = 0;
    format->just_distrib = LXW_FALSE;
    format->color_indexed = LXW_FALSE;
    format->font_only = LXW_FALSE;

    format->quote_prefix = LXW_FALSE;

    return format;

mem_error:
    lxw_format_free(format);
    return NULL;
}

/*
 * Free a format object.
 */
void
lxw_format_free(lxw_format *format)
{
    if (!format)
        return;

    free(format);
    format = NULL;
}

/*
 * Check a user input border.
 */
STATIC uint8_t
_check_border(uint8_t border)
{
    if (border >= LXW_BORDER_THIN && border <= LXW_BORDER_SLANT_DASH_DOT)
        return border;
    else
        return LXW_BORDER_NONE;
}

/*****************************************************************************
 *
 * Public functions.
 *
 ****************************************************************************/

/*
 * Returns a format struct suitable for hashing as a lookup key. This is
 * mainly a memcpy with any pointer members set to NULL.
 */
STATIC lxw_format *
_get_format_key(lxw_format *self)
{
    lxw_format *key = calloc(1, sizeof(lxw_format));
    GOTO_LABEL_ON_MEM_ERROR(key, mem_error);

    memcpy(key, self, sizeof(lxw_format));

    /* Set pointer members to NULL since they aren't part of the comparison. */
    key->xf_format_indices = NULL;
    key->dxf_format_indices = NULL;
    key->num_xf_formats = NULL;
    key->num_dxf_formats = NULL;
    key->list_pointers.stqe_next = NULL;

    return key;

mem_error:
    return NULL;
}

/*
 * Returns a font struct suitable for hashing as a lookup key.
 */
lxw_font *
lxw_format_get_font_key(lxw_format *self)
{
    lxw_font *key = calloc(1, sizeof(lxw_font));
    GOTO_LABEL_ON_MEM_ERROR(key, mem_error);

    LXW_FORMAT_FIELD_COPY(key->font_name, self->font_name);
    key->font_size = self->font_size;
    key->bold = self->bold;
    key->italic = self->italic;
    key->underline = self->underline;
    key->theme = self->theme;
    key->font_color = self->font_color;
    key->font_strikeout = self->font_strikeout;
    key->font_outline = self->font_outline;
    key->font_shadow = self->font_shadow;
    key->font_script = self->font_script;
    key->font_family = self->font_family;
    key->font_charset = self->font_charset;
    key->font_condense = self->font_condense;
    key->font_extend = self->font_extend;

    return key;

mem_error:
    return NULL;
}

/*
 * Returns a border struct suitable for hashing as a lookup key.
 */
lxw_border *
lxw_format_get_border_key(lxw_format *self)
{
    lxw_border *key = calloc(1, sizeof(lxw_border));
    GOTO_LABEL_ON_MEM_ERROR(key, mem_error);

    key->bottom = self->bottom;
    key->left = self->left;
    key->right = self->right;
    key->top = self->top;
    key->diag_border = self->diag_border;
    key->diag_type = self->diag_type;
    key->bottom_color = self->bottom_color;
    key->left_color = self->left_color;
    key->right_color = self->right_color;
    key->top_color = self->top_color;
    key->diag_color = self->diag_color;

    return key;

mem_error:
    return NULL;
}

/*
 * Returns a pattern fill struct suitable for hashing as a lookup key.
 */
lxw_fill *
lxw_format_get_fill_key(lxw_format *self)
{
    lxw_fill *key = calloc(1, sizeof(lxw_fill));
    GOTO_LABEL_ON_MEM_ERROR(key, mem_error);

    key->fg_color = self->fg_color;
    key->bg_color = self->bg_color;
    key->pattern = self->pattern;

    return key;

mem_error:
    return NULL;
}

/*
 * Returns the XF index number used by Excel to identify a format.
 */
int32_t
lxw_format_get_xf_index(lxw_format *self)
{
    lxw_format *format_key;
    lxw_format *existing_format;
    lxw_hash_element *hash_element;
    lxw_hash_table *formats_hash_table = self->xf_format_indices;
    int32_t index;

    /* Note: The formats_hash_table/xf_format_indices contains the unique and
     * more importantly the *used* formats in the workbook.
     */

    /* Format already has an index number so return it. */
    if (self->xf_index != LXW_PROPERTY_UNSET) {
        return self->xf_index;
    }

    /* Otherwise, the format doesn't have an index number so we assign one.
     * First generate a unique key to identify the format in the hash table.
     */
    format_key = _get_format_key(self);

    /* Return the default format index if the key generation failed. */
    if (!format_key)
        return 0;

    /* Look up the format in the hash table. */
    hash_element =
        lxw_hash_key_exists(formats_hash_table, format_key,
                            sizeof(lxw_format));

    if (hash_element) {
        /* Format matches existing format with an index. */
        free(format_key);
        existing_format = hash_element->value;
        return existing_format->xf_index;
    }
    else {
        /* New format requiring an index. */
        index = formats_hash_table->unique_count;
        self->xf_index = index;
        lxw_insert_hash_element(formats_hash_table, format_key, self,
                                sizeof(lxw_format));
        return index;
    }
}

/*
 * Returns the DXF index number used by Excel to identify a format.
 */
int32_t
lxw_format_get_dxf_index(lxw_format *self)
{
    lxw_format *format_key;
    lxw_format *existing_format;
    lxw_hash_element *hash_element;
    lxw_hash_table *formats_hash_table = self->dxf_format_indices;
    int32_t index;

    /* Note: The formats_hash_table/dxf_format_indices contains the unique and
     * more importantly the *used* formats in the workbook.
     */

    /* Format already has an index number so return it. */
    if (self->dxf_index != LXW_PROPERTY_UNSET) {
        return self->dxf_index;
    }

    /* Otherwise, the format doesn't have an index number so we assign one.
     * First generate a unique key to identify the format in the hash table.
     */
    format_key = _get_format_key(self);

    /* Return the default format index if the key generation failed. */
    if (!format_key)
        return 0;

    /* Look up the format in the hash table. */
    hash_element =
        lxw_hash_key_exists(formats_hash_table, format_key,
                            sizeof(lxw_format));

    if (hash_element) {
        /* Format matches existing format with an index. */
        free(format_key);
        existing_format = hash_element->value;
        return existing_format->dxf_index;
    }
    else {
        /* New format requiring an index. */
        index = formats_hash_table->unique_count;
        self->dxf_index = index;
        lxw_insert_hash_element(formats_hash_table, format_key, self,
                                sizeof(lxw_format));
        return index;
    }
}

/*
 * Set the font_name property.
 */
void
format_set_font_name(lxw_format *self, const char *font_name)
{
    LXW_FORMAT_FIELD_COPY(self->font_name, font_name);
}

/*
 * Set the font_size property.
 */
void
format_set_font_size(lxw_format *self, double size)
{

    if (size >= LXW_MIN_FONT_SIZE && size <= LXW_MAX_FONT_SIZE)
        self->font_size = size;
}

/*
 * Set the font_color property.
 */
void
format_set_font_color(lxw_format *self, lxw_color_t color)
{
    self->font_color = color;
}

/*
 * Set the bold property.
 */
void
format_set_bold(lxw_format *self)
{
    self->bold = LXW_TRUE;
}

/*
 * Set the italic property.
 */

void
format_set_italic(lxw_format *self)
{
    self->italic = LXW_TRUE;
}

/*
 * Set the underline property.
 */
void
format_set_underline(lxw_format *self, uint8_t style)
{
    if (style >= LXW_UNDERLINE_SINGLE
        && style <= LXW_UNDERLINE_DOUBLE_ACCOUNTING)
        self->underline = style;
}

/*
 * Set the font_strikeout property.
 */
void
format_set_font_strikeout(lxw_format *self)
{
    self->font_strikeout = LXW_TRUE;
}

/*
 * Set the font_script property.
 */
void
format_set_font_script(lxw_format *self, uint8_t style)
{
    if (style >= LXW_FONT_SUPERSCRIPT && style <= LXW_FONT_SUBSCRIPT)
        self->font_script = style;
}

/*
 * Set the font_outline property.
 */
void
format_set_font_outline(lxw_format *self)
{
    self->font_outline = LXW_TRUE;
}

/*
 * Set the font_shadow property.
 */
void
format_set_font_shadow(lxw_format *self)
{
    self->font_shadow = LXW_TRUE;
}

/*
 * Set the num_format property.
 */
void
format_set_num_format(lxw_format *self, const char *num_format)
{
    LXW_FORMAT_FIELD_COPY(self->num_format, num_format);
}

/*
 * Set the unlocked property.
 */
void
format_set_unlocked(lxw_format *self)
{
    self->locked = LXW_FALSE;
}

/*
 * Set the hidden property.
 */
void
format_set_hidden(lxw_format *self)
{
    self->hidden = LXW_TRUE;
}

/*
 * Set the align property.
 */
void
format_set_align(lxw_format *self, uint8_t value)
{
    if (value >= LXW_ALIGN_LEFT && value <= LXW_ALIGN_DISTRIBUTED) {
        self->text_h_align = value;
    }

    if (value >= LXW_ALIGN_VERTICAL_TOP
        && value <= LXW_ALIGN_VERTICAL_DISTRIBUTED) {
        self->text_v_align = value;
    }
}

/*
 * Set the text_wrap property.
 */
void
format_set_text_wrap(lxw_format *self)
{
    self->text_wrap = LXW_TRUE;
}

/*
 * Set the rotation property.
 */
void
format_set_rotation(lxw_format *self, int16_t angle)
{
    /* Convert user angle to Excel angle. */
    if (angle == 270) {
        self->rotation = 255;
    }
    else if (angle >= -90 && angle <= 90) {
        if (angle < 0)
            angle = -angle + 90;

        self->rotation = angle;
    }
    else {
        LXW_WARN("Rotation rotation outside range: -90 <= angle <= 90.");
        self->rotation = 0;
    }
}

/*
 * Set the indent property.
 */
void
format_set_indent(lxw_format *self, uint8_t value)
{
    self->indent = value;
}

/*
 * Set the shrink property.
 */
void
format_set_shrink(lxw_format *self)
{
    self->shrink = LXW_TRUE;
}

/*
 * Set the text_justlast property.
 */
void
format_set_text_justlast(lxw_format *self)
{
    self->text_justlast = LXW_TRUE;
}

/*
 * Set the pattern property.
 */
void
format_set_pattern(lxw_format *self, uint8_t value)
{
    self->pattern = value;
}

/*
 * Set the bg_color property.
 */
void
format_set_bg_color(lxw_format *self, lxw_color_t color)
{
    self->bg_color = color;
}

/*
 * Set the fg_color property.
 */
void
format_set_fg_color(lxw_format *self, lxw_color_t color)
{
    self->fg_color = color;
}

/*
 * Set the border property.
 */
void
format_set_border(lxw_format *self, uint8_t style)
{
    style = _check_border(style);
    self->bottom = style;
    self->top = style;
    self->left = style;
    self->right = style;
}

/*
 * Set the border_color property.
 */
void
format_set_border_color(lxw_format *self, lxw_color_t color)
{
    self->bottom_color = color;
    self->top_color = color;
    self->left_color = color;
    self->right_color = color;
}

/*
 * Set the bottom property.
 */
void
format_set_bottom(lxw_format *self, uint8_t style)
{
    self->bottom = _check_border(style);
}

/*
 * Set the bottom_color property.
 */
void
format_set_bottom_color(lxw_format *self, lxw_color_t color)
{
    self->bottom_color = color;
}

/*
 * Set the left property.
 */
void
format_set_left(lxw_format *self, uint8_t style)
{
    self->left = _check_border(style);
}

/*
 * Set the left_color property.
 */
void
format_set_left_color(lxw_format *self, lxw_color_t color)
{
    self->left_color = color;
}

/*
 * Set the right property.
 */
void
format_set_right(lxw_format *self, uint8_t style)
{
    self->right = _check_border(style);
}

/*
 * Set the right_color property.
 */
void
format_set_right_color(lxw_format *self, lxw_color_t color)
{
    self->right_color = color;
}

/*
 * Set the top property.
 */
void
format_set_top(lxw_format *self, uint8_t style)
{
    self->top = _check_border(style);
}

/*
 * Set the top_color property.
 */
void
format_set_top_color(lxw_format *self, lxw_color_t color)
{
    self->top_color = color;
}

/*
 * Set the diag_type property.
 */
void
format_set_diag_type(lxw_format *self, uint8_t type)
{
    if (type >= LXW_DIAGONAL_BORDER_UP && type <= LXW_DIAGONAL_BORDER_UP_DOWN)
        self->diag_type = type;
}

/*
 * Set the diag_color property.
 */
void
format_set_diag_color(lxw_format *self, lxw_color_t color)
{
    self->diag_color = color;
}

/*
 * Set the diag_border property.
 */
void
format_set_diag_border(lxw_format *self, uint8_t style)
{
    self->diag_border = style;
}

/*
 * Set the num_format_index property.
 */
void
format_set_num_format_index(lxw_format *self, uint8_t value)
{
    self->num_format_index = value;
}

/*
 * Set the valign property.
 */
void
format_set_valign(lxw_format *self, uint8_t value)
{
    self->text_v_align = value;
}

/*
 * Set the reading_order property.
 */
void
format_set_reading_order(lxw_format *self, uint8_t value)
{
    self->reading_order = value;
}

/*
 * Set the font_family property.
 */
void
format_set_font_family(lxw_format *self, uint8_t value)
{
    self->font_family = value;
}

/*
 * Set the font_charset property.
 */
void
format_set_font_charset(lxw_format *self, uint8_t value)
{
    self->font_charset = value;
}

/*
 * Set the font_scheme property.
 */
void
format_set_font_scheme(lxw_format *self, const char *font_scheme)
{
    LXW_FORMAT_FIELD_COPY(self->font_scheme, font_scheme);
}

/*
 * Set the font_condense property.
 */
void
format_set_font_condense(lxw_format *self)
{
    self->font_condense = LXW_TRUE;
}

/*
 * Set the font_extend property.
 */
void
format_set_font_extend(lxw_format *self)
{
    self->font_extend = LXW_TRUE;
}

/*
 * Set the theme property.
 */
void
format_set_theme(lxw_format *self, uint8_t value)
{
    self->theme = value;
}

/*
 * Set the color_indexed property.
 */
void
format_set_color_indexed(lxw_format *self, uint8_t value)
{
    self->color_indexed = value;
}

/*
 * Set the font_only property.
 */
void
format_set_font_only(lxw_format *self)
{
    self->font_only = LXW_TRUE;
}

/*
 * Set the theme property.
 */
void
format_set_hyperlink(lxw_format *self)
{
    self->hyperlink = LXW_TRUE;
    self->xf_id = 1;
    self->underline = LXW_UNDERLINE_SINGLE;
    self->theme = 10;
}

/*
 * Set the quote_prefix property.
 */
void
format_set_quote_prefix(lxw_format *self)
{
    self->quote_prefix = LXW_TRUE;
}
