/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

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.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 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;

    @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();

    }

    @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);
    }

    protected abstract S createBeanState(RunFileBean bean, RunFileBean.StationType type);

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

        setView(chooser);
    }

    protected void setView(StationChooser chooser){

    }


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

    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);
        }

    }

    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;
    }

    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);
        }

    }

    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 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.
     * @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;

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

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

        public void addStationChooser(StationChooser chooser){
            c_choosers.add(chooser);
        }

        public void removeStationChooser(StationChooser chooser){
            c_choosers.remove(chooser);
        }

        public RunFileBean getBean(){
            return c_bean;
        }

        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.");
            }
        }

        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.");
            }
        }

        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.");
            }
        }

        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.");
            }
        }

        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.");
            }
        }

        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;
            }
        }


        @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((S)this, oldStation, newStation, loading, event);

        }


        
    }



}
