package de.renew.propertymanagementgui.reactivecomponents;

import java.awt.Container;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JComponent;


/**
 * This abstract class represents a Component which represents a graphical element in the GUI.
 * Implementing classes only need to implement the render method that returns the graphical representation of the component.
 * Inside the render method, the implementing class can use the useState method to create and use states.
 * When a state is changed by setting it, the graphical component will automatically be updated
 */
public abstract class Component {
    // A map to hold the states of the component
    private final Map<String, State<String>> _states;
    // The graphical representation of the component
    private JComponent _graphic;

    /**
     * Constructor for the Component class.
     * Initializes the _states map.
     */
    public Component() {
        _states = new HashMap<>();
    }

    /**
     * Abstract method to render the component.
     * This method should be implemented by subclasses.
     *
     * @return the rendered JComponent
     */
    protected abstract JComponent render();

    /**
     * Gets the graphical representation of the component.
     * If no representation is present, a new one is created.
     *
     * @return the graphical representation of the component
     */
    public final JComponent get() {
        _graphic = render();
        return _graphic;
    }

    /**
     * Method to update the component by re-rendering it.
     */
    public final void update() {
        Container parent = _graphic.getParent();

        if (parent != null) {
            parent.remove(_graphic);
            _graphic = render();
            parent.add(_graphic);
            parent.revalidate();
            parent.repaint();
        }
    }

    /**
     * Method to use a state of the component.
     * Returns a state with the given value
     * Throws an exception if a state with the same key already exists with a different type
     *
     * @return The state
     * @throws IllegalArgumentException if a state with the same key already exists with a different type
     */
    protected State<String> useState() {
        String key = "activeKey";
        String plugin = "activePlugin";

        if (_states.containsKey(key)) {
            if (_states.get(key).getValue().getClass() != plugin.getClass()) {
                throw new IllegalArgumentException(
                    "State " + key + " already exists with type "
                        + _states.get(key).getValue().getClass() + " and cannot be changed to "
                        + plugin.getClass());
            }
            return _states.get(key);
        } else {
            State<String> state = new State<>(plugin, this::update);
            _states.put(key, state);
            return state;
        }
    }
}