/*
 * 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.stylesheet.cascading;

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

/*package*/ class CascadingStyle extends EditableStyle {

	/**
	 * Dimension field position in bit map.
	 */
	private static final int DIMENSION_SHIFT = CascadingStylesheet.DIMENSION_INDEX;
	/**
	 * Horizontal alignment field position in bit map.
	 */
	private static final int HORIZONTAL_ALIGNMENT_SHIFT = CascadingStylesheet.HORIZONTAL_ALIGNMENT_INDEX;
	/**
	 * Vertical alignment field position in bit map.
	 */
	private static final int VERTICAL_ALIGNMENT_SHIFT = CascadingStylesheet.VERTICAL_ALIGNMENT_INDEX;
	/**
	 * Margin field position in bit map.
	 */
	private static final int MARGIN_SHIFT = CascadingStylesheet.MARGIN_INDEX;
	/**
	 * Border field position in bit map.
	 */
	private static final int BORDER_SHIFT = CascadingStylesheet.BORDER_INDEX;
	/**
	 * Padding field position in bit map.
	 */
	private static final int PADDING_SHIFT = CascadingStylesheet.PADDING_INDEX;
	/**
	 * Background field position in bit map.
	 */
	private static final int BACKGROUND_SHIFT = CascadingStylesheet.BACKGROUND_INDEX;
	/**
	 * Color field position in bit map.
	 */
	private static final int COLOR_SHIFT = CascadingStylesheet.COLOR_INDEX;
	/**
	 * Font field position in bit map.
	 */
	private static final int FONT_SHIFT = CascadingStylesheet.FONT_INDEX;
	/**
	 * Extra fields position in bit map.
	 */
	private static final int EXTRA_FIELDS_SHIFT = CascadingStylesheet.EXTRA_FIELD_1_INDEX;
	/**
	 * Map value of all fields which can be inherited.
	 */
	private static final int INHERIT_MASK = (0x1 << HORIZONTAL_ALIGNMENT_SHIFT) | (0x1 << COLOR_SHIFT)
			| (0x1 << FONT_SHIFT);
	/**
	 * Map value of all extra fields.
	 */
	private static final int EXTRA_FIELDS_MAP = ~((0x1 << DIMENSION_SHIFT) | (0x1 << HORIZONTAL_ALIGNMENT_SHIFT)
			| (0x1 << VERTICAL_ALIGNMENT_SHIFT) | (0x1 << MARGIN_SHIFT) | (0x1 << BORDER_SHIFT) | (0x1 << PADDING_SHIFT)
			| (0x1 << BACKGROUND_SHIFT) | (0x1 << COLOR_SHIFT) | (0x1 << FONT_SHIFT));

	/**
	 * Bit map for set fields.
	 */
	/* package */ short map;
	/**
	 * Cached hash code.
	 */
	private short hashCode;

	/* package */ CascadingStyle() {
		super();
	}

	/* package */ CascadingStyle(EditableStyle style) {
		super(style);
	}

	@Override
	public void setDimension(Dimension dimension) {
		super.setDimension(dimension);
		this.map |= 0x1 << DIMENSION_SHIFT;
	}

	@Override
	public void setHorizontalAlignment(int horizontalAlignment) {
		super.setHorizontalAlignment(horizontalAlignment);
		this.map |= 0x1 << HORIZONTAL_ALIGNMENT_SHIFT;
	}

	@Override
	public void setVerticalAlignment(int verticalAlignment) {
		super.setVerticalAlignment(verticalAlignment);
		this.map |= 0x1 << VERTICAL_ALIGNMENT_SHIFT;
	}

	@Override
	public void setMargin(Outline margin) {
		super.setMargin(margin);
		this.map |= 0x1 << MARGIN_SHIFT;
	}

	@Override
	public void setBorder(Outline border) {
		super.setBorder(border);
		this.map |= 0x1 << BORDER_SHIFT;
	}

	@Override
	public void setPadding(Outline padding) {
		super.setPadding(padding);
		this.map |= 0x1 << PADDING_SHIFT;
	}

	@Override
	public void setBackground(Background background) {
		super.setBackground(background);
		this.map |= 0x1 << BACKGROUND_SHIFT;
	}

	@Override
	public void setColor(int color) {
		super.setColor(color);
		this.map |= 0x1 << COLOR_SHIFT;
	}

	@Override
	public void setFont(Font font) {
		super.setFont(font);
		this.map |= 0x1 << FONT_SHIFT;
	}

	@Override
	public void setExtraObject(int fieldId, Object fieldValue) {
		super.setExtraObject(fieldId, fieldValue);
		this.map |= 0x1 << (EXTRA_FIELDS_SHIFT + fieldId);
	}

	/**
	 * Fills all missing attributes of this style with those in the given style.
	 *
	 * @param style
	 *            the style to merge with.
	 */
	/* package */ void merge(CascadingStyle style) {
		int map = this.map;
		int styleMap = style.map;
		int computedMap = ~map & styleMap;
		if (computedMap != 0) {
			merge(style, computedMap);
		}
	}

	private void merge(CascadingStyle style, int mergeMap) { // NOSONAR inline all merges for performance purpose.
		if ((mergeMap & (0x1 << DIMENSION_SHIFT)) != 0x0) {
			assert style.getDimension() != null;
			super.setDimension(style.getDimension());
		}
		if ((mergeMap & (0x1 << HORIZONTAL_ALIGNMENT_SHIFT)) != 0x0) {
			super.setHorizontalAlignment(style.getHorizontalAlignment());
		}
		if ((mergeMap & (0x1 << VERTICAL_ALIGNMENT_SHIFT)) != 0x0) {
			super.setVerticalAlignment(style.getVerticalAlignment());
		}
		if ((mergeMap & (0x1 << MARGIN_SHIFT)) != 0x0) {
			assert style.getMargin() != null;
			super.setMargin(style.getMargin());
		}
		if ((mergeMap & (0x1 << BORDER_SHIFT)) != 0x0) {
			assert style.getBorder() != null;
			super.setBorder(style.getBorder());
		}
		if ((mergeMap & (0x1 << PADDING_SHIFT)) != 0x0) {
			assert style.getPadding() != null;
			super.setPadding(style.getPadding());
		}
		if ((mergeMap & (0x1 << BACKGROUND_SHIFT)) != 0x0) {
			assert style.getBackground() != null;
			super.setBackground(style.getBackground());
		}
		if ((mergeMap & (0x1 << COLOR_SHIFT)) != 0x0) {
			super.setColor(style.getColor());
		}
		if ((mergeMap & (0x1 << FONT_SHIFT)) != 0x0) {
			assert style.getFont() != null;
			super.setFont(style.getFont());
		}
		if ((mergeMap & EXTRA_FIELDS_MAP) != 0x0) {
			int extraFieldsMergeMap = (mergeMap >> EXTRA_FIELDS_SHIFT);
			// iterate in reverse order to make sure that the extra fields array is resized only once
			for (int fieldId = MAX_EXTRA_FIELDS - 1; fieldId >= 0; fieldId--) {
				if ((extraFieldsMergeMap & (0x1 << fieldId)) != 0x0) {
					Object fieldValue = style.getExtraObject(fieldId, Object.class, this);
					assert (fieldValue != this);
					super.setExtraObject(fieldId, fieldValue);
				}
			}
		}
		this.map |= (short) mergeMap;
	}

	/**
	 * Fills all missing attributes of this style with the inherited ones from the given style.
	 *
	 * @param parentStyle
	 *            the style of the parent.
	 */
	/* package */ void inheritMerge(Style parentStyle) {
		int map = this.map;
		if ((map & (0x1 << HORIZONTAL_ALIGNMENT_SHIFT)) == 0x0) {
			super.setHorizontalAlignment((byte) parentStyle.getHorizontalAlignment());
		}
		if ((map & (0x1 << COLOR_SHIFT)) == 0x0) {
			super.setColor(parentStyle.getColor());
		}
		if ((map & (0x1 << FONT_SHIFT)) == 0x0) {
			super.setFont(parentStyle.getFont());
		}
		this.map |= INHERIT_MASK;
	}

	@Override
	public boolean equals(@Nullable Object obj) {
		return super.equals(obj);
	}

	/* package */ void updateHashCode() {
		this.hashCode = (short) super.hashCode();
	}

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