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

package usda.weru.weps.fuel;

import de.schlichtherle.io.File;
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

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

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

    public FuelTableModel(File 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);

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

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

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

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

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



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

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

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




    private File file;

    public void setFile(File 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();        
    }

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

    @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(Exception e){
                super.fireTableChanged(event);
            }
        }
    }


}
