package edu.sc.seis.sod.source.event;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.w3c.dom.Element;

import com.csvreader.CsvReader;

import edu.sc.seis.seisFile.TimeUtils;
import edu.sc.seis.sod.ConfigurationException;
import edu.sc.seis.sod.DOMHelper;
import edu.sc.seis.sod.UserConfigurationException;
import edu.sc.seis.sod.model.common.Location;
import edu.sc.seis.sod.model.common.ParameterRef;
import edu.sc.seis.sod.model.common.QuantityImpl;
import edu.sc.seis.sod.model.common.UnitImpl;
import edu.sc.seis.sod.model.event.CacheEvent;
import edu.sc.seis.sod.model.event.EventAttrImpl;
import edu.sc.seis.sod.model.event.FlinnEngdahlRegion;
import edu.sc.seis.sod.model.event.FlinnEngdahlType;
import edu.sc.seis.sod.model.event.Magnitude;
import edu.sc.seis.sod.model.event.OriginImpl;
import edu.sc.seis.sod.subsetter.AreaSubsetter;

/**
 * @author oliverpa
 * 
 * Created on Jul 1, 2005
 */
public class CSVEventSource extends SimpleEventSource {

    private static final String DEFAULT_CSV_SOURCE_NAME = "CSVEventSource";

    public CSVEventSource(Element config) throws ConfigurationException {
        super(config, DEFAULT_CSV_SOURCE_NAME);
        if (DOMHelper.hasElement(config, "filename")) {
            initFromFile(DOMHelper.extractText(config, "filename"));
            if (getName().equals(DEFAULT_CSV_SOURCE_NAME)) {
                name += " "+csvFilename;
            }
        } else if (DOMHelper.hasElement(config, "events")) {
            try {
                events = getEventsFromReader(new StringReader(DOMHelper.extractText(config, "events").trim()));
            } catch(IOException e) {
                throw new ConfigurationException("Unable to read events from:" + DOMHelper.extractText(config, "events"), e);
            }
        } else {
            throw new ConfigurationException("Can't find filename or events in configuration.");
        }
    }
    
    public CSVEventSource(String filename) throws ConfigurationException {
        super(filename);
        initFromFile(filename);
    }
    
    protected void initFromFile(String filename) throws ConfigurationException {
        this.csvFilename = filename;
        try {
            events = getEventsFromCSVFile(csvFilename);
        } catch(FileNotFoundException e) {
            throw new UserConfigurationException(e.getMessage()
                    + " as a event CSV file.");
        } catch(IOException e) {
            throw new ConfigurationException("Unable to read " + csvFilename, e);
        }
    }
    
    public String getDescription() {
        return "CSVEventSource: "+csvFilename;
    }

    public CacheEvent[] getEvents() {
        return events;
    }

    public static CacheEvent[] getEventsFromCSVFile(String filename)
            throws FileNotFoundException, IOException, ConfigurationException {
        return getEventsFromReader(AreaSubsetter.makeRelativeOrRecipeDirReader(filename));
    }

    public static CacheEvent[] getEventsFromReader(Reader reader)
            throws IOException, FileNotFoundException, ConfigurationException {
        List events = new ArrayList();
        CsvReader csvReader = new CsvReader(reader);
        csvReader.readHeaders();
        List headers = Arrays.asList(csvReader.getHeaders());
        for(int i = 0; i < headers.size(); i++) {
            String cur = (String)headers.get(i);
            if(!isValidField(cur)) {
                throw new UserConfigurationException(cur
                        + " is not a known CSV field.  "
                        + concatenateValidFields() + " are valid options.");
            }
        }
        while(csvReader.readRecord()) {
            // time to start populating field values
            // first up: the only required field...
            Instant time = TimeUtils.parseISOString(csvReader.get(TIME));
            float latitude = 0f;
            if(headers.contains(LATITUDE)) {
                latitude = Float.parseFloat(csvReader.get(LATITUDE));
            }
            float longitude = 0f;
            if(headers.contains(LONGITUDE)) {
                longitude = Float.parseFloat(csvReader.get(LONGITUDE));
            }
            double depth = 0f;
            if(headers.contains(DEPTH)) {
                depth = Double.parseDouble(csvReader.get(DEPTH));
            }
            UnitImpl depthUnit = UnitImpl.KILOMETER;
            if(headers.contains(DEPTH_UNITS)) {
                String unitName = csvReader.get(DEPTH_UNITS);
                try {
                    depthUnit = UnitImpl.getUnitFromString(unitName);
                } catch(NoSuchFieldException e) {
                    throw new UserConfigurationException(unitName
                            + " in record "
                            + csvReader.getCurrentRecord()
                            + " is not a valid unit name.  Try KILOMETER or METER");
                }
            }
            Location location = new Location(latitude,
                                             longitude,
                                             new QuantityImpl(0.0,
                                                              UnitImpl.METER),
                                             new QuantityImpl(depth, depthUnit));
            String defaultString = "csvEvent";
            String catalog = defaultString;
            if(headers.contains(CATALOG)) {
                catalog = csvReader.get(CATALOG);
            }
            String contributor = defaultString;
            if(headers.contains(CONTRIBUTOR)) {
                contributor = csvReader.get(CONTRIBUTOR);
            }
            Magnitude[] magnitudes = new Magnitude[0];
            String[] magValues, magTypes, magContribs;
            if(headers.contains(MAGNITUDE)) {
                magValues = csvReader.get(MAGNITUDE).split(":");
            } else {
                magValues = new String[] {"0"};
            }
            if(magValues.length > 1 || (headers.contains(MAGNITUDE_TYPE))) {
                magTypes = csvReader.get(MAGNITUDE_TYPE).split(":");
                if(magTypes.length != magValues.length) {
                    throw new UserConfigurationException("count of magnitude types does not match count of magnitude values in record "
                            + csvReader.getCurrentRecord()+", "+magTypes.length +"!="+ magValues.length);
                }
            } else {
                magTypes = new String[] {"M"};
            }
            if(magValues.length > 1 || headers.contains(MAGNITUDE_CONTRIBUTOR)) {
                magContribs = csvReader.get(MAGNITUDE_CONTRIBUTOR).split(":");
                if(magContribs.length != magValues.length) {
                    String[] tmp = magContribs;
                    magContribs = new String[magValues.length];
                    System.arraycopy(tmp, 0, magContribs, 0, tmp.length);
                    for(int i = tmp.length; i<magContribs.length; i++) {
                        magContribs[i] = UNKNOWN;
                    }
                }
            } else {
                magContribs = new String[] {UNKNOWN};
            }
            magnitudes = new Magnitude[magValues.length];
            for(int i = 0; i < magValues.length; i++) {
                magnitudes[i] = new Magnitude(magTypes[i],
                                              Float.parseFloat(magValues[i]),
                                              magContribs[i]);
            }
            OriginImpl origin = new OriginImpl("",
                                           catalog,
                                           contributor,
                                           time,
                                           location,
                                           magnitudes,
                                           new ParameterRef[0]);
            String name = defaultString;
            if(headers.contains(NAME)) {
                name = csvReader.get(NAME);
            }
            FlinnEngdahlType feType = FlinnEngdahlType.SEISMIC_REGION;
            if(headers.contains(FE_REGION_TYPE)) {
                int type = Integer.parseInt(csvReader.get(FE_REGION_TYPE));
                if(type == FlinnEngdahlType._SEISMIC_REGION) {
                    feType = FlinnEngdahlType.SEISMIC_REGION;
                } else if(type == FlinnEngdahlType._GEOGRAPHIC_REGION) {
                    feType = FlinnEngdahlType.GEOGRAPHIC_REGION;
                }
            }
            FlinnEngdahlRegion feRegion = new FlinnEngdahlRegion(feType, 0);
            if(headers.contains(FE_REGION)) {
                feRegion = new FlinnEngdahlRegion(feType,
                                                      Integer.parseInt(csvReader.get(FE_REGION)));
            }
            events.add(new CacheEvent(new EventAttrImpl(name, feRegion), origin));
        }
        return (CacheEvent[])events.toArray(new CacheEvent[0]);
    }

    public static String concatenateValidFields() {
        String allFields = "";
        for(int i = 0; i < FIELDS.length - 1; i++) {
            allFields += FIELDS[i] + ", ";
        }
        return allFields + FIELDS[FIELDS.length - 1];
    }

    private static boolean isValidField(String field) {
        for(int i = 0; i < FIELDS.length; i++) {
            if(field.equals(FIELDS[i])) {
                return true;
            }
        }
        return false;
    }

    public String toString() {
        return "CSVEventSource using " + csvFilename;
    }

    private CacheEvent[] events;

    // required
    public static final String TIME = "time";

    // optional
    public static final String LONGITUDE = "longitude";

    public static final String LATITUDE = "latitude";

    public static final String DEPTH = "depth";

    public static final String MAGNITUDE = "magnitude";

    public static final String CATALOG = "catalog";

    public static final String CONTRIBUTOR = "contributor";

    public static final String NAME = "name";

    public static final String FE_SEIS_REGION = "flinnEngdahlSeismicRegion";

    public static final String FE_GEO_REGION = "flinnEngdahlGeographicRegion";

    public static final String FE_REGION = "flinnEngdahlRegion";

    public static final String FE_REGION_TYPE = "flinnEngdahlRegionType";

    // defaultable
    public static final String DEPTH_UNITS = "depthUnits";

    public static final String MAGNITUDE_TYPE = "magnitudeType";

    public static final String MAGNITUDE_CONTRIBUTOR = "magnitudeContributor";

    private static final String[] FIELDS = new String[] {TIME,
                                                         LONGITUDE,
                                                         LATITUDE,
                                                         DEPTH,
                                                         DEPTH_UNITS,
                                                         MAGNITUDE,
                                                         MAGNITUDE_TYPE,
                                                         MAGNITUDE_CONTRIBUTOR,
                                                         CATALOG,
                                                         CONTRIBUTOR,
                                                         NAME,
                                                         FE_SEIS_REGION,
                                                         FE_GEO_REGION,
                                                         FE_REGION,
                                                         FE_REGION_TYPE};
    
    public static final String UNKNOWN = "unknown";

    private String csvFilename;
}