/*
 * @(#)FigureAttributes.java 5.1
 *
 */

package CH.ifa.draw.figures;

import java.awt.Color;
import java.io.IOException;
import java.io.Serial;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.Hashtable;

import de.renew.draw.storables.ontology.Figure;
import de.renew.draw.storables.ontology.Storable;
import de.renew.draw.storables.ontology.StorableInput;
import de.renew.draw.storables.ontology.StorableOutput;

/**
 * A container for a figure's attributes. The attributes are stored
 * as key/value pairs.
 * <p>
 * {@link Figure}
 */
public class FigureAttributes implements Cloneable, Serializable {
    private static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(FigureAttributes.class);
    /*
     * Serialization support.
     */
    @Serial
    private static final long serialVersionUID = -6886355144423666716L;

    /**
     * Stores the attribute key-value pairs for the figure.
     * Keys are attribute names (as Strings) and values are their corresponding objects.
     */
    private Hashtable<String, Object> _map;

    /**
     * Serial number for the serialized data format of FigureAttributes.
     */
    @SuppressWarnings("unused")
    private final int _figureAttributesSerializedDataVersion = 1;

    /**
     * Constructs the FigureAttributes.
     */
    public FigureAttributes() {
        _map = new Hashtable<>();
    }

    /**
     * Gets the attribute with the given name.
     *
     * @param name attribute name
     * @return attribute or null if the key is not defined
     */
    public Object get(String name) {
        return _map.get(name);
    }

    /**
     * Sets the attribute with the given name and
     * overwrites its previous value.
     * If the value is <code>null</code>, the attribute
     * is removed from the container (ergo undefined).
     *
     * @param name attribute name
     * @param value new value
     */
    public void set(String name, Object value) {
        if (value == null) {
            _map.remove(name);
        } else {
            _map.put(name, value);
        }
    }

    /**
     * Tests if an attribute is defined.
     *
     * @param name attribute name
     * @return if the attribute is defined
     */
    public boolean hasDefined(String name) {
        return _map.containsKey(name);
    }

    /**
     * Returns an enumeration of all attribute keys.
     *
     * @return Enumeration of all defined attributes
     */
    public Enumeration<String> definedAttributes() {
        return _map.keys();
    }

    /**
     * Clones the attributes.
     */
    @Override
    public Object clone() {
        try {
            FigureAttributes a = (FigureAttributes) super.clone();
            a._map = new Hashtable<>(_map);
            return a;
        } catch (CloneNotSupportedException e) {
            throw new InternalError();
        }
    }

    /**
     * Reads the attributes from a StorableInput.
     * FigureAttributes store the following types directly:
     * Color, Boolean, String, Int. Other attribute types
     * have to implement the Storable interface, or they
     * have to be wrapped by an object that implements Storable.
     *
     * @param dr StorableInput from which the attributes will be read
     * @throws IOException if there are problems with the input stream
     * {@link Storable}
     * {@link #write}
     */
    public void read(StorableInput dr) throws IOException {
        String s = dr.readString();
        if (!"attributes".equalsIgnoreCase(s)) {
            throw new IOException("Attributes expected");
        }
        int version = dr.getVersion();

        //fMap = new Hashtable();
        int size = dr.readInt();
        for (int i = 0; i < size; i++) {
            String key = dr.readString();
            String valType = dr.readString();
            Object val = null;
            switch (valType) {
                case "Color" -> {
                    //Alpha values are not saved before version 11
                    if (version < 11) {
                        val = new Color(dr.readInt(), dr.readInt(), dr.readInt());
                    } else {
                        val = new Color(dr.readInt(), dr.readInt(), dr.readInt(), dr.readInt());
                    }
                }
                case "Boolean" -> val = Boolean.valueOf(dr.readString());
                case "String" -> val = dr.readString();
                case "Int" -> val = dr.readInt();
                case "Storable" -> val = dr.readStorable();
                case "UNKNOWN" -> {
                    continue;
                }
            }

            _map.put(key, val);
        }
    }

    /**
     * Writes the attributes to a StorableOutput.
     * FigureAttributes store the following types directly:
     * Color, Boolean, String, Int. Other attribute types
     * have to implement the Storable interface, or they
     * have to be wrapped by an object that implements Storable.
     *
     * @param dw StorableOutput to which the attributes will be written
     * {@link Storable}
     */
    public void write(StorableOutput dw) {
        dw.writeString("attributes");

        dw.writeInt(_map.size()); // number of attributes
        Enumeration<String> k = _map.keys();
        while (k.hasMoreElements()) {
            String s = k.nextElement();
            dw.writeString(s);
            Object v = _map.get(s);
            if (v instanceof String) {
                dw.writeString("String");
                dw.writeString((String) v);
            } else if (v instanceof Color) {
                dw.writeString("Color");
                dw.writeInt(((Color) v).getRed());
                dw.writeInt(((Color) v).getGreen());
                dw.writeInt(((Color) v).getBlue());
                dw.writeInt(((Color) v).getAlpha());
            } else if (v instanceof Boolean) {
                dw.writeString("Boolean");
                if ((Boolean) v) {
                    dw.writeString("TRUE");
                } else {
                    dw.writeString("FALSE");
                }
            } else if (v instanceof Integer) {
                dw.writeString("Int");
                dw.writeInt((Integer) v);
            } else if (v instanceof Storable) {
                dw.writeString("Storable");
                dw.writeStorable((Storable) v);
            } else {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(
                        FigureAttributes.class.getSimpleName() + ": Unknown attribute type: " + v);
                }
                dw.writeString("UNKNOWN");
            }
        }
    }
}