package usda.weru.weps.location.mode;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.measure.Measurable;
import javax.measure.quantity.Length;
import org.apache.log4j.Logger;
import usda.weru.util.Caster;
import usda.weru.util.ConfigData;
import usda.weru.util.FireAllContext;
import usda.weru.util.LoadingContext;
import usda.weru.util.ReportContext;
import usda.weru.weps.RunFileBean;
import usda.weru.weps.location.CligenDataModel;
import usda.weru.weps.location.Station;
import usda.weru.weps.location.StationDataModel;
import usda.weru.weps.location.StationModeController;
import usda.weru.weps.location.WindgenDataModel;
import usda.weru.weps.location.chooser.StationChooser;
import usda.weru.weps.location.mode.AbstractStationModeHandler.BeanState;

/**
 *
 * @param <S> BeanState class
 * @author Joseph Levin <joelevin@weru.ksu.edu>
 */
public abstract class AbstractStationModeHandler<S extends AbstractStationModeHandler<?>.BeanState>
        implements StationModeController {

    private static final Logger LOGGER = Logger.getLogger(AbstractStationModeHandler.class);

    private final Object LOCK_STATE = new Object();

    private Map<RunFileBean, Map<RunFileBean.StationType, S>> c_beanState;

    /**
     *
     * @param bean
     * @param type
     */
    @Override
    public void installModel(RunFileBean bean, RunFileBean.StationType type) {
        if (isInstalled(bean, type)) {
            LOGGER.warn(getDisplayName() + " already installed on bean for " + type.name() + ".");
        }
        S state = getBeanState(bean, type);
        state.install();

    }

    /**
     *
     * @param bean
     * @param type
     */
    @Override
    public void uninstallModel(RunFileBean bean, RunFileBean.StationType type) {
        if (!isInstalled(bean, type)) {
            LOGGER.warn(getDisplayName() + " not installed on bean for " + type.name() + ".");
        }
        S state = getBeanState(bean, type);

        state.uninstall();

        //clean up the state
        removeBeanState(bean, type);
    }

    /**
     *
     * @param bean
     * @param type
     * @return
     */
    protected abstract S createBeanState(RunFileBean bean, RunFileBean.StationType type);

    @Override
    public void installView(StationChooser chooser) {
        BeanState state = Caster.<BeanState>cast(getBeanState(chooser.getRunFileBean(), chooser.getStationType()));
        state.addStationChooser(chooser);

        setView(chooser);
    }

    /**
     *
     * @param chooser
     */
    protected void setView(StationChooser chooser) {

    }

    @Override
    public void uninstallView(StationChooser chooser) {
        BeanState state = Caster.<BeanState>cast(getBeanState(chooser.getRunFileBean(), chooser.getStationType()));
        state.removeStationChooser(chooser);
    }

    /**
     *
     * @param state
     */
    protected void storeBeanState(S state) {
        synchronized (LOCK_STATE) {
            if (c_beanState == null) {
                c_beanState = new HashMap<RunFileBean, Map<RunFileBean.StationType, S>>();
            }

            Map<RunFileBean.StationType, S> levelTwo = c_beanState.get(state.getBean());

            if (levelTwo == null) {
                levelTwo = new HashMap<RunFileBean.StationType, S>();
                c_beanState.put(state.getBean(), levelTwo);
            }

            //store the state
            levelTwo.put(state.getType(), state);
        }

    }

    /**
     *
     * @param bean
     * @param type
     * @return
     */
    protected final boolean isInstalled(RunFileBean bean, RunFileBean.StationType type) {
        if (c_beanState != null) {
            Map<RunFileBean.StationType, S> levelTwo = c_beanState.get(bean);
            if (levelTwo != null) {
                return levelTwo.containsKey(type);
            }
        }
        return false;
    }

    /**
     *
     * @param bean
     * @param type
     * @return
     */
    protected S getBeanState(RunFileBean bean, RunFileBean.StationType type) {
        S state = getBeanState2(bean, type);
        if (state == null) {
            state = createBeanState(bean, type);
            storeBeanState(state);
        }
        return state;
    }

    private S getBeanState2(RunFileBean bean, RunFileBean.StationType type) {
        synchronized (LOCK_STATE) {
            if (c_beanState == null) {
                return null;
            }

            Map<RunFileBean.StationType, S> levelTwo = c_beanState.get(bean);

            if (levelTwo == null) {
                return null;
            }

            //return the state
            return levelTwo.get(type);
        }

    }

    /**
     *
     * @param bean
     * @param type
     */
    protected void removeBeanState(RunFileBean bean, RunFileBean.StationType type) {
        synchronized (LOCK_STATE) {
            if (c_beanState == null) {
                return;
            }

            Map<RunFileBean.StationType, S> levelTwo = c_beanState.get(bean);

            if (levelTwo != null) {
                levelTwo.remove(type);
                if (levelTwo.isEmpty()) {
                    c_beanState.remove(bean);

                }
            }

            if (c_beanState.isEmpty()) {
                c_beanState = null;
            }
        }
    }

    /**
     * The abstract implementation registers/unregisters itself as a VetoableChangeListener.
     * This allows the different modes to decide if
     * a selection is valid.
     * @param state
     * @param oldStation
     * @param newStation
     * @param loading indicates if the value is being validated during the loading process.
     * Some implementations may prevent invalid stations.
     * It is wise to tell the user this with some type of dialog.
     * @param event
     * @throws PropertyVetoException
     */
    protected void handleValidate(S state, Station oldStation, Station newStation, boolean loading,
            PropertyChangeEvent event) throws PropertyVetoException {
        //by default we accept all changes
    }

    /**
     *
     */
    public abstract class BeanState implements VetoableChangeListener {

        private final RunFileBean c_bean;
        private final RunFileBean.StationType c_type;

        private final List<StationChooser> c_choosers;

        /**
         *
         * @param bean
         * @param type
         */
        public BeanState(RunFileBean bean, RunFileBean.StationType type) {
            c_bean = bean;
            c_type = type;
            c_choosers = new LinkedList<StationChooser>();

        }

        /**
         *
         * @return
         */
        public Iterator<StationChooser> choosers() {
            return c_choosers.iterator();
        }

        /**
         *
         * @param chooser
         */
        public void addStationChooser(StationChooser chooser) {
            c_choosers.add(chooser);
        }

        /**
         *
         * @param chooser
         */
        public void removeStationChooser(StationChooser chooser) {
            c_choosers.remove(chooser);
        }

        /**
         *
         * @return
         */
        public RunFileBean getBean() {
            return c_bean;
        }

        /**
         *
         * @return
         */
        public Station getSelectedStation() {
            switch (getType()) {
                case Cligen:
                    return getBean().getCligenStation();
                case Windgen:
                    return getBean().getWindgenStation();
                default:
                    throw new IllegalStateException("not sure why we're here.");
            }
        }

        /**
         *
         * @param station
         */
        public void setSelectedStation(Station station) {
            switch (getType()) {
                case Cligen:
                    getBean().setCligenStation(station);
                    break;
                case Windgen:
                    getBean().setWindgenStation(station);
                    break;
                default:
                    throw new IllegalStateException("not sure why we're here.");
            }
        }

        /**
         *
         * @return
         */
        public Measurable<Length> getDistanceLimit() {
            switch (getType()) {
                case Cligen:
                    return ConfigData.getDefault().getCligenSearchRadius();
                case Windgen:
                    return ConfigData.getDefault().getWindgenSearchRadius();
                default:
                    throw new IllegalStateException("not sure why we're here.");
            }
        }

        /**
         *
         * @return
         */
        public int getCountLimit() {
            switch (getType()) {
                case Cligen:
                    return ConfigData.getDefault().getCligenSearchLimit();
                case Windgen:
                    return ConfigData.getDefault().getWindgenSearchLimit();
                default:
                    throw new IllegalStateException("not sure why we're here.");
            }
        }

        /**
         *
         * @return
         */
        public StationDataModel getDataModel() {
            switch (getType()) {
                case Cligen:
                    return CligenDataModel.getInstance();
                case Windgen:
                    return WindgenDataModel.getInstance();
                default:
                    throw new IllegalStateException("not sure why we're here.");
            }
        }

        /**
         *
         * @return
         */
        public RunFileBean.StationType getType() {
            return c_type;
        }

        /**
         *
         */
        public void install() {
            switch (getType()) {
                case Cligen:
                    getBean().addVetoableChangeListener(RunFileBean.PROP_CLIGEN_STATION, this);
                    break;
                case Windgen:
                    getBean().addVetoableChangeListener(RunFileBean.PROP_WINDGEN_STATION, this);
                    break;
            }
        }

        /**
         *
         */
        public void uninstall() {
            switch (getType()) {
                case Cligen:
                    getBean().removeVetoableChangeListener(RunFileBean.PROP_CLIGEN_STATION, this);
                    break;
                case Windgen:
                    getBean().removeVetoableChangeListener(RunFileBean.PROP_WINDGEN_STATION, this);
                    break;
            }
        }

        /**
         *
         * @param event
         * @throws PropertyVetoException
         */
        @Override
        public void vetoableChange(PropertyChangeEvent event) throws PropertyVetoException {
            if (FireAllContext.isInFireAll()) {
                //This should all work without the firealls
                return;
            }
            if (ReportContext.isInReport()) {
                //don't do any checks in reports.
                return;
            }
            boolean loading = LoadingContext.isLoading();
            Station oldStation = null;
            Station newStation = null;

            //cast objects to Stations
            if (event.getOldValue() instanceof Station) {
                oldStation = (Station) event.getOldValue();
            }

            if (event.getNewValue() instanceof Station) {
                newStation = (Station) event.getNewValue();
            }

            //pass off to the handler
            handleValidate(Caster.<S>cast(this), oldStation, newStation, loading, event);
        }

    }

}
