/*
 * Copyright 2019-2024 MicroEJ Corp. All rights reserved.
 * This library is provided in source code for use, modification and test, subject to license terms.
 * Any modification of the source code will break MicroEJ Corp. warranties on the whole library.
 */
package ej.mwt.style;

import ej.annotation.Nullable;
import ej.microui.display.Font;
import ej.mwt.style.background.Background;
import ej.mwt.style.dimension.Dimension;
import ej.mwt.style.outline.Outline;
import ej.mwt.util.Alignment;

/**
 * An editable style is a simple implementation of a style. It allows to set all the attributes of a style.
 */
public class EditableStyle implements Style {

    /**
     * The maximum number of extra fields.
     */
    public static final int MAX_EXTRA_FIELDS = 7;

    /**
     * Creates an editable style from the default style.
     */
    public EditableStyle() {
        this.dimension = DefaultStyle.DIMENSION;
        this.horizontalAlignment = DefaultStyle.HORIZONTAL_ALIGNMENT;
        this.verticalAlignment = DefaultStyle.VERTICAL_ALIGNMENT;
        this.margin = DefaultStyle.MARGIN;
        this.border = DefaultStyle.BORDER;
        this.padding = DefaultStyle.PADDING;
        this.background = DefaultStyle.BACKGROUND;
        this.color = DefaultStyle.COLOR;
        this.font = Font.getDefaultFont();
        this.extraFields = EMPTY_EXTRA_FIELDS;
    }

    /**
     * Creates an editable style from an other one.
     *
     * @param style
     *            the editable style to copy.
     */
    public EditableStyle(EditableStyle style) {
        this.dimension = style.dimension;
        this.horizontalAlignment = style.horizontalAlignment;
        this.verticalAlignment = style.verticalAlignment;
        this.margin = style.margin;
        this.border = style.border;
        this.padding = style.padding;
        this.background = style.background;
        this.color = style.color;
        this.font = style.font;
        this.extraFields = (style.extraFields == EMPTY_EXTRA_FIELDS ? EMPTY_EXTRA_FIELDS : style.extraFields.clone());
    }

    @Override
    public Dimension getDimension() {
        return this.dimension;
    }

    @Override
    public int getHorizontalAlignment() {
        return this.horizontalAlignment;
    }

    @Override
    public int getVerticalAlignment() {
        return this.verticalAlignment;
    }

    @Override
    public Outline getMargin() {
        return this.margin;
    }

    @Override
    public Outline getBorder() {
        return this.border;
    }

    @Override
    public Outline getPadding() {
        return this.padding;
    }

    @Override
    public Background getBackground() {
        return this.background;
    }

    @Override
    public int getColor() {
        return this.color;
    }

    @Override
    public Font getFont() {
        return this.font;
    }

    @Override
    public <T> T getExtraObject(int fieldId, Class<T> clazz, T defaultValue) {
        validateExtraFieldId(fieldId);
        Object[] extraFields = this.extraFields;
        if (fieldId < extraFields.length) {
            Object value = extraFields[fieldId];
            if (value != null) {
                T castedValue = clazz.cast(value);
                assert (castedValue != null);
                return castedValue;
            }
        }
        return defaultValue;
    }

    @Override
    public int getExtraInt(int fieldId, int defaultValue) {
        validateExtraFieldId(fieldId);
        Object[] extraFields = this.extraFields;
        if (fieldId < extraFields.length) {
            Object value = extraFields[fieldId];
            if (value != null) {
                return ((Integer) value).intValue();
            }
        }
        return defaultValue;
    }

    @Override
    public float getExtraFloat(int fieldId, float defaultValue) {
        validateExtraFieldId(fieldId);
        Object[] extraFields = this.extraFields;
        if (fieldId < extraFields.length) {
            Object value = extraFields[fieldId];
            if (value != null) {
                return ((Float) value).floatValue();
            }
        }
        return defaultValue;
    }

    /**
     * Sets the dimension.
     *
     * @param dimension
     *            the dimension.
     */
    public void setDimension(Dimension dimension) {
        this.dimension = dimension;
    }

    /**
     * Sets the horizontal alignment.
     *
     * The horizontal alignment must be equal to one of the horizontal constants ({@link Alignment#LEFT},
     * {@link Alignment#HCENTER} or {@link Alignment#RIGHT}).
     *
     * @param horizontalAlignment
     *            the horizontal alignment.
     * @throws IllegalArgumentException
     *             if the horizontal alignment is not valid.
     * @see Alignment#validateHorizontalAlignment(int)
     */
    public void setHorizontalAlignment(int horizontalAlignment) {
        Alignment.validateHorizontalAlignment(horizontalAlignment);
        this.horizontalAlignment = (byte) horizontalAlignment;
    }

    /**
     * Sets the vertical alignment.
     *
     * The vertical alignment must be equal to one of the vertical constants ({@link Alignment#TOP},
     * {@link Alignment#VCENTER} or {@link Alignment#BOTTOM}).
     *
     * @param verticalAlignment
     *            the vertical alignment.
     * @throws IllegalArgumentException
     *             if the vertical alignment is not valid.
     * @see Alignment#validateVerticalAlignment(int)
     */
    public void setVerticalAlignment(int verticalAlignment) {
        Alignment.validateVerticalAlignment(verticalAlignment);
        this.verticalAlignment = (byte) verticalAlignment;
    }

    /**
     * Sets the margin.
     *
     * @param margin
     *            the margin.
     */
    public void setMargin(Outline margin) {
        this.margin = margin;
    }

    /**
     * Sets the border.
     *
     * @param border
     *            the border.
     */
    public void setBorder(Outline border) {
        this.border = border;
    }

    /**
     * Sets the padding.
     *
     * @param padding
     *            the padding.
     */
    public void setPadding(Outline padding) {
        this.padding = padding;
    }

    /**
     * Sets the background.
     *
     * @param background
     *            the background.
     */
    public void setBackground(Background background) {
        this.background = background;
    }

    /**
     * Sets the color.
     *
     * @param color
     *            the color.
     */
    public void setColor(int color) {
        this.color = color;
    }

    /**
     * Sets the font.
     *
     * @param font
     *            the font.
     */
    public void setFont(Font font) {
        this.font = font;
    }

    /**
     * Sets an extra field to the given object value.
     *
     * @param fieldId
     *            the ID of the extra field.
     * @param fieldValue
     *            the value of the extra field.
     * @throws IllegalArgumentException
     *             if the given ID is not within the [0, 6] interval.
     */
    public void setExtraObject(int fieldId, Object fieldValue) {
        validateExtraFieldId(fieldId);
        this.extraFields = setExtraField(this.extraFields, fieldId, fieldValue);
    }

    /**
     * Sets an extra field to the given int value.
     *
     * @param fieldId
     *            the ID of the extra field.
     * @param fieldValue
     *            the value of the extra field.
     * @throws IllegalArgumentException
     *             if the given ID is not within the [0, 6] interval.
     */
    public void setExtraInt(int fieldId, int fieldValue) {
        setExtraObject(fieldId, Integer.valueOf(fieldValue));
    }

    /**
     * Sets an extra field to the given float value.
     *
     * @param fieldId
     *            the ID of the extra field.
     * @param fieldValue
     *            the value of the extra field.
     * @throws IllegalArgumentException
     *             if the given ID is not within the [0, 6] interval.
     */
    public void setExtraFloat(int fieldId, float fieldValue) {
        setExtraObject(fieldId, Float.valueOf(fieldValue));
    }

    /**
     * Validates that the extra field ID is correct.
     *
     * @param fieldId
     *            the ID to validate.
     * @throws IllegalArgumentException
     *             if the given ID is not within the [0, 6] interval.
     */
    protected static void validateExtraFieldId(int fieldId) {
        if (fieldId < 0 || fieldId >= MAX_EXTRA_FIELDS) {
            throw new IllegalArgumentException();
        }
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        if (obj == this) {
            return true;
        } else if (!(obj instanceof EditableStyle)) {
            return false;
        } else {
            EditableStyle other = (EditableStyle) obj;
            return this.dimension.equals(other.dimension) && this.horizontalAlignment == other.horizontalAlignment && this.verticalAlignment == other.verticalAlignment && this.margin.equals(other.margin) && this.border.equals(other.border) && this.padding.equals(other.padding) && this.background.equals(other.background) && this.color == other.color && this.font == other.font && identityEquals(this.extraFields, other.extraFields);
        }
    }

    @Override
    public int hashCode() {
        // don't call this.font.hashCode() to avoid embedding the hashCode() method of every class (M0081MEJA-1240)
        return this.dimension.hashCode() - this.horizontalAlignment + this.verticalAlignment - this.margin.hashCode() + this.border.hashCode() - this.padding.hashCode() + this.background.hashCode() - this.color + this.extraFields.length;
    }
}
