/**
 * This software was developed and / or modified by Raytheon Company,
 * pursuant to Contract DG133W-05-CQ-1067 with the US Government.
 * 
 * U.S. EXPORT CONTROLLED TECHNICAL DATA
 * This software product contains export-restricted data whose
 * export/transfer/disclosure is restricted by U.S. law. Dissemination
 * to non-U.S. persons whether in the United States or abroad requires
 * an export license or other authorization.
 * 
 * Contractor Name:        Raytheon Company
 * Contractor Address:     6825 Pine Street, Suite 340
 *                         Mail Stop B8
 *                         Omaha, NE 68106
 *                         402.291.0100
 * 
 * See the AWIPS II Master Rights File ("Master Rights File.pdf") for
 * further licensing information.
 **/
package com.raytheon.uf.common.sounding;

import static com.raytheon.uf.common.sounding.SoundingLayer.MISSING;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.raytheon.uf.common.pointdata.spatial.SurfaceObsLocation;
import com.raytheon.uf.common.sounding.SoundingLayer.DATA_TYPE;
import com.raytheon.uf.common.time.DataTime;
import com.vividsolutions.jts.geom.Geometry;

/**
 * VerticalSounding is an ordered collection of SoundingLayers sorted by
 * pressure, where the highest pressure is at the beginning of the collection.
 * 
 * <pre>
 * SOFTWARE HISTORY
 * 
 * Date         Ticket#    Engineer    Description
 * ------------ ---------- ----------- --------------------------
 * 06 Nov 2006             jkorman     Initial Coding
 * 20071127            382 jkorman     Moved from Cave graphing.
 * 15Jan2008           682 ebabin      Updated to remove non calculated parameters.
 * 16Jan2008           682 ebabin      Updates for grib model traps on multiple loads.
 * 04Oct2008               dhladky     Many changes.
 * </pre>
 * 
 * @author jkorman
 * @version 1.0
 */
public class VerticalSounding implements Iterable<SoundingLayer>, Cloneable {

    private static final float FREEZING = 273.15f;

    /**
     * Is the value not null and above the missing/invalid values.
     * 
     * @param value
     *            A value to check.
     * @return Is the value valid.
     */
    protected static boolean isValid(double value) {
        return value < MISSING;
    }

    // *************************************************
    private SurfaceObsLocation spatialInfo;

    private Calendar obsTime = null;

    private DataTime dataTime = null;

    private List<SoundingLayer> layerData = new ArrayList<SoundingLayer>();

    private Map<Float, SoundingLayer> layerMap = new HashMap<Float, SoundingLayer>();

    private List<SoundingLayer> maxWinds = new ArrayList<SoundingLayer>();

    private SoundingLayer sfcLayer = null;

    private String displayFormat;

    // *************************************************
    // * Computed data.
    // *************************************************
    private SoundingLayer maxTemperatureLayer = null;

    private SoundingLayer minTemperatureLayer = null;

    private boolean initialLoad = true;

    private String name = "";

    /**
     * Create an empty sounding.
     */
    public VerticalSounding() {
    }

    /**
     * @return the displayFormat
     */
    public String getDisplayFormat() {
        return displayFormat;
    }

    /**
     * @param displayFormat
     *            the displayFormat to set
     */
    public void setDisplayFormat(String displayFormat) {
        this.displayFormat = displayFormat;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#clone()
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        VerticalSounding clone = (VerticalSounding) super.clone();
        clone.layerData = new ArrayList<SoundingLayer>();
        clone.layerMap = new HashMap<Float, SoundingLayer>();
        clone.maxWinds = new ArrayList<SoundingLayer>();
        clone.spatialInfo = (SurfaceObsLocation) spatialInfo.clone();
        clone.setInitialLoad(true);

        for (SoundingLayer layer : layerData) {
            clone.addLayer((SoundingLayer) layer.clone());
        }
        clone.invalidate();

        return clone;
    }

    /**
     * Check to see if a surface layer exists. If it does not then find the
     * lowest layer and set this as the surface.
     */
    public void checkSfcLayer() {

        // Did the data declare a surface layer?
        if (sfcLayer == null) {
            // Did we set Elevation? If so use that to calculate the surface
            // layer.
            if (this.getElevation() != null) {
                int press = (int) WxMath.heightToPressure(this.getElevation());
                sfcLayer = this.getLayerNearest(press);
                if (press != sfcLayer.getPressure()) {
                    SoundingLayer sLayer = new SoundingLayer();
                    sLayer.setGeoHeight(this.getElevation());
                    sLayer.setPressure(press);
                    sLayer.setTemperature((float) (Math.pow(
                            press / sfcLayer.getPressure(), 0.286) * sfcLayer
                            .getTemperature()));
                    sLayer.setWindU(sfcLayer.getWindU());
                    sLayer.setWindV(sfcLayer.getWindV());
                    sLayer.setWindDirection(sfcLayer.getWindDirection());
                    sLayer.setWindSpeed(sfcLayer.getWindSpeed());
                    sfcLayer = sLayer;
                    addLayer(sfcLayer);
                }
            } else {
                SoundingLayer lowestLayer = new SoundingLayer();
                // Start at 1 hPa. We won't consider anything higher that that.
                lowestLayer.setPressure(1.0f);
                for (SoundingLayer layer : layerData) {
                    if (layer.isLowerThan(lowestLayer)) {
                        lowestLayer = layer;
                    }
                }
                sfcLayer = lowestLayer;
            }
        }
    }

    /**
     * Get the surface layer if defined.
     */
    public SoundingLayer getSfcLayer() {
        return sfcLayer;
    }

    /**
     * @return the layerData
     */
    public List<SoundingLayer> getLayerData() {
        return layerData;
    }

    /**
     */
    public void removeBelowSfcLayers() {
        if (layerData != null) {
            if (sfcLayer != null) {
                Iterator<SoundingLayer> layers = layerData.iterator();
                while (layers.hasNext()) {
                    SoundingLayer layer = layers.next();
                    if (layer.isLowerThan(sfcLayer)) {
                        layers.remove();
                    }
                }
            }
        }
    }

    private void rebuildMap() {
        layerMap.clear();
        for (SoundingLayer layer : layerData) {
            layerMap.put(new Float(layer.getPressure()), layer);
        }
    }

    /**
     * Add a layer to this sounding.
     * 
     * @param layer
     *            A sounding layer to add.
     */
    /**
     * Add a layer to this sounding.
     * 
     * @param layer
     *            A sounding layer to add.
     */
    public void addLayer(SoundingLayer layer) {
        if (okToAdd(layer)) {
            sortLayerIntoSounding(layer);
            if (layer.getPressure() != MISSING) {
                // update values
                SoundingLayer currLayer = layerMap.get(layer.getPressure());
                if (currLayer != null) {
                    if (layer.getTemperature() != MISSING) {
                        currLayer.setTemperature(layer.getTemperature());
                    }
                    if (layer.getDewpoint() != MISSING) {
                        currLayer.setDewpoint(layer.getDewpoint());
                    }
                    if (layer.getWindDirection() != MISSING) {
                        currLayer.setWindDirection(layer.getWindDirection());
                    }
                    if (layer.getWindSpeed() != MISSING) {
                        currLayer.setWindSpeed(layer.getWindSpeed());
                    }
                } else {
                    layerMap.put(new Float(layer.getPressure()), layer);
                }
            }

            invalidate();
        }
    }

    /**
     * 
     * @param layers
     */
    public void addLayers(List<SoundingLayer> layers) {
        initialLoad = true;
        layerData.clear();
        layerMap.clear();
        for (SoundingLayer layer : layers) {
            addLayer(layer);
        }
    }

    /**
     * 
     */
    public void invalidate() {
        if (!isInitialLoad()) {
            sortByPressure();
            rebuildMap();
            updateMaxMinTemp();
        }
    }

    /**
     * Checks a layer to be inserted to determine if a layer with the same
     * pressure already exists.
     * 
     * @param layer
     *            The layer to be added.
     */
    private boolean okToAdd(SoundingLayer layer) {
        boolean shouldAdd = false;
        if (layer != null) {
            float p = layer.getPressure();
            if ((p != MISSING) && (layer.getLayerType() != null)) {
                SoundingLayer currLayer = layerMap.get(p);
                if (currLayer != null) {
                    switch (layer.getLayerType()) {
                    case SURFACE: {
                        if (LayerType.MAN_PRESSURE.equals(currLayer
                                .getLayerType())) {
                            int i = layerData.indexOf(currLayer);
                            layerData.remove(i);
                            layerMap.remove(currLayer.getPressure());
                            shouldAdd = true;
                        } else if (LayerType.SIG_PRESSURE.equals(currLayer
                                .getLayerType())) {
                            int i = layerData.indexOf(currLayer);
                            layerData.remove(i);
                            layerMap.remove(currLayer.getPressure());
                            shouldAdd = true;
                        }
                        break;
                    }
                    case MAN_PRESSURE: {
                        if (LayerType.SIG_PRESSURE.equals(currLayer
                                .getLayerType())) {
                            int i = layerData.indexOf(currLayer);
                            layerData.remove(i);
                            layerMap.remove(currLayer.getPressure());
                            shouldAdd = true;
                        }
                        break;
                    }
                    case SIG_PRESSURE: {
                        // TODO : Make sure to check other layer types.
                        break;
                    }
                    default: {
                        shouldAdd = true;
                    }
                    } // end switch

                } else {
                    shouldAdd = true;
                }
            } else {
                layerMap.put(new Float(layer.getPressure()), layer);
            }
        }
        return shouldAdd;
    }

    /**
     * Remove Data layer
     * 
     * @param layer
     */
    public void removeLayer(SoundingLayer layer) {

        if (layerMap.containsKey(layer.getPressure())) {
            layerMap.remove(layer.getPressure());
        }
        layerData.remove(layer);
        invalidate();
    }

    /**
     * Gets the layer at the specified pressure level
     * 
     * @param level
     * @return the layer
     */
    public SoundingLayer getLayer(float level) {
        return layerMap.get(level);
    }

    /**
     * Get the layer at the specified index
     * 
     * @param index
     * @return the layer
     */
    public SoundingLayer get(int index) {
        return layerData.get(index);
    }

    /**
     * Sort a layer into this sounding. The primary sort key is the layer
     * pressure. If pressure is not available, the geopotential height is used.
     * 
     * @param layer
     *            The layer that is being added.
     */
    private void sortLayerIntoSounding(SoundingLayer layer) {
        // If empty, just add the layer.
        if (LayerType.MAX_WIND.equals(layer.getLayerType())) {
            maxWinds.add(layer);
            return;
        }
        if (LayerType.SURFACE.equals(layer.getLayerType())) {
            sfcLayer = layer;
        }
        if (layerData.size() == 0) {
            layerData.add(layer);
        } else {
            boolean entered = false;
            for (int i = 0; i < layerData.size(); i++) {
                SoundingLayer cLayer = layerData.get(i);
                if (layer.isLowerThan(cLayer)) {
                    layerData.add(i, layer);
                    entered = true;
                    break;
                }
            }
            if (!entered) {
                layerData.add(layer);
            }
        }
    }

    /**
     * Gets the initialLoad boolean
     * 
     * @return
     */
    public boolean isInitialLoad() {
        return initialLoad;
    }

    /**
     * Sets the initialLoad param
     * 
     * @param initalLoad
     */
    public void setInitialLoad(boolean initialLoad) {
        this.initialLoad = initialLoad;
    }

    /**
     * Return the layer closest to a given pressure.
     * 
     * @param pressure
     *            The target pressure.
     * @return The SoundingLayer instance nearest to the target.
     */
    public SoundingLayer getLayerNearest(float pressure) {
        SoundingLayer retLayer = new SoundingLayer(pressure, MISSING, MISSING,
                MISSING, MISSING, MISSING, MISSING);

        if (layerData.size() > 0) {
            retLayer = layerData.get(0);
            if (layerData.size() == 1) {
                return retLayer;
            }

            SoundingLayer loLayer = null;
            SoundingLayer hiLayer = null;
            SoundingLayer sLayer = new SoundingLayer(pressure, MISSING,
                    MISSING, MISSING, MISSING, MISSING, MISSING);
            int hiIndex = 0;
            for (int i = 0; i < layerData.size(); i++) {
                SoundingLayer layer = layerData.get(i);
                if (layer.getPressure() == pressure) {
                    return layer;
                } else if (layer.isHigherThan(sLayer)) {
                    // Make sure we have a lower pressure level.
                    if (layer.getPressure() < MISSING) {
                        hiLayer = layer;
                        hiIndex = i;
                        break;
                    }
                }
            }
            if (hiIndex > 0) {
                SoundingLayer layer = layerData.get(0);
                loLayer = layer;

                for (int i = hiIndex - 1; i > 0; i--) {
                    layer = layerData.get(i);
                    if (layer.isLowerThan(sLayer)) {
                        // Make sure we have a lower pressure level.
                        if (layer.getPressure() < MISSING) {
                            loLayer = layer;
                            break;
                        }
                    }
                }
            } else {
                if (pressure > layerData.get(0).getPressure()) {
                    retLayer = layerData.get(0);
                } else {
                    retLayer = layerData.get(layerData.size() - 1);
                }
            }

            if ((loLayer != null) && (hiLayer != null)) {
                double hPoint = (loLayer.getPressure() + hiLayer.getPressure()) / 2;

                if (pressure < hPoint) {
                    retLayer = hiLayer;
                } else {
                    retLayer = loLayer;
                }
            }
        }
        return retLayer;
    }

    /**
     * Return the layer closest to a given windspeed and winddir.
     * 
     * @param windspeed
     *            , winddir The target pressure.
     * @return The SoundingLayer instance nearest to the target.
     */
    public SoundingLayer getLayerNearest(float windspeed, float winddir) {
        SoundingLayer retLayer = null;
        SoundingLayer loLayer = null;
        SoundingLayer hiLayer = null;
        SoundingLayer sLayer = new SoundingLayer(MISSING, MISSING, MISSING,
                MISSING, windspeed, winddir, MISSING);
        int hiIndex = 0;

        // Check and find closest wind speed
        for (int i = 0; i < layerData.size(); i++) {
            SoundingLayer layer = layerData.get(i);
            if (layer.getWindSpeed() == sLayer.getWindSpeed()

            && layer.getWindDirection() == sLayer.getWindDirection()) {
                return layer;
            } else if ((layer.getWindSpeed() > sLayer.getWindSpeed())
                    && layer.getWindSpeed() != 99999.0) {
                hiLayer = layer;
                hiIndex = i;
                break;
            }
        }
        if (hiIndex > 0) {
            for (int i = hiIndex + 1; i < layerData.size(); i++) {
                SoundingLayer layer = layerData.get(i);
                if (layer.getWindSpeed() < sLayer.getWindSpeed()) {
                    loLayer = layer;
                    break;
                }
            }
        } else {
            if (windspeed > layerData.get(0).getPressure()) {
                retLayer = layerData.get(0);
            } else {
                retLayer = layerData.get(layerData.size() - 1);
            }
        }

        if ((loLayer != null) && (hiLayer != null)) {
            double hPoint = (loLayer.getWindSpeed() + hiLayer.getWindSpeed()) / 2;

            if (windspeed < hPoint) {
                retLayer = hiLayer;
            } else {
                retLayer = loLayer;
            }
        }
        return retLayer;
    }

    /**
     * Get an iterator to the internal layer data.
     * 
     * @return The layer data iterator.
     */
    public Iterator<SoundingLayer> iterator() {
        return layerData.iterator();
    }

    /**
     * Get this observation's geometry.
     * 
     * @return The geometry for this observation.
     */
    public Geometry getObsGeometry() {
        Geometry obsGeometry = null;
        if (spatialInfo != null) {
            obsGeometry = spatialInfo.getGeometry();
        }
        return obsGeometry;
    }

    /**
     * Get the geometry latitude.
     * 
     * @return The geometry latitude.
     */
    public double getLatitude() {
        return spatialInfo.getLatitude();
    }

    /**
     * Get the geometry longitude.
     * 
     * @return The geometry longitude.
     */
    public double getLongitude() {
        return spatialInfo.getLongitude();
    }

    /**
     * Set the geometry latitude.
     * 
     */
    public void setLatitude(double latitude) {
        spatialInfo.setLatitude(latitude);
    }

    /**
     * Set the geometry longitude.
     * 
     */
    public void setLongitude(double longitude) {
        spatialInfo.setLongitude(longitude);
    }

    /**
     * Get the station identifier for this observation.
     * 
     * @return the stationId
     */
    public String getStationId() {
        String stationId = null;
        if (spatialInfo != null) {
            stationId = (spatialInfo).getStationId();

        }
        return stationId;
    }

    /**
     * Set the station identifier for this observation.
     * 
     * @param stationId
     *            the stationId to set
     */
    public void setStationId(String stationId) {
        if (spatialInfo == null) {
            spatialInfo = new SurfaceObsLocation();
        }
        (spatialInfo).setStationId(stationId);
    }

    /**
     * Get the elevation, in meters, of the observing platform or location.
     * 
     * @return The observation elevation, in meters.
     */
    public Integer getElevation() {
        Integer elevation = null;
        if (spatialInfo != null) {
            elevation = (spatialInfo).getElevation();
        }
        return elevation;
    }

    /**
     * Set the elevation, in meters, of the observing platform or location.
     * 
     * @param elevation
     *            The elevation to set
     */
    public void setElevation(Integer elevation) {
        if (spatialInfo == null) {
            spatialInfo = new SurfaceObsLocation();
        }
        (spatialInfo).setElevation(elevation);
    }

    /**
     * @return the timeObs
     */
    public Calendar getObsTime() {
        return obsTime;
    }

    /**
     * @param timeObs
     *            the timeObs to set
     */
    public void setObsTime(Calendar obsTime) {
        this.obsTime = obsTime;
    }

    /**
     * @return the dataTime
     */
    public DataTime getDataTime() {
        return dataTime;
    }

    /**
     * @param dataTime
     *            the dataTime to set
     */
    public void setDataTime(DataTime dataTime) {
        this.dataTime = dataTime;
    }

    /**
     * Get the three char ICAO climo station name associated with this sounding.
     * 
     * @return The ICAO climo station name.
     */
    public String getName() {
        return name;
    }

    /**
     * Set the three char ICAO climo station name associated with this sounding.
     * 
     * @param name
     *            The ICAO climo station name.
     */
    public void setName(String name) {
        this.name = name;
    }

    // *************************************************

    /**
     * Gets the maximum pressure layer, always first in array unless added to.
     * 
     * @return
     */
    public SoundingLayer getMaxPressurelayer() {
        int i = 0;
        while (layerData.get(i).getPressure() >= MISSING) {
            i++;
        }

        return layerData.get(i);
    }

    /**
     * Returns the top of the sounding layer
     * 
     * @return SoundingLayer
     */
    public SoundingLayer getMinPressureLayer() {
        int i = layerData.size();
        while (layerData.get(--i).getPressure() >= MISSING) {
        }

        return layerData.get(i);
    }

    /**
     * Get the layer containing the maximum temperature in the sounding.
     * 
     * @return The layer containing the maximum temperature in the sounding.
     */
    public SoundingLayer getMaxTempLayer() {
        return maxTemperatureLayer;
    }

    /**
     * Get the layer containing the minimum temperature in the sounding.
     * 
     * @return The layer containing the minimum temperature in the sounding.
     */
    public SoundingLayer getMinTempLayer() {
        return minTemperatureLayer;
    }

    /**
     * Determine the pressure level of the first occurrence of a specified
     * temperature.
     * 
     * @param temperature
     *            The temperature to find.
     * @return The pressure of the first occurrence. Returns a null value if the
     *         temperature does not exist.
     */
    // public Double tempLevel(double temperature) {
    // Double temperatureLevel = null;
    // updateMaxMinTemp();
    // // If either of the following layers are null don't bother checking
    // // further.
    // if ((maxTemperatureLayer != null) && (minTemperatureLayer != null)) {
    // // Check if the temperature is in this sounding's range.
    // if (isValid(maxTemperatureLayer.getTemperature())
    // && isValid(minTemperatureLayer.getTemperature())) {
    // if (temperature < maxTemperatureLayer.getTemperature()
    // && temperature > minTemperatureLayer.getTemperature()) {
    // SoundingLayer layerBelow = null;
    // SoundingLayer layerAbove = null;
    //
    // // find the first level that contains a valid temperature.
    // for (SoundingLayer layer : layerData) {
    // if (isValid(layer.getTemperature())) {
    // // we're looking for a base layer
    // if (layerBelow == null) {
    // layerBelow = layer;
    // } else {
    // // we have a base layer, now find the next layer
    // // with a temperature.
    // layerAbove = layer;
    //
    // // now we have two layers with temperature.
    // // Find out if the temperature is here.
    // Double p = isContained(layerBelow, layerAbove,
    // temperature);
    // // we found it.
    // if (isValid(p)) {
    // temperatureLevel = p;
    // break;
    // }
    // }
    // }
    // }
    // }
    // }
    // }
    // return temperatureLevel;
    // }
    /**
     * 
     * @param type
     * @return
     */
    public float[] getValues(DATA_TYPE type) {

        float f[] = new float[layerData.size()];
        for (int i = 0; i < layerData.size(); i++) {
            f[i] = layerData.get(i).getValue(type);
        }
        return f;
    }

    public String printableSoundingData() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("START******" + this.getDataTime().getLegendString()
                + "*******/n");
        for (SoundingLayer l : layerData) {
            buffer.append(l + "\n");
        }
        buffer.append("END  ******" + this.getDataTime().getLegendString()
                + "*******/n");
        return buffer.toString();
    }

    public SurfaceObsLocation getSpatialInfo() {
        return spatialInfo;
    }

    public void setSpatialInfo(SurfaceObsLocation spatialInfo) {
        this.spatialInfo = spatialInfo;
    }

    /**
     * Iterate the sounding to recompute the max and min data. This method must
     * be used when edits have been performed.
     */
    private void updateMaxMinTemp() {
        SoundingLayer maxTempLayer = null;
        SoundingLayer minTempLayer = null;

        double minTemp = Double.MAX_VALUE;
        double maxTemp = -Double.MAX_VALUE;

        for (SoundingLayer layer : layerData) {
            double t = layer.getTemperature();
            if (isValid(t)) {
                if (t > maxTemp) {
                    maxTemp = t;
                    maxTempLayer = layer;
                }
                if (t < minTemp) {
                    minTemp = t;
                    minTempLayer = layer;
                }
            }
        }
        maxTemperatureLayer = maxTempLayer;
        minTemperatureLayer = minTempLayer;
    }

    public int size() {
        return layerData.size();
    }

    /**
     * Interpolate the desired value at specified pressure
     * 
     * @param pressure
     * @param dataType
     * @return the interpolated value
     */
    public float interpolateValue(float pressure, DATA_TYPE dataType) {

        // find the bracketing layers
        SoundingLayer layer, prevLayer;
        layer = prevLayer = null;
        float press = (float) Math.floor(pressure);
        for (SoundingLayer l : layerData) {
            if (l.getPressure() < MISSING && l.getValue(dataType) < MISSING) {
                if (Math.floor(l.getPressure()) <= press) {
                    layer = l;
                    break;
                }
                prevLayer = l;
            }
        }

        // if bracketing layers not found
        if (layer == null || prevLayer == null) {
            return MISSING;
        }

        // compute weighting factors
        double w1 = Math.log(prevLayer.getPressure() / layer.getPressure());
        double w2 = Math.log(prevLayer.getPressure() / pressure) / w1;
        w1 = Math.log(pressure / layer.getPressure()) / w1;

        // interpolate desired value
        double interpVal = w1 * prevLayer.getValue(dataType) + w2
                * layer.getValue(dataType);

        return (float) interpVal;
    }

    public void sortByPressure() {
        Collections.sort(layerData, SoundingLayer.getPressureComparator());
    }

    public float totalTotals() {
        float totals = -9999.0f;

        // TT = (T850 - T500) + (Td850 - T500)
        SoundingLayer lyr850 = null;
        SoundingLayer lyr500 = null;

        Float val = new Float(850.0f);
        if (layerMap.containsKey(val)) {
            lyr850 = layerMap.get(val);
        }
        val = new Float(500.0f);
        if (layerMap.containsKey(val)) {
            lyr500 = layerMap.get(val);
        }
        if ((lyr850 != null) && (lyr500 != null)) {
            // 500 temperature is required for cross and vertical totals.
            float t500 = lyr500.getTemperature();
            float t850 = lyr850.getTemperature();
            float td850 = lyr850.getTemperature();
            if (t500 > -9999.0f) {
                if (t850 > -9999.0f) {
                    if (td850 > -9999.0f) {
                        totals = (t850 - t500) + (td850 - t500);
                    }
                }
            }
        }
        return totals;
    }

    public float getWindSpeed700() {
        float ws = -9999.0f;

        // TT = (T850 - T500) + (Td850 - T500)
        SoundingLayer lyr700 = null;

        Float val = new Float(700.0f);
        if (layerMap.containsKey(val)) {
            lyr700 = layerMap.get(val);
        }
        if (lyr700 != null) {
            ws = lyr700.getWindSpeed();
        }
        return ws;
    }

    public float getWindUComp500() {
        float uComp = -9999.0f;

        SoundingLayer lyr500 = null;

        Float val = new Float(500.0f);
        if (layerMap.containsKey(val)) {
            lyr500 = layerMap.get(val);
        }
        if (lyr500 != null) {
            float ws = lyr500.getWindSpeed();
            float wd = lyr500.getWindDirection();
            if ((ws >= 0) && (wd > 0) && (wd < MISSING)) {
                uComp = -ws * (float) Math.sin(Math.toRadians(wd));

            }
        }
        return uComp;
    }

    /**
     * Find the height in meters of the freezing level. 1. Surface is the
     * freezing level.
     * 
     * 
     * 
     * 
     * @return
     */
    public float firstFreezingLevel() {

        float fzlLevel = 0;

        // Create a list of layers that have both temperature and
        // height data.
        List<SoundingLayer> data = new ArrayList<SoundingLayer>();
        for (SoundingLayer layer : layerData) {
            if ((layer.getGeoHeight() != MISSING)
                    && (layer.getTemperature() != MISSING)) {
                data.add(layer);
            }
        }
        if (data.size() > 0) {
            // Scenerio 1 - Freezing level at the surface.
            if (data.get(0).getTemperature() == FREEZING) {
                fzlLevel = getElevation().floatValue();
            } else {
                if (data.size() > 2) {
                    for (int i = 1; i < data.size(); i++) {
                        float ta = data.get(i).getTemperature();
                        float tb = data.get(i - 1).getTemperature();
                        float ha = data.get(i).getGeoHeight();
                        float hb = data.get(i - 1).getGeoHeight();

                        if ((ta < FREEZING) && (tb > FREEZING)) {
                            fzlLevel = interp(hb, tb, ha, ta);
                        } else if ((ta > FREEZING) && (tb < FREEZING)) {
                            fzlLevel = interp(hb, tb, ha, ta);
                        }
                    }
                }
            }
        }
        return fzlLevel;
    }

    /**
     * Interpolate the temperature between tlo and thi at heights hlo and hhi
     * respectively.
     * 
     * @param hlo
     * @param tlo
     * @param hhi
     * @param thi
     * @return
     */
    private static final float interp(float hlo, float tlo, float hhi, float thi) {
        return hlo + ((FREEZING - tlo) * (hhi - hlo) / (thi - tlo));
    }

    public static final void main(String[] args) {

        VerticalSounding sounding = new VerticalSounding();
        sounding.setElevation(350);
        sounding.setInitialLoad(true);

        sounding.addLayer(new SoundingLayer(1000.0f, 76f, -9999.0f, -9999.0f,
                -9999.0f, -9999.0f, -9999.0f));
        sounding.addLayer(new SoundingLayer(969.0f, 350f, 294.3f, 290.73f,
                6.18f, 80f, -9999.0f));
        sounding.addLayer(new SoundingLayer(925.0f, 757f, 294.1f, 292.7f,
                14.93f, 105f, -9999.0f));
        sounding.addLayer(new SoundingLayer(850.0f, 1490f, 293.7f, 293.6f,
                11.33f, 195f, -9999.0f));
        sounding.addLayer(new SoundingLayer(700.0f, 3155f, 285.1f, 277.1f,
                16.99f, 245f, -9999.0f));

        sounding.addLayer(new SoundingLayer(639.0f, 3910f, 278.3f, 276.3f,
                16.99f, 245f, -9999.0f));
        sounding.addLayer(new SoundingLayer(611.5f, 4267f, 275.3f, 273.7f,
                16.99f, 245f, -9999.0f));
        sounding.addLayer(new SoundingLayer(591.0f, 4543f, 273.0f, 271.6f,
                16.99f, 245f, -9999.0f));
        sounding.addLayer(new SoundingLayer(566.8f, 4877f, 270.4f, 265.9f,
                16.99f, 245f, -9999.0f));

        sounding.addLayer(new SoundingLayer(500.0f, 5860f, 265.6f, 254.6f,
                11.8f, 295f, -9999.0f));
        sounding.addLayer(new SoundingLayer(493.0f, 5970f, 265.4f, 252.4f, 38f,
                260f, -9999.0f));

        sounding.setInitialLoad(false);
        sounding.invalidate();

        for (SoundingLayer layer : sounding) {
            System.out.println(layer);
        }
        System.out.println("Total totals     = " + sounding.totalTotals());
        System.out.println("700mb Wind Speed = " + sounding.getWindSpeed700());
        System.out.println("500mb u comp     = " + sounding.getWindUComp500());
        System.out.println("Freezing level 1 = "
                + sounding.firstFreezingLevel());

        sounding = new VerticalSounding();
        sounding.setElevation(350);
        sounding.setInitialLoad(true);

        sounding.addLayer(new SoundingLayer(969.0f, 350f, 273.15f, MISSING,
                MISSING, MISSING, MISSING));
        sounding.addLayer(new SoundingLayer(925.0f, 757f, 275f, MISSING,
                MISSING, MISSING, MISSING));
        sounding.addLayer(new SoundingLayer(925.0f, 757f, 278f, MISSING,
                MISSING, MISSING, MISSING));

        sounding.setInitialLoad(false);
        sounding.invalidate();
        System.out.println("Freezing level 2 = "
                + sounding.firstFreezingLevel());

        sounding = new VerticalSounding();
        sounding.setElevation(350);
        sounding.setInitialLoad(true);

        sounding.addLayer(new SoundingLayer(969.0f, 350f, 263.15f, MISSING,
                MISSING, MISSING, MISSING));
        sounding.addLayer(new SoundingLayer(925.0f, 757f, 271f, MISSING,
                MISSING, MISSING, MISSING));
        sounding.addLayer(new SoundingLayer(925.0f, 1500f, 275f, MISSING,
                MISSING, MISSING, MISSING));

        sounding.setInitialLoad(false);
        sounding.invalidate();
        System.out.println("Freezing level 3 = "
                + sounding.firstFreezingLevel());

    }
}