package usda.weru.weps.fuel;

import de.schlichtherle.truezip.file.TFile;
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.measure.Measurable;
import javax.measure.Measure;
import javax.measure.unit.NonSI;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;
import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;
import org.jscience.economics.money.Currency;
import usda.weru.util.ConfigData;
import usda.weru.util.Units;
import usda.weru.util.Util;

/**
 *
 * @author josephalevin
 */
public class FuelTableModel extends AbstractTableModel {

    private static final long serialVersionUID = 1L;

    private List<Fuel> fuels;
    private Map<String, Fuel> index;

    private enum Column {

        Name("Fuel", String.class) {

                    @Override
                    public Object getValue(FuelTableModel model, Fuel fuel) {
                        return fuel.getDisplayName();
                    }

                },
        Description("Description", String.class) {

                    @Override
                    public Object getValue(FuelTableModel model, Fuel fuel) {
                        return fuel.getDescription();
                    }

                },
        DesielEquivalence("Diesel", Double.class) {

                    @Override
                    public Object getValue(FuelTableModel model, Fuel fuel) {
                        Fuel diesel = model.getFuel("diesel");
                        if (diesel != null && fuel != null) {
                            double fuelEnergy = fuel.getEnergy().doubleValue(model.getEnergyUnits());
                            double diseselEnergy = diesel.getEnergy().doubleValue(model.getEnergyUnits());
                            return fuelEnergy / diseselEnergy;

                        } else {
                            return null;
                        }

                    }

                },
        Energy("Energy", Double.class) {

                    @Override
                    public Object getValue(FuelTableModel model, Fuel fuel) {
                        Measurable<EnergyPerVolume> energy = fuel.getEnergy();
                        return energy.doubleValue(model.getEnergyUnits());
                    }

                    @Override
                    public String getDisplayName(FuelTableModel model) {
                        return model.getEnergyUnits().toString();
                    }

                },
        Price("Price", Double.class) {

                    @Override
                    public Object getValue(FuelTableModel model, Fuel fuel) {
                        Measurable<PricePerVolume> price = fuel.getPrice();
                        return price.doubleValue(model.getPriceUnits());
                    }

                    @Override
                    public String getDisplayName(FuelTableModel model) {
                        return model.getPriceUnits().toString();
                    }

                    @Override
                    public boolean isEditable() {
                        return true;
                    }

                    @Override
                    public void setValue(FuelTableModel model, Fuel fuel, Object value, int row, int column) {
                        if (value instanceof Number) {
                            double d = ((Number) value).doubleValue();
                            Measurable<PricePerVolume> price = Measure.valueOf(d, model.getPriceUnits());
                            fuel.setPrice(price);

                            //will be put on the event queue automagically!
                            model.fireTableRowsUpdated(row, row);
                        }
                    }

                };
        private final String displayName;
        private final Class<?> type;

        private Column(String displayName, Class<?> type) {
            this.displayName = displayName;
            this.type = type;
        }

        public Class<?> getType() {
            return type;
        }

        public String getDisplayName(FuelTableModel model) {
            return displayName;
        }

        public Object getValue(FuelTableModel model, Fuel fuel) {
            return null;
        }

        public boolean isEditable() {
            return false;
        }

        public void setValue(FuelTableModel model, Fuel fuel, Object value, int row, int column) {
            //do nothing
        }

    };

    private Unit<EnergyPerVolume> getEnergyUnits() {
        if (Util.SIUnits.equals(units)) {
            return SI.KILO(SI.JOULE).divide(NonSI.LITER).asType(EnergyPerVolume.class);
        } else {
            return Units.createEnergyPerVolumeUnit(Units.BTU, NonSI.GALLON_LIQUID_US);
        }
    }

    private Unit<PricePerVolume> getPriceUnits() {
        if (Util.SIUnits.equals(units)) {
            return Units.createPricePerVolumeUnit(Currency.USD, NonSI.LITER);
        } else {
            return Units.createPricePerVolumeUnit(Currency.USD, NonSI.GALLON_LIQUID_US);
        }
    }

    /**
     *
     * @param name
     * @return
     */
    public Fuel getFuel(String name) {
        if (index == null) {
            enqueue();
            return null;
        }
        return index.get(name);
    }

    /**
     *
     * @param file
     */
    public FuelTableModel(TFile file) {
        this.file = file;
        ConfigData.getDefault().addPropertyChangeListener(ConfigData.Units, new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                setUnits(ConfigData.getDefault().getData(ConfigData.Units));
            }
        });
    }

    private String units = ConfigData.getDefault().getData(ConfigData.Units);

    /**
     *
     * @param units
     */
    public void setUnits(String units) {
        if (units != null && units.equals(this.units)) {
            return;
        }
        this.units = units;
        fireTableStructureChanged();
    }

    /**
     *
     * @return
     */
    @Override
    public int getRowCount() {
        if (fuels == null) {
            enqueue();
            return 0;
        }
        return fuels.size();
    }

    /**
     *
     * @return
     */
    @Override
    public int getColumnCount() {
        return Column.values().length;
    }

    /**
     *
     * @param rowIndex
     * @param columnIndex
     * @return
     */
    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        return Column.values()[columnIndex].getValue(this, fuels.get(rowIndex));
    }

    /**
     *
     * @param value
     * @param rowIndex
     * @param columnIndex
     */
    @Override
    public void setValueAt(Object value, int rowIndex, int columnIndex) {
        Column.values()[columnIndex].setValue(this, fuels.get(rowIndex), value, rowIndex, columnIndex);
    }

    /**
     *
     * @param column
     * @return
     */
    @Override
    public String getColumnName(int column) {
        return Column.values()[column].getDisplayName(this);
    }

    /**
     *
     * @param columnIndex
     * @return
     */
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return Column.values()[columnIndex].getType();
    }

    /**
     *
     * @param rowIndex
     * @param columnIndex
     * @return
     */
    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return Column.values()[columnIndex].isEditable();
    }

    private TFile file;

    /**
     *
     * @param file
     */
    public void setFile(TFile file) {
        this.file = file;
        enqueue();
    }

    boolean isEnqueued;

    private synchronized void enqueue() {
        if (isEnqueued) {
            return;
        } else {
            isEnqueued = true;
        }
        Thread thread = new Thread("Load fuel table thread") {

            @Override
            public void run() {
                try {
                    FuelDAO dao = new FuelDAO(file);
                    List<Fuel> result = new ArrayList<Fuel>();

                    for (String name : dao.getFuelNames()) {
                        Fuel fuel = dao.getFuel(name);
                        result.add(fuel);
                    }

                    FuelTableModel.this.fuels = result;

                    Collections.sort(result, new Comparator<Fuel>() {

                        @Override
                        public int compare(Fuel o1, Fuel o2) {
                            return o1.getDisplayName().compareTo(o2.getDisplayName());
                        }

                    });

                    index = new HashMap<String, Fuel>();
                    for (Fuel fuel : result) {
                        index.put(fuel.getName(), fuel);
                    }

                    //fire event, will be enqueue for the eventqueue later
                    fireTableDataChanged();
                } finally {
                    isEnqueued = false;
                }

            }

        };
        thread.start();
    }

    /**
     *
     * @return
     */
    public Fuel[] getFuels() {
        return fuels.toArray(new Fuel[fuels.size()]);
    }

    /**
     *
     * @param event
     */
    @Override
    public void fireTableChanged(final TableModelEvent event) {
        if (EventQueue.isDispatchThread()) {
            super.fireTableChanged(event);
        } else {
            try {
                EventQueue.invokeAndWait(new Runnable() {

                    @Override
                    public void run() {
                        FuelTableModel.super.fireTableChanged(event);
                    }
                });
            } catch (InterruptedException | InvocationTargetException e) {
                super.fireTableChanged(event);
            }
        }
    }

}
