package usda.weru.weps.location;

import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.file.TFileReader;
import edu.umd.cs.findbugs.ba.obl.Path;
import java.beans.PropertyChangeEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.DirectoryIteratorException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import javax.measure.Measurable;
import javax.measure.Measure;
import javax.measure.quantity.Length;
import javax.measure.unit.NonSI;
import javax.measure.unit.SI;
import org.apache.log4j.Logger;
import org.jscience.geography.coordinates.LatLong;
import usda.weru.util.ConfigData;

/**
 *
 * @author Joseph Levin <joelevin@weru.ksu.edu>
 */
public class CligenDataModel extends AbstractStationDataModel {

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

    private final Object DATA_LOCK = new Object();

    private static CligenDataModel c_instance;

    private List<CligenStation> c_stations;

    /**
     *
     * @return
     */
    public static final synchronized CligenDataModel getInstance() {
        if (c_instance == null) {
            c_instance = new CligenDataModel();
        }
        return c_instance;
    }

    /**
     *
     * @param state
     * @param id
     * @return
     */
    public CligenStation getStation(long state, long id) {
        for (CligenStation station : stations()) {
            if (station.getId() == id && station.getState() == (state)) {
                return station;
            }
        }
        return null;
    }

    @Override
    protected void configDataPropertyChange(PropertyChangeEvent event) {
        super.configDataPropertyChange(event);
        switch (event.getPropertyName()) {
            case ConfigData.CliIndex:
                //the backing data has changed.
                clearStations();
                break;
            case ConfigData.GISData:
                fireGISDataChanged();
                break;
        }
    }

    public void resetStations() {
        this.clearStations();
    }

    public void clearStations() {
        synchronized (DATA_LOCK) {
            if (c_stations == null) {
                //nothing has changed
                return;
            }
            c_stations = null;
        }
        clearCache();
        fireStationDataChanged();
    }

    @Override
    public List<CligenStation> stations() {
        //lock around the data
        synchronized (DATA_LOCK) {
            if (c_stations == null) {
                c_stations = new LinkedList<CligenStation>();
                readFromFile(c_stations);
            }
            return c_stations;
        }
    }

    private void readFromFile(List<CligenStation> stations) {
        String path = ConfigData.getDefault().getDataParsed(ConfigData.CliIndex);
        if (path == null) {
            return;
        }
        TFile file = new TFile(path);
        if (!file.canRead()) {
            LOGGER.warn("Can not read file: " + file.getAbsolutePath());
        }
        BufferedReader in = null;
        try {
            in = new BufferedReader(new TFileReader(file.getCanOrAbsFile()));
            for (String line = in.readLine(); line != null; line = in.readLine()) {
                line = line.trim();
                if (line.startsWith("#") || line.length() == 0) {
                    //skip comments and blank lines
                    continue;
                }

                String[] parts = line.split("\\s+");

                int offset = 0;
                long state = Long.parseLong(parts[offset + 0].trim());
                long id = Long.parseLong(parts[offset + 1].trim());

                double lat = parseCoordinate(parts, offset + 3);
                double lon = parseCoordinate(parts, offset + 6);

                LatLong latlong = LatLong.valueOf(lat, lon, NonSI.DEGREE_ANGLE);

                double elevationMeters = Double.parseDouble(parts[offset + 9].trim());
                Measurable<Length> elevation = Measure.valueOf(elevationMeters, SI.METER);

                String name = join(parts, offset + 11);

                CligenStation station = new CligenStation(latlong, elevation, name, state, id);

                if (!stations.contains(station)) {
                    stations.add(station);
                } else {
                    LOGGER.warn("Duplicate cligen station id.  Skipping record: " + station);
                }

            }

            in.close();
        } catch (FileNotFoundException fnfe) {
            LOGGER.error("Cligen index file not found:" + file.getPath());
        } catch (IOException | NumberFormatException e) {
            LOGGER.error("Error reading the cligen index file:" + file.getPath(), e);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    LOGGER.error("Unable to close stream.", e);
                }
            }
        }
    }

    private String join(String[] parts, int start) {
        StringBuilder buffer = new StringBuilder();
        for (int i = start; i < parts.length; i++) {
            buffer.append(parts[i]);
            buffer.append(" ");
        }
        return buffer.toString().trim();

    }

    private double parseCoordinate(String[] parts, int start) {
        double degree = Double.parseDouble(parts[start].trim());
        double minutes = Double.parseDouble(parts[start + 1].trim());

        double value = degree + (minutes / 60);

        String quadrant = parts[start + 2].trim();
        if ("S".equals(quadrant) || "W".equals(quadrant)) {
            value = value * -1;
        }

        return value;
    }

    /* modifications/additions by: alexblum
    * function dir_files working with getFiles determines
    * the files from the db/cligen directory 
    * that have entensions .idx;
    * this determines what files need to be added the the layers of MapViewer 
     */
    public void dir_files(ArrayList<String> files) {
        String path = ConfigData.getDefault().getDataParsed(ConfigData.CliIndex);
        if (path == null) {
            return;
        }
        TFile file = new TFile(path);
        String f_path = file.getCanOrAbsPath();
        int last_slash = f_path.lastIndexOf("\\");
        if (last_slash == -1) {
            last_slash = f_path.lastIndexOf("/");
        }
        f_path = f_path.substring(0, last_slash + 1);
        getFiles(f_path, files);
    }

    public void getFiles(String path, ArrayList<String> files) {
        File folder = new File(path);
        File[] listOfFiles = folder.listFiles();

        for (int i = 0; i < listOfFiles.length; i++) {
            if (listOfFiles[i].isFile()) {
                if (listOfFiles[i].toString().contains(".idx")) {
                    files.add(listOfFiles[i].toString());
                }
            } else if (listOfFiles[i].isDirectory()) {
                //return the director if needed
                //if needed read from directory in current directory
            }
        }
    }

    //create new list to make sure file F.idx is processed
    //this will allow for the creation of multiple cligen stations for "View Map"
    //according to the files in the db/Cligen directory ending with .idx
    public List<CligenStation> stations(String f) {
        //lock around the data
        synchronized (DATA_LOCK) {
            c_stations = new LinkedList<CligenStation>();
            readFromFile(c_stations, f);
            return c_stations;
        }
    }

    private void readFromFile(List<CligenStation> stations, String f) {
        if (f == null) {
            return;
        }
        TFile file = new TFile(f);
        if (!file.canRead()) {
            LOGGER.warn("Can not read file: " + file.getAbsolutePath());
        }
        BufferedReader in = null;
        try {
            in = new BufferedReader(new TFileReader(file.getCanOrAbsFile()));
            for (String line = in.readLine(); line != null; line = in.readLine()) {
                line = line.trim();
                if (line.startsWith("#") || line.length() == 0) {
                    //skip comments and blank lines
                    continue;
                }

                String[] parts = line.split("\\s+");
                long state = 0;
                long id = 0;
                int offset = 0;
                try {
                    state = Long.parseLong(parts[offset + 0].trim());
                    id = Long.parseLong(parts[offset + 1].trim());
                } catch (NumberFormatException nfe) {
                    System.err.println("error in line: " + line);
                    continue;
                }
                double lat = 0;
                double lon = 0;

                try {
                    lat = parseCoordinate(parts, offset + 3);
                    lon = parseCoordinate(parts, offset + 6);
                } catch (NumberFormatException nfe) {
                    System.err.println("error in line: " + line);
                    continue;
                }
                LatLong latlong = LatLong.valueOf(lat, lon, NonSI.DEGREE_ANGLE);
                double elevationMeters = 0;
                try {
                    elevationMeters = Double.parseDouble(parts[offset + 9].trim());
                } catch (NumberFormatException nfe) {
                    System.err.println("error in line: " + line);
                    continue;
                }

                Measurable<Length> elevation = Measure.valueOf(elevationMeters, SI.METER);

                String name = join(parts, offset + 11);

                CligenStation station = new CligenStation(latlong, elevation, name, state, id);

                if (!stations.contains(station)) {
                    stations.add(station);
                } else {
                    LOGGER.warn("Duplicate cligen station id.  Skipping record: " + station);
                }

            }

            in.close();
        } catch (FileNotFoundException fnfe) {
            LOGGER.error("Cligen index file not found:" + file.getPath());
        } catch (IOException | NumberFormatException e) {
            LOGGER.error("Error reading the cligen index file:" + file.getPath(), e);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    LOGGER.error("Unable to close stream.", e);
                }
            }
        }
    }
}
