/*
 * $Id: Level2RadarDataSource.java,v 1.44 2004/08/27 15:14:57 dmurray Exp $
 *
 * Copyright © 1997-2004 Unidata Program Center/University Corporation for
 * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
 * support@unidata.ucar.edu.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

package ucar.nc2.visad.radar;


import java.util.*;

import java.io.File;

import visad.*;
import visad.georef.EarthLocation;

import javax.swing.AbstractAction;
import javax.swing.JOptionPane;

import java.awt.event.ActionEvent;


/**
 * A data source for Archive Level II Radar data files.
 *
 * @author Don Murray
 * @version $Revision: 1.44 $ $Date: 2004/08/27 15:14:57 $
 */
public class Level2RadarDataSource extends DataSourceImpl implements RadarConstants {

    /** Logging category */
    static ucar.unidata.util.LogUtil.LogCategory log_ =
        ucar.unidata.util.LogUtil.getLogInstance(
            Level2RadarDataSource.class.getName());

    /** List of sources (files */
    private List sources;

    /** List of Level2Adapters */
    private List l2as;

    /** Data category for a 3D sweep */
    protected static final String CATEGORY_3D =
        DataCategory.CATEGORY_SWEEP_3D;

    /** Data category for a time series of a 3D sweep */
    protected static final String CATEGORY_3D_TIME = CATEGORY_3D + "-TIME";

    /** Data category for a 2D sweep */
    protected static final String CATEGORY_2D =
        DataCategory.CATEGORY_SWEEP_2D;

    /** Data category for a time series of a 2D sweep */
    protected static final String CATEGORY_2D_TIME = CATEGORY_2D + "-TIME";

    /** Data category for an RHI */
    protected static final String CATEGORY_RHI = DataCategory.CATEGORY_RHI;

    /** Data category for a time series of RHIs */
    protected static final String CATEGORY_RHI_TIME = CATEGORY_RHI + "-TIME";

    /** Data category for a CAPPI */
    protected static final String CATEGORY_CAPPI =
        DataCategory.CATEGORY_CAPPI;

    /** Data category for a time series of CAPPIs */
    protected static final String CATEGORY_CAPPI_TIME = CATEGORY_CAPPI
                                                        + "-TIME";

    /** Data category for a vertical cross section */
    protected static final String CATEGORY_VCS =
        DataCategory.CATEGORY_RADAR_VCS;

    /** Data category for a time series of vertical cross sections */
    protected static final String CATEGORY_VCS_TIME = CATEGORY_VCS + "-TIME";

    /** Data category for a VAD Wind Profile (VWP) */
    protected static final String CATEGORY_VWP =
        DataCategory.CATEGORY_RADAR_VWP;

    /** Data category for a time series of VAD Wind Profile (VWP) */
    protected static final String CATEGORY_VWP_TIME = CATEGORY_VWP + "-TIME";

    /** Data category for a time/height cross section */
    protected static final String CATEGORY_RADAR_TH =
        DataCategory.CATEGORY_RADAR_TH;

    /** Data category for a time series of time/height cross sections */
    protected static final String CATEGORY_RADAR_TH_TIME = CATEGORY_RADAR_TH
                                                           + "-TIME";

    /** Data category for a volume */
    protected static final String CATEGORY_RADAR_VOLUME =
        DataCategory.CATEGORY_RADAR_VOLUME;

    /** Data category for a time series of volumes */
    protected static final String CATEGORY_RADAR_VOLUME_TIME =
        CATEGORY_RADAR_VOLUME + "-TIME";

    /** Data category for an isosurface */
    protected static final String CATEGORY_RADAR_ISOSURFACE =
        DataCategory.CATEGORY_RADAR_ISOSURFACE;

    /** Data category for a time series of isosurfaces */
    protected static final String CATEGORY_RADAR_ISOSURFACE_TIME =
        CATEGORY_RADAR_ISOSURFACE + "-TIME";

    /** Identifier for Station location */
    public static final String DATA_DESCRIPTION = "Level II Radar Data";


    /** local copy of the station name */
    private String stationName = "";

    /**
     * Construct a new Level II data source.
     *
     * @throws VisADException  couldn't create the data
     */
    public Level2RadarDataSource() throws VisADException {}

    /**
     * Construct a new Level II data source.
     * @param  descriptor  descriptor for this datasource
     * @param  source  file to read
     * @param  properties  hashtable of properties.  Includes radar location
     *
     * @throws VisADException  couldn't create the data
     */
    public Level2RadarDataSource(DataSourceDescriptor descriptor, String source, Hashtable properties)
            throws VisADException {
        this(descriptor, new String[]{ source }, properties);
    }


    /**
     * Construct a new Level II data source.
     * @param  descriptor  descriptor for this datasource
     * @param  sources  files to read
     * @param  properties  hashtable of properties.  Includes radar location
     *
     * @throws VisADException  couldn't create the data
     */
    public Level2RadarDataSource(DataSourceDescriptor descriptor, String[] sources, Hashtable properties)
            throws VisADException {
        this(descriptor, Misc.toList(sources), properties);
    }


    /**
     * Construct a new Level II data source.
     * @param  descriptor  descriptor for this datasource
     * @param  sources  <code>List</code> of file to read
     * @param  properties  hashtable of properties.  Includes radar location
     *
     * @throws VisADException  couldn't create the data
     */
    public Level2RadarDataSource(DataSourceDescriptor descriptor, List sources, Hashtable properties)
            throws VisADException {
        super(descriptor, DATA_DESCRIPTION, DATA_DESCRIPTION, properties);
        this.sources = sources;
        try {
            init();
        } catch (VisADException exc) {
            setInError(true);
            throw new WrapperException(exc);
        }
    }

    /**
     * Method for intializing the class.
     *
     * @throws VisADException  couldn't create the data
     */
    private void init() throws VisADException {
        initData();
    }

    /**
     * Method for intializing the data.
     *
     * @throws VisADException  couldn't create the data
     */
    private void initData() throws VisADException {

        if (sources == null) {
            return;
        }

        Integer mostRecent = null;
        List    files;
        if (getProperties() != null) {
            mostRecent = (Integer) getProperties().get(MOST_RECENT);
        }

        if ((mostRecent != null) && (mostRecent.intValue() > 0)) {
            File dir = new File(sources.get(0).toString());
            files = new ArrayList();
            File[]     subFiles = dir.listFiles();
            Comparator comp     = new Comparator() {
                public int compare(Object o1, Object o2) {
                    File f1 = (File) o1;
                    File f2 = (File) o2;
                    if (f1.lastModified() < f2.lastModified()) {
                        return 1;
                    }
                    if (f1.lastModified() == f2.lastModified()) {
                        return 0;
                    }
                    return -1;
                }
            };
            Arrays.sort(subFiles, comp);
            int cnt = 0;
            for (int i = 0;
                    (cnt < mostRecent.intValue()) && (i < subFiles.length);
                    i++) {
                if (subFiles[i].isDirectory()) {
                    continue;
                }
                if (Misc.getFileTail(subFiles[i].toString()).startsWith(
                        ".")) {
                    continue;
                }
                //             System.err.println ("file:" + subFiles[i].toString());
                files.add(subFiles[i].toString());
                cnt++;
            }

        } else {
            files = sources;
        }

        if (files.size() == 0) {
            return;
        }

        try {
            NamedStation ns = (getProperties() != null)
                              ? (NamedStation) getProperties().get(
                                  STATION_LOCATION)
                              : null;
            l2as = new ArrayList();
            ArrayList badones = new ArrayList();
            for (Iterator iter = files.iterator(); iter.hasNext(); ) {
                String        filename = (String) iter.next();
                Level2Adapter l2a      = null;
                try {
                    l2a = new Level2Adapter(this, filename, (ns != null)
                                                            ? (EarthLocation) ns
                                                                .getNamedLocation()
                                                            : null);
                    l2as.add(l2a);
                } catch (Exception e) {
                    badones.add(filename);
                    // System.err.println("Unable to read " + filename);
                }
            }
            if ( !badones.isEmpty()) {
                StringBuffer buf = new StringBuffer();
                if (badones.size() < files.size()) {
                    buf.append("<html>");
                    buf.append("There were problems reading these files:");
                    buf.append("<ul>");
                    for (Iterator iterator = badones.iterator();
                            iterator.hasNext(); ) {
                        buf.append("<li>");
                        buf.append((String) iterator.next());
                        buf.append("</li>");
                    }
                    buf.append("</ul>");
                    buf.append("<p>Continue loading good data?<p></html>");
                    boolean ok = ucar.unidata.util.GuiUtils.askYesNo(
                                     "Error reading data", buf.toString());

                    if (ok) {
                        files.removeAll(badones);
                    } else {
                        throw new VisADException("error reading files");
                    }
                } else {
                    throw new VisADException("error reading files");
                }
            }
        } catch (Exception e) {
            // e.printStackTrace();
            throw new VisADException("unable to create adapter(s): "
                                     + e.getMessage());
        }
    }


    /**
     * Make the set of DataChoices associated with this DataSource.
     */
    public void doMakeDataChoices() {
        if ((l2as == null) || l2as.isEmpty()) {
            return;
        }

        NamedStation ns = (getProperties() != null)
                          ? (NamedStation) getProperties().get(
                              STATION_LOCATION)
                          : null;
        if (ns != null) {
            stationName = ns.getIdentifier() + " ";
        }

        double[] angles = ((Level2Adapter) l2as.get(0)).getAngles();
        String[] moments = { "Reflectivity", "Radial Velocity",
                             "Spectrum Width" };
        boolean haveTimes = (l2as.size() > 1);

        List volumeCategories = DataCategory.parseCategories(CATEGORY_RHI
                                    + ";"
        //+ CATEGORY_VCS + ";"
        + CATEGORY_CAPPI + ";"
        //+ CATEGORY_RADAR_TH + ";" + CATEGORY_VWP
        + ";" + CATEGORY_RADAR_VOLUME + ";"
              + CATEGORY_RADAR_ISOSURFACE, false);
        List sweepCategories = (haveTimes
                                ? DataCategory.parseCategories(
                                    CATEGORY_2D_TIME + ";"
                                    + CATEGORY_3D_TIME, false)
                                : DataCategory.parseCategories(CATEGORY_2D
                                    + ";" + CATEGORY_3D, false));

        volumeCategories.addAll(sweepCategories);
        Hashtable properties;
        try {
            for (int moment = 0; moment < moments.length; moment++) {
                Integer momentObj  = new Integer(moment);
                String  momentName = moments[moment];
                CompositeDataChoice momentChoice =
                    new CompositeDataChoice(
                        this,
                        new ObjectArray(momentName, momentObj, RadarConstants.VALUE_3D),
                        stationName + " " + momentName, momentName,
                        volumeCategories,
                        Misc.newHashtable(
                            PROP_ANGLES, angles, STATION_LOCATION, ns));
                // make a DirectDataChoice for 2D plots
                //   for every tilt ("angle") above horizontal;
                for (int i = 0; i < angles.length; i++) {
                    String name = "Elevation Angle " + angles[i];
                    momentChoice.addDataChoice(new DirectDataChoice(this,
                            new ObjectArray(new Double(angles[i]), momentObj, RadarConstants.VALUE_2D),
                            stationName + " " + momentName,
                            momentName + " " + name, sweepCategories,
                            Misc.newHashtable(PROP_ANGLES, angles,
                                              PROP_ANGLE,
                                              new Double(angles[i]),
                                              STATION_LOCATION, ns)));
                }
                addDataChoice(momentChoice);
            }
        } catch (Exception excp) {
            logException("Creating data choices", excp);
        }
    }


    /**
     * Create the list of times associated with this DataSource.
     * @return list of times.
     */
    protected List doMakeDateTimes() {
        List times = new ArrayList();
        for (Iterator iter = l2as.iterator(); iter.hasNext(); ) {
            times.add(((Level2Adapter) iter.next()).getBaseTime());
        }
        return times;
    }

    /**
     * Get the data for the given DataChoice and selection criteria.
     * @param dataChoice         DataChoice for selection
     * @param category           DataCategory for the DataChoice (not used)
     * @param subset             subsetting criteria
     * @param requestProperties  extra request properties
     * @return  the Data object for the request
     *
     * @throws RemoteException couldn't create a remote data object
     * @throws VisADException  couldn't create the data
     */
    protected Data getDataInner(DataChoice dataChoice, DataCategory category, DataSelection subset, Hashtable requestProperties)
            throws VisADException, RemoteException {
        try {
            List times = null;
            if (subset != null) {
                times = getTimesFromDataSelection(subset, dataChoice);
            }
            if (times == null) {
                times = dataChoice.getSelectedDateTimes();
            }
            // if times are null, then that means all times
            DateTime[] dateTimes = null;
            if (times == null) {
                dateTimes = new DateTime[l2as.size()];
                int i = 0;
                for (Iterator iter = l2as.iterator(); iter.hasNext(); ) {
                    Level2Adapter l2a = (Level2Adapter) iter.next();
                    dateTimes[i++] = l2a.getBaseTime();
                }
            } else {
                dateTimes =
                    (DateTime[]) times.toArray(new DateTime[times.size()]);
            }
            Arrays.sort(dateTimes);
            Data[]   datas     = new Data[dateTimes.length];
            int      timeIndex = 0;
            MathType mt        = null;
            // create a new field of (Time -> (radar data)).
            // fill in the times array and data array with dates/data
            // only from those adapters which match the selected times.
            // if a data object is null, stick it in the list.
            // if all are null, then the MathType (mt) will never get set,
            // so return null.
            for (Iterator iter = l2as.iterator(); iter.hasNext(); ) {
                Level2Adapter l2a = (Level2Adapter) iter.next();
                timeIndex = Arrays.binarySearch(dateTimes, l2a.getBaseTime());
                if (timeIndex >= 0) {
                    Data d = l2a.getData(dataChoice, subset,
                                         requestProperties);
                    datas[timeIndex] = d;
                    if (d != null) {
                        mt = d.getType();
                    } else {}
                }
            }
            if (mt == null) {
                return null;
            }
            FunctionType ft        = new FunctionType(RealType.Time, mt);
            SampledSet   domainSet = (dateTimes.length == 1)
                                     ? (SampledSet) new SingletonSet(
                                         new RealTuple(dateTimes))
                                     : (SampledSet) DateTime.makeTimeSet(
                                         dateTimes);
            FieldImpl fi = new FieldImpl(ft, domainSet);
            fi.setSamples(datas, false);
            return fi;
        } catch (Exception exc) {
            logException("Creating obs", exc);
        }
        return null;
    }


    /**
     * Check to see if this NetcdfRadarDataSource is equal to the object
     * in question.
     * @param o  object in question
     * @return true if they are the same or equivalent objects
     */
    public boolean equals(Object o) {
        if ( !(o instanceof Level2RadarDataSource)) {
            return false;
        }
        Level2RadarDataSource that = (Level2RadarDataSource) o;
        return (this == that);
    }

    /**
     * Get the hash code for this object.
     * @return hash code.
     */
    public int hashCode() {
        int hashCode = getName().hashCode();
        return hashCode;
    }

    /**
     * Main routine for testing.
     *
     * @param args  list of file names
     *
     * @throws Exception  problem occurred
     */
    public static void main(String[] args) throws Exception {

        if (args.length == 0) {
            System.out.println("Must supply a file name");
            System.exit(1);
        }
        Level2RadarDataSource l2rds = new Level2RadarDataSource(null, args,
                                          null);
        System.out.println("DataChoices:");
        for (Iterator iter = l2rds.getDataChoices().iterator();
                iter.hasNext(); ) {
            System.out.println(iter.next());
        }
        /*
        Data testData = l2rds.getData ((DataChoice)
            l2rds.getDataChoices().get(0), null);
        visad.python.JPythonMethods.dumpTypes (testData);
        */
        System.out.println("DateTimes:");
        for (Iterator iter = l2rds.getAllDateTimes().iterator();
                iter.hasNext(); ) {
            System.out.println(iter.next());
        }
    }

    /**
     * Process the name so we can make it readable
     * @param name  default name
     * @return processed name
     */
    public String processName(String name) {
        NamedStation ns = (getProperties() != null)
                          ? (NamedStation) getProperties().get(
                              STATION_LOCATION)
                          : null;
        return (ns == null)
               ? name
               : DATA_DESCRIPTION + " (" + ns.getIdentifier() + ")";
    }

    /**
     * Called when Datasource is removed.
     */
    public void doRemove() {
        super.doRemove();
        l2as = null;
    }

    /**
     * Method to do stuff after this <code>DataSource</code> has been
     * unpersisted from XML.
     */
    public void initAfterUnpersistence() {
        super.initAfterUnpersistence();
        try {
            init();
        } catch (Exception e) {
            setInError(true);
        }
    }

    /**
     * Set the source of this data.  Used by the XML persistence
     * @param sources <code>List</code> of filenames of data.
     */
    public void setSources(List sources) {
        this.sources = sources;
    }

    /**
     * Get the sources (files) for the data.
     * @return <code>List</code> of filenames
     */
    public List getSources() {
        return sources;
    }

    /**
     * Get the list of actions associated with this data source.
     *
     * @return List of actions
     */
    public List getActions() {
        List l = super.getActions();
        if (l == null) {
            l = new ArrayList();
        }
        AbstractAction a = new AbstractAction("Set Radar Location...") {
            public void actionPerformed(ActionEvent e) {
                NamedStation station =
                    (NamedStation) JOptionPane.showInputDialog(null,
                        "Select Station", "Select Station for Data",
                        JOptionPane.INFORMATION_MESSAGE, null,
                        Level2Adapter.getStations().values().toArray(), null);
                setStationInfo(station);
            }
        };
        l.add(a);
        return l;
    }

    /**
     * Set the station information for this DataSource
     *
     * @param station new station information
     */
    private void setStationInfo(NamedStation station) {
        if (station == null) {
            return;
        }
        Hashtable props = getProperties();
        if (props != null) {
            props.put(STATION_LOCATION, station);
        } else {
            setProperties(Misc.newHashtable(STATION_LOCATION, station));
        }
        setName(getName());
        setDescription(getName());

        // update the data adapters
        if (l2as != null) {
            for (int i = 0; i < l2as.size(); i++) {
                try {
                    ((Level2Adapter) l2as.get(i)).setRadarLocation(
                        (EarthLocation) station.getNamedLocation());
                } catch (Exception excp) {
                    logException("Couldn't set station on adapter", excp);
                }
            }
        }

        // update the data choices
        List l = getDataChoices();
        for (int i = 0; i < l.size(); i++) {
            DataChoice dc = (DataChoice) l.get(i);
            Hashtable  ht = dc.getProperties();
            if (ht != null) {
                ht.put(STATION_LOCATION, station);
            } else {
                dc.setProperties(Misc.newHashtable(STATION_LOCATION,
                                                   station));
            }
        }

        getDataContext().dataSourceChanged(this);
    }

}
