/***************************************************************************
 *   crengine-ng                                                           *
 *   Copyright (C) 2007-2013 Vadim Lopatin <coolreader.org@gmail.com>      *
 *   Copyright (C) 2015,2016 Yifei(Frank) ZHU <fredyifei@gmail.com>        *
 *   Copyright (C) 2020 Aleksey Chernov <valexlin@gmail.com>               *
 *   Copyright (C) 2018-2021 poire-z <poire-z@users.noreply.github.com>    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License           *
 *   as published by the Free Software Foundation; either version 2        *
 *   of the License, or (at your option) any later version.                *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,            *
 *   MA 02110-1301, USA.                                                   *
 ***************************************************************************/

/**
 * \file lvstyles.h
 * \brief CSS Styles and node format
 */

#if !defined(__LV_STYLES_H_INCLUDED__)
#define __LV_STYLES_H_INCLUDED__

#include <cssdef.h>
#include <lvmemman.h>
#include <lvrefcache.h>
#include <lvfntman.h>
#include <lvstring8collection.h>

class SerialBuf;

/* bit position (in 'lUInt32[] important' and 'lUInt32[] importance' bitmaps)
 * of each css_style_rec_tag properties to flag its '!important' status */
enum css_style_rec_important_bit
{
    imp_bit_display = 0,
    imp_bit_white_space,
    imp_bit_text_align,
    imp_bit_text_align_last,
    imp_bit_text_decoration,
    imp_bit_text_transform,
    imp_bit_vertical_align,
    imp_bit_font_family,
    imp_bit_font_name,
    imp_bit_font_size,
    imp_bit_font_style,
    imp_bit_font_weight,
    imp_bit_font_features,
    imp_bit_text_indent,
    imp_bit_line_height,
    imp_bit_width,
    imp_bit_height,
    imp_bit_min_width,
    imp_bit_min_height,
    imp_bit_max_width,
    imp_bit_max_height,
    imp_bit_margin_left,
    imp_bit_margin_right,
    imp_bit_margin_top,
    imp_bit_margin_bottom,
    imp_bit_padding_left,
    imp_bit_padding_right,
    imp_bit_padding_top,
    imp_bit_padding_bottom,
    imp_bit_color,
    imp_bit_background_color,
    imp_bit_letter_spacing,
    imp_bit_page_break_before,
    imp_bit_page_break_after,
    imp_bit_page_break_inside,
    imp_bit_hyphenate,
    imp_bit_list_style_type,
    imp_bit_list_style_position,
    imp_bit_border_style_top,
    imp_bit_border_style_bottom,
    imp_bit_border_style_right,
    imp_bit_border_style_left,
    imp_bit_border_width_top,
    imp_bit_border_width_right,
    imp_bit_border_width_bottom,
    imp_bit_border_width_left,
    imp_bit_border_color_top,
    imp_bit_border_color_right,
    imp_bit_border_color_bottom,
    imp_bit_border_color_left,
    imp_bit_background_image,
    imp_bit_background_repeat,
    imp_bit_background_position,
    imp_bit_background_size_h,
    imp_bit_background_size_v,
    imp_bit_border_collapse,
    imp_bit_border_spacing_h,
    imp_bit_border_spacing_v,
    imp_bit_orphans,
    imp_bit_widows,
    imp_bit_float,
    imp_bit_clear,
    imp_bit_direction,
    imp_bit_visibility,
    imp_bit_line_break,
    imp_bit_word_break,
    imp_bit_content,
    imp_bit_cr_hint
};
#define NB_IMP_BITS 68 // The number of lines in the enum above: KEEP IT UPDATED.

#define NB_IMP_SLOTS ((NB_IMP_BITS - 1) >> 5) + 1
// In lvstyles.cpp, we have hardcoded important[0] ... importance[1]
// So once NB_IMP_SLOTS becomes 4 when IMP_BIT_MAX > 96, add in lvstyles.cpp
// the needed important[3] and importance[3]. Let us know if we forget that:
#if (NB_IMP_SLOTS != 3)
#error "NB_IMP_SLOTS != 3, some updates in lvstyles.cpp (and then here) are needed"
#endif

// Style handling flags
#define STYLE_REC_FLAG_MATCHED 0x01 // This style has had some stylesheet declaration matched and applied. \
                                    // Currently only used for a pseudo element style,                     \
                                    // see LVCssSelector::apply() if more generic usage needed.

/**
    \brief Element style record.

    Contains set of style properties.
*/
typedef struct css_style_rec_tag css_style_rec_t;
struct css_style_rec_tag
{
    int refCount;                     // for reference counting
    lUInt32 hash;                     // cache calculated hash value here
    lUInt32 important[NB_IMP_SLOTS];  // bitmap for !important (used only by LVCssDeclaration)
    lUInt32 importance[NB_IMP_SLOTS]; // bitmap for important bit's importance/origin
                                      // (allows for 2 level of !important importance)
    css_display_t display;
    css_white_space_t white_space;
    css_text_align_t text_align;
    css_text_align_t text_align_last;
    css_text_decoration_t text_decoration;
    css_text_transform_t text_transform;
    css_length_t vertical_align;
    css_font_family_t font_family;
    lString8 font_name;
    css_length_t font_size;
    css_font_style_t font_style;
    css_font_weight_t font_weight;
    css_length_t font_features;
    css_length_t text_indent;
    css_length_t line_height;
    css_length_t width;
    css_length_t height;
    css_length_t min_width;
    css_length_t min_height;
    css_length_t max_width;
    css_length_t max_height;
    css_length_t margin[4];  ///< margin-left, -right, -top, -bottom
    css_length_t padding[4]; ///< padding-left, -right, -top, -bottom
    css_length_t color;
    css_length_t background_color;
    css_length_t letter_spacing;
    css_page_break_t page_break_before;
    css_page_break_t page_break_after;
    css_page_break_t page_break_inside;
    css_hyphenate_t hyphenate;
    css_list_style_type_t list_style_type;
    css_list_style_position_t list_style_position;
    css_border_style_type_t border_style_top;
    css_border_style_type_t border_style_bottom;
    css_border_style_type_t border_style_right;
    css_border_style_type_t border_style_left;
    css_length_t border_width[4]; ///< border-top-width, -right-, -bottom-, -left-
    css_length_t border_color[4]; ///< border-top-color, -right-, -bottom-, -left-
    lString8 background_image;
    css_background_repeat_value_t background_repeat;
    css_background_position_value_t background_position;
    css_length_t background_size[2]; //first width and second height
    css_border_collapse_value_t border_collapse;
    css_length_t border_spacing[2]; //first horizontal and the second vertical spacing
    css_orphans_widows_value_t orphans;
    css_orphans_widows_value_t widows;
    css_float_t float_; // "float" is a C++ keyword...
    css_clear_t clear;
    css_direction_t direction;
    css_visibility_t visibility;
    css_line_break_t line_break;
    css_word_break_t word_break;
    lString32 content;
    css_length_t cr_hint;
    // The following should only be used when applying stylesheets while in lvend.cpp setNodeStyle(),
    // and cleaned up there, before the style is cached and shared. They are not serialized.
    lInt8 flags; // bitmap of STYLE_REC_FLAG_*
    css_style_rec_t* pseudo_elem_before_style;
    css_style_rec_t* pseudo_elem_after_style;

    css_style_rec_tag()
            : refCount(0)
            , hash(0)
            , important {} // zero-initialization of all array slots
            , importance {}
            , display(css_d_inline)
            , white_space(css_ws_inherit)
            , text_align(css_ta_inherit)
            , text_align_last(css_ta_inherit)
            , text_decoration(css_td_inherit)
            , text_transform(css_tt_inherit)
            , vertical_align(css_val_unspecified, css_va_baseline)
            , font_family(css_ff_inherit)
            , font_size(css_val_inherited, 0)
            , font_style(css_fs_inherit)
            , font_weight(css_fw_inherit)
            , font_features(css_val_inherited, 0)
            , text_indent(css_val_inherited, 0)
            , line_height(css_val_inherited, 0)
            , width(css_val_unspecified, 0)
            , height(css_val_unspecified, 0)
            , min_width(css_val_unspecified, 0)
            , min_height(css_val_unspecified, 0)
            , max_width(css_val_unspecified, 0)
            , max_height(css_val_unspecified, 0)
            , color(css_val_inherited, 0)
            , background_color(css_val_unspecified, 0)
            , letter_spacing(css_val_inherited, 0)
            , page_break_before(css_pb_auto)
            , page_break_after(css_pb_auto)
            , page_break_inside(css_pb_auto)
            , hyphenate(css_hyph_inherit)
            , list_style_type(css_lst_inherit)
            , list_style_position(css_lsp_inherit)
            , border_style_top(css_border_none)
            , border_style_bottom(css_border_none)
            , border_style_right(css_border_none)
            , border_style_left(css_border_none)
            , background_repeat(css_background_r_none)
            , background_position(css_background_p_none)
            , border_collapse(css_border_seperate)
            , orphans(css_orphans_widows_inherit)
            , widows(css_orphans_widows_inherit)
            , float_(css_f_none)
            , clear(css_c_none)
            , direction(css_dir_inherit)
            , visibility(css_v_inherit)
            , line_break(css_lb_inherit)
            , word_break(css_wb_inherit)
            , cr_hint(css_val_inherited, 0)
            , flags(0)
            , pseudo_elem_before_style(NULL)
            , pseudo_elem_after_style(NULL) {
        // css_length_t fields are initialized by css_length_tag()
        // to (css_val_screen_px, 0)
        // These should not: a not specified border width will
        // use DEFAULT_BORDER_WIDTH (=2)
        border_width[0] = css_length_t(css_val_unspecified, 0);
        border_width[1] = css_length_t(css_val_unspecified, 0);
        border_width[2] = css_length_t(css_val_unspecified, 0);
        border_width[3] = css_length_t(css_val_unspecified, 0);
        background_size[0] = css_length_t(css_val_unspecified, 0);
        background_size[1] = css_length_t(css_val_unspecified, 0);
        // Also initialize border colors
        border_color[0] = css_length_t(css_val_unspecified, css_generic_currentcolor);
        border_color[1] = css_length_t(css_val_unspecified, css_generic_currentcolor);
        border_color[2] = css_length_t(css_val_unspecified, css_generic_currentcolor);
        border_color[3] = css_length_t(css_val_unspecified, css_generic_currentcolor);
    }
    void AddRef() {
        refCount++;
    }
    int Release() {
        return --refCount;
    }
    int getRefCount() {
        return refCount;
    }
    bool serialize(SerialBuf& buf);
    bool deserialize(SerialBuf& buf);
    //  important bitmap management
    bool isImportant(css_style_rec_important_bit bit) {
        return important[bit >> 5] & (1 << (bit & 0x1f));
    }
    void setImportant(css_style_rec_important_bit bit) {
        important[bit >> 5] |= (1 << (bit & 0x1f));
    }
    // apply value to field if important bit not yet set, then set it if is_important
    template <typename T>
    inline void Apply(T value, T* field, css_style_rec_important_bit bit, lUInt8 is_important) {
        int slot = bit >> 5;          // 32 bits per LUint32 slot
        int sbit = 1 << (bit & 0x1f); // bitmask for this single bit in its slot
        // is_important is 2 bits: (high_importance, is_important) (see lvstsheet.cpp)
        if (!(important[slot] & sbit)                              // important flag not previously set
            || (is_important == 0x3)                               // coming value has '!important' and comes from some CSS parsed with higher_importance=true
            || (is_important == 0x1 && !(importance[slot] & sbit)) // coming value has '!important' and comes from some CSS parsed
                                                                   // with higher_importance=false, but previous value was not set from
                                                                   // a !important that came from CSS parsed with higher_importance=true
        ) {
            *field = value; // apply
            if (is_important & 0x1)
                important[slot] |= sbit; // update important flag
            if (is_important == 0x3)
                importance[slot] |= sbit; // update importance flag (!important comes from higher_importance CSS)
        }
    }
    // Similar to previous one, but logical-OR'ing values, for bitmaps (currently, only style->font_features and style->cr_hint)
    inline void ApplyAsBitmapOr(css_length_t value, css_length_t* field, css_style_rec_important_bit bit, lUInt8 is_important) {
        int slot = bit >> 5;          // 32 bits per LUint32 slot
        int sbit = 1 << (bit & 0x1f); // bitmask for this bit in its slot
        if (!(important[slot] & sbit) || (is_important == 0x3) || (is_important == 0x1 && !(importance[slot] & sbit))) {
            field->value |= value.value; // logical-or values
            field->type = value.type;    // use the one from value (always css_val_unspecified for font_features)
            if (is_important & 0x1)
                important[slot] |= sbit;
            if (is_important == 0x3)
                importance[slot] |= sbit;
        }
    }
};

/// style record reference type
typedef LVFastRef<css_style_rec_t> css_style_ref_t;
/// font reference type
typedef LVFontRef font_ref_t;

/// to compare two styles
bool operator==(const css_style_rec_t& r1, const css_style_rec_t& r2);

/// style hash table size
#define LV_STYLE_HASH_SIZE 0x100

/// style cache: allows to avoid duplicate style object allocation
class lvdomStyleCache: public LVRefCache<css_style_ref_t>
{
public:
    lvdomStyleCache(int size = LV_STYLE_HASH_SIZE)
            : LVRefCache<css_style_ref_t>(size) { }
};

/// element rendering methods
enum lvdom_element_render_method
{
    erm_invisible = 0,      ///< invisible: don't render
    erm_killed,             ///< reset to this when there is no room to render element
    erm_block,              ///< render as block element (render as containing other elements)
    erm_final,              ///< final element: render the whole it's content as single render block
    erm_inline,             ///< inline element
    erm_table,              ///< table element: render as table
    erm_table_row_group,    ///< table row group
    erm_table_header_group, ///< table header group
    erm_table_footer_group, ///< table footer group
    erm_table_row,          ///< table row
    erm_table_column_group, ///< table column group
    erm_table_column,       ///< table column
    // Note that table cells always become either erm_block or erm_final depending on their content
    // and that table captions are set erm_final.
};

/// node format record
class lvdomElementFormatRec
{
protected:
    // Values on the x-axis can be stored in a 16bit int, values
    // on the y-axis should better be stored in a 32bit int.
    // Some of these fields were added to support floats and
    // optimize some computations (we increased the size of
    // this object from 4 to 16 32bits-int - this make cache
    // files grow only by ~1%, probably because they compress
    // well when not filled and left to be 0).

    // Position and size of a (block or final) element, relative to
    // it's parent block container top left, not including margins,
    // but including borders and paddings.
    int _y;
    int _height;
    short _x;
    short _width;
    // For erm_final block, inner width and position (so, excluding
    // paddings and border) of the LFormattedText inside that element.
    short _inner_width; // = _width - left and right borders and paddings
    short _inner_x;     // = left border + padding
    short _inner_y;     // = top border + padding
                        // (no need for any _inner_height currently)

    short _baseline; // reference baseline y (only set for inline-block box)
                     // (Note: it's limited to 32767px here, but images and inline-box height
                     // is also limited to that for being carried in lInt16 slots when
                     // formatting text in lvtextfm.cpp.)

    short _usable_left_overflow;  // Usable overflow for hanging punctuation
    short _usable_right_overflow; // and glyphs negative side bearings

    // Children blocks should be fully contained in their parent block,
    // and sibling nodes blocks should not overlap with other siblings,
    // except when float are involved and we allow them to continue
    // over next outer elements.
    // Record these overflows, mostly to just be able to draw floats
    // correctly and not ignore them if their container block is out
    // of screen. Also help selecting text in overflowing floats.
    int _top_overflow;    // Overflow (positive value) below _y
    int _bottom_overflow; // Overflow (positive value) after _y+_height

    int _lang_node_idx; // dataIndex of the upper node this erm_final block
                        // should get its lang= langage from

    // Flags & extras, to have additional info related to this rect cached.
    // - For erm_final nodes, these contain the footprint of outer floats
    //   that we need to know when accessing this final node (for re-
    //   formatting, when drawing, or selecting text or links...) to expect
    //   a reproducible layout (see BlockFloatFootprint for the different
    //   possible usages of these extra fields).
    unsigned short _flags;
    unsigned short _extra0;
    int _extra1;
    int _extra2;
    int _extra3;
    int _extra4;
    int _extra5;

    int _listprop_node_idx; // dataIndex of the UL/OL node this erm_final block
                            // should get its marker from

    // We're now at 16 32-bits ints. If needing new fields, add some padding:
    // Added for padding from 17 to 20 32-bits ints
    // int _available1; int _available2; int _available3;
public:
    lvdomElementFormatRec()
            : _x(0)
            , _width(0)
            , _y(0)
            , _height(0)
            , _inner_width(0)
            , _inner_x(0)
            , _inner_y(0)
            , _baseline(0)
            , _usable_left_overflow(0)
            , _usable_right_overflow(0)
            , _top_overflow(0)
            , _bottom_overflow(0)
            , _lang_node_idx(0)
            , _listprop_node_idx(0)
            , _flags(0)
            , _extra0(0)
            , _extra1(0)
            , _extra2(0)
            , _extra3(0)
            , _extra4(0)
            , _extra5(0) {
    }
    ~lvdomElementFormatRec() {
    }
    void clear() {
        _x = _width = _y = _height = 0;
        _inner_width = _inner_x = _inner_y = _baseline = 0;
        _usable_left_overflow = _usable_right_overflow = 0;
        _top_overflow = _bottom_overflow = 0;
        _lang_node_idx = _listprop_node_idx = 0;
        _flags = _extra0 = 0;
        _extra1 = _extra2 = _extra3 = _extra4 = _extra5 = 0;
    }
    bool operator==(lvdomElementFormatRec& v) {
        return (_height == v._height && _y == v._y && _width == v._width && _x == v._x &&
                _inner_width == v._inner_width && _inner_x == v._inner_x &&
                _inner_y == v._inner_y && _baseline == v._baseline &&
                _usable_left_overflow == v._usable_left_overflow && _usable_right_overflow == v._usable_right_overflow &&
                _top_overflow == v._top_overflow && _bottom_overflow == v._bottom_overflow &&
                _lang_node_idx == v._lang_node_idx && _listprop_node_idx == v._listprop_node_idx &&
                _flags == v._flags && _extra0 == v._extra0 &&
                _extra1 == v._extra1 && _extra2 == v._extra2 && _extra3 == v._extra3 &&
                _extra4 == v._extra4 && _extra5 == v._extra5);
    }
    bool operator!=(lvdomElementFormatRec& v) {
        return (_height != v._height || _y != v._y || _width != v._width || _x != v._x ||
                _inner_width != v._inner_width || _inner_x != v._inner_x ||
                _inner_y != v._inner_y || _baseline != v._baseline ||
                _usable_left_overflow != v._usable_left_overflow || _usable_right_overflow != v._usable_right_overflow ||
                _top_overflow != v._top_overflow || _bottom_overflow != v._bottom_overflow ||
                _lang_node_idx != v._lang_node_idx || _listprop_node_idx != v._listprop_node_idx ||
                _flags != v._flags || _extra0 != v._extra0 ||
                _extra1 != v._extra1 || _extra2 != v._extra2 || _extra3 != v._extra3 ||
                _extra4 != v._extra4 || _extra5 != v._extra5);
    }
    // Get/Set
    int getX() const {
        return _x;
    }
    int getY() const {
        return _y;
    }
    int getWidth() const {
        return _width;
    }
    int getHeight() const {
        return _height;
    }
    void getRect(lvRect& rc) const {
        rc.left = _x;
        rc.top = _y;
        rc.right = _x + _width;
        rc.bottom = _y + _height;
    }
    void setX(int x) {
        _x = x;
    }
    void setY(int y) {
        _y = y;
    }
    void setWidth(int w) {
        _width = w;
    }
    void setHeight(int h) {
        _height = h;
    }
    // No real need for setters/getters for the other fields, RenderRectAccessor
    // extends this class and can access them freely.
};

/// calculate cache record hash
lUInt32 calcHash(css_style_rec_t& rec);
/// calculate font instance record hash
lUInt32 calcHash(font_ref_t& rec);
/// calculate cache record hash
inline lUInt32 calcHash(css_style_ref_t& rec) {
    return rec.isNull() ? 0 : calcHash(*rec.get());
}

/// splits string like "Arial", Times New Roman, Courier;  into list
// returns number of characters processed
int splitPropertyValueList(const char* fontNames, lString8Collection& list);

/// joins list into string of comma separated quoted values
lString8 joinPropertyValueList(const lString8Collection& list);

#endif // __LV_STYLES_H_INCLUDED__
