/* Delft-FEWS
 * ================================================================
 *
 * Software Info: http://www.delft-fews.com
 * Contact Info:  Delft-FEWS Product Management (fews-pm@deltares.nl)
 *
 * (C) Copyright 2008, by Deltares
 *                        P.O. Box 177
 *                        2600 MH  Delft
 *                        The Netherlands
 *                        http://www.deltares.nl
 *
 * DELFT-FEWS: A platform for real time forecasting and water
 * resources management. Delft-FEWS is expert data handling and
 * model integration software for flood forecasting, drought and
 * seasonal forecasting, and real-time water resources management
 *
 * ----------------------------------------------------------------
 * TimeSeriesContentHandler.java
 * ----------------------------------------------------------------
 * (C) Copyright 2008, by Deltares
 */
package nl.wldelft.util.timeseries;

import nl.wldelft.util.ExternalFloatsReference;
import nl.wldelft.util.Missings;
import nl.wldelft.util.Period;
import nl.wldelft.util.Properties;
import nl.wldelft.util.Season;
import nl.wldelft.util.coverage.Geometry;
import nl.wldelft.util.geodatum.GeoDatum;
import nl.wldelft.util.ratingcurve.RatingCurveEquation;
import nl.wldelft.util.ratingcurve.RatingCurveInterpolationMethod;

import java.text.SimpleDateFormat;
import java.util.TimeZone;

/**
 * The interface <code>TimeSeriesContentHandler</code> represents the classes
 * to handle the time series data that are supplied  by the time series parsers.
 */
public interface TimeSeriesContentHandler {
    /**
     * Defines time zone which should be used while importing time series.
     * If time zone is defined in the file format - this TimeZone should not be used.
     * @return
     */
    TimeZone getDefaultTimeZone();

    void clearMissingValues();

    /**
     * Adds a value that should be recognized as missing when calling {@link #setValue(float)}, {@link #setValue(char, String)}  or {@link #setCoverageValues(float[])}
     * {@link Float#NaN} is always recognized as missing
     */
    void addMissingValue(float missingValue);

    /**
     * Adds a alpha numeric tag that should be recognized as missing when calling {@link #setValue(char, String)}
     * These alphanumeric missings are not recognized when using {@link #setValue(float)} or {@link #setCoverageValues(float[])}
     * The missings added with {@link #addMissingValue(float)}  and {@link #addMissingValueRange(float, float)}  are also recognized
     * {@link Float#NaN} is always recognized as missing
     *
     */
    void addMissingValue(String missingValue);

    /**
     * Adds a range of values that should be recognized as missing when calling {@link #setValue} or {@link #setCoverageValues(float[])}
     *  NaN, null and an empty string, string with only spaces are always recognized as missing
     */
    void addMissingValueRange(float minMissingValue, float maxMissingValue);

    /**
     * Creating an alias allows high speed switching between different headers
     * E.g. For files with multiple parameters per row, for every row multiple switches are required between different headers
     * This will not work properly without defining an alias for every column
     * The alias is ultimate fast in the range form 0 to 1000
     *
     * @param alias, integer for ultimate speed, good practice is to use the parameter column index.
     * @param header
     */
    void createTimeSeriesHeaderAlias(int alias, TimeSeriesHeader header);


    /**
     * Changes the header that will be used when calling {@link #applyCurrentFields()}
     * A call to this method will not consume any significant time
     * {@link #setTimeSeriesHeader(TimeSeriesHeader)}  is relatively time consuming
     * @see #createTimeSeriesHeaderAlias(int, TimeSeriesHeader)
     * @param alias defined with {@link #createTimeSeriesHeaderAlias (int, TimeSeriesHeader)}
     * @throws IllegalArgumentException when alias is not created before
     */
    void setTimeSeriesHeader(int alias);


    /**
     * Same as {@link #setTimeSeriesHeader(int)} , but slightly SLOWER
     * The second time this method is called for the SAME header,
     * there is NO new time series created but the first one is re-selected
     * This method is relatively time consuming.
     * When parsing multiple parameters per row use {@link #setTimeSeriesHeader (int)}
     */
    void setTimeSeriesHeader(TimeSeriesHeader header);

    /**
     * Changes the time that will be used when calling {@link #applyCurrentFields()}
     * A NEW time series is created, with a new forecast time and new ensemble member index
     * A warning is logged when this method is called twice for the same header (historical non ensemble time series)
     */
    void setNewTimeSeriesHeader(TimeSeriesHeader header);

    /**
     * The parser should call this method when it starts parsing time/values and has any idea of the period of the values that will come.
     * This information is only used by this content handler for OPTIMISATION and
     * is never required and never results in an error when the real period is differs from the estimated period
     * @param period
     */
    void setEstimatedPeriod(Period period);

    /**
     * Changes the time that will be used when calling {@link #applyCurrentFields()}
     *
     * @param time Represents the number of milliseconds since January 1, 1970, 00:00:00 GMT
     */
    void setTime(long time);

    /**
     * Changes the time that will be used when calling {@link #applyCurrentFields()}
     *
     * @param time Represents the number of milliseconds since January 1, 1970, 00:00:00 GMT
     * @param minTime Represents the number of milliseconds since January 1, 1970, 00:00:00 GMT
     * @param maxTime Represents the number of milliseconds since January 1, 1970, 00:00:00 GMT
     */
    void setTimeAndRange(long time, long startTime, long endTime);

    /**
     * Changes the time that will be used when calling {@link #applyCurrentFields()}
     *
     * @param timeZone
     * @param year eg 1996
     * @param oneBasedMonth 1 .. 12 (1==January)
     * @param dayOfMonth 1 .. 31
     * @param hourInDay 0 .. 23
     * @param minute 0 .. 59
     * @param second 0 .. 59
     * @param millis 0 .. 999
     */
    void setTime(TimeZone timeZone, int year, int oneBasedMonth, int dayOfMonth, int hourInDay, int minute, int second, int millis);


    /**
     * Changes the time that will be used when calling {@link #applyCurrentFields()}
     *
     * In addition to simple date format 24h will be recognized as 0h the next day
     *
     * @param timeZone. When not known use {@link #getDefaultTimeZone()}
     * @param pattern see {@link SimpleDateFormat}, in addition for HH  24 will be recognized as 0:00 the next day
     * @param dateTime leading and trailing spaces are ignored
     */
    void setTime(TimeZone timeZone, String pattern, String dateTime);

    /**
     * Changes the time that will be used when calling {@link #applyCurrentFields()}
     * <p>
     * In addition to simple date format 24h will be recognized as 0h the next day
     *
     * @param timeZone. When not known use {@link #getDefaultTimeZone()}
     * @param pattern   see {@link SimpleDateFormat}, in addition for HH  24 will be recognized as 0:00 the next day
     * @param dateTime  leading and trailing spaces are ignored
     * @param startDateTime  leading and trailing spaces are ignored
     * @param endDateTime  leading and trailing spaces are ignored
     */
    void setTimeAndRange(TimeZone timeZone, String pattern, String dateTime, String startDateTime, String endDateTime);

    /**
     * Changes the time that will be used when calling {@link #applyCurrentFields()}
     *
     * In addition to simple date format 24h will be recognized as 0h the next day
     *
     * @param timeZone. When not known use {@link #getDefaultTimeZone()}
     * @param datePattern see {@link SimpleDateFormat}
     * @param date leading and trailing spaces are ignored
     * @param timePattern see {@link SimpleDateFormat}, in addition for HH  24 will be recognized as 0:00 the next day
     * @param time leading and trailing spaces are ignored
     */
    void setTime(TimeZone timeZone, String datePattern, String date, String timePattern, String time);

    /**
     * Return false if any value for the selected time series with {@link #setTimeSeriesHeader} is wanted
     * When true parsing of ALL values for this time series can be skipped
     */
    boolean isCurrentTimeSeriesHeaderForAllTimesRejected();

    /**
     * Return false if the value selected time {@link #setTimeSeriesHeader} and selected time {@link #setTime(long)}  is wanted
     * When true parsing of the time and time series can be skipped
     */
    boolean isCurrentTimeSeriesHeaderForCurrentTimeRejected();

    /**
     * Return true if the selected time series {@link #setTimeSeriesHeader} is alphanumeric
     */
    boolean isCurrentTimeSeriesAlphanumeric();

    /**
     * Return true if the selected time series {@link #setTimeSeriesHeader} is of TimeSeriesType temporary
     */
    boolean isCurrentTimeSeriesTemporary();

    /**
     * Changes the flag that will be used for when calling {@link #applyCurrentFields()}
     */
    void setFlag(int flag);

    /**
     * Changes the flag that will be used for when calling {@link #applyCurrentFields()}
     */
    void setFlag(String flag);

    /**
     * Changes the flag that will be used for when calling {@link #applyCurrentFields()}
     */
    void setFlag(Flag flag);


    /**
     * Changes the flag source that will be used for when calling {@link #applyCurrentFields()}
     * When a flag is marked as manual the flag will be fixed until the value is changed
     * An automatic flag can be changed on automatic revalidation (rate of change)
     */
    void setFlagSource(String flag);

    /**
     * Clear all flag source columns.
     */
    void clearFlagSourceColumns();

    /**
     * Adds a new flag source column
     * The returned index should be used for {@link #setColumnFlagSource(int, String)}
     */
    int addFlagSourceColumn(String flagSourceColumnId);

    /**
     * Set the flag source for the specified flag source column
     * Use {@link #addFlagSourceColumn(String)} for the columnIndex
     */
    void setColumnFlagSource(int columnIndex, String flagSource);

    /**
     * Changes the sample id that will be used for when calling {@link #applyCurrentFields()}
     */
    void setSampleId(String sampleId);

    /**
     * Reject the last sample id set {@link #setSampleId(String)} The changes to the current sample are not saved.
     */
    void rejectSample();

    void setModifierName(String modifierName);

    /**
     * Changes the out of detection range that will be used when calling {@link #applyCurrentFields()}
     */
    void setOutOfDetectionRangeFlag(OutOfDetectionRangeFlag flag);

    /**
     * Changes the {@link State} that will be used when calling {@link #applyCurrentFields()}
     */
    void setState(State state);

    /**
     * Changes the comment that will be used when calling {@link #applyCurrentFields()}
     */
    void setComment(String comment);

    /**
     * Changes the user that will be used when calling {@link #applyCurrentFields()}
     */
    void setUser(String user);

    /**
     * Changes the properties that will be used when calling {@link #applyCurrentFields()}
     * The properties are used as extra tags, like comments, that is stored per value
     */
    void setProperties(Properties properties);

    /**
     * When a overruling geometry is defined (in grids.xml) this geometry will overturn the geometry set with {@link #setGeometry(Geometry)}
     * When there is a overruling geometry the content handler will log an error when the number of rows and cols is not the same a set with {@link #setGeometry(Geometry)}
     * @return
     */
    Geometry getOverrulingGeometry();

    /**
     * Used by the parser to create a geometry when there is no geometry info available in the file
     */
    GeoDatum getDefaultGeoDatum();

    /**
     * Changes the geometry that will be used when calling {@link #applyCurrentFields()}
     *
     * When only the number of rows and cols are available use {@link NonGeoReferencedGridGeometry#create(int, int))))|
     * @see #getDefaultGeoDatum()
     */
    void setGeometry(Geometry geometry);

    /**
     * Changes the value resolution that will be used when calling {@link #applyCurrentFields()}
     * Only be used when the file format don't uses IEEE floats to store the values
     * e.g. When there are only integers parsed the value resolution is 1.0
     * e.g. When there at maximum to decimals the value resolution is 0.01;
     * e.g. When file format store the values as integers and divides the integers by 5 afterwards the value resolution is 0.2
     *
     * @param valueResolution
     */
    void setValueResolution(float valueResolution);

    /**
     * Changes the domain axes value resolutions that will be used when calling {@link #applyCurrentFields()}
     * Only be used when the file format don't uses IEEE floats to store the values
     * e.g. When there are only integers parsed the value resolution is 1.0
     * e.g. When there at maximum to decimals the value resolution is 0.01;
     * e.g. When file format store the values as integers and divides the integers by 5 afterwards the value resolution is 0.2
     *
     * @param valueResolutions
     */
    void setDomainAxesValueResolutions(float... valueResolutions);

    /**
     * Changes the value that will be used when calling {@link #applyCurrentFields()}
     */
    void setValue(float value);

    /**
     * Changes the value that will be used when calling {@link #applyCurrentFields()}
     * When the value is missing the minValue and maxValue should also be missing (and visa versa)
     */
    void setValueAndRange(float value, float minValue, float maxValue);

    /**
     * Changes the value that will be used when calling {@link #applyCurrentFields()}
     * When the value can not be parsed an error will be logged, no exception is thrown
     * Add missing value tags before calling this function {@link #addMissingValue(String)}
     * e.g. addMissingValue('?')
     *
     * @param value leading and trailing spaces are ignored
     */
    void setValue(char decimalSeparator, String value);

    /**
     * Changes the value that will be used when calling {@link #applyCurrentFields()}
     * When the value can not be parsed an error will be logged, no exception is thrown
     * Add missing value tags before calling this function {@link #addMissingValue(String)}
     * e.g. addMissingValue('?')
     * When the value is missing the minValue and maxValue should also be missing (and visa versa)
     *
     * @param value leading and trailing spaces are ignored
     */
    void setValueAndRange(char decimalSeparator, String value, String minValue, String maxValue);

    /**
     * Puts the domain axes values. For a spectrum these are the frequencies. For directional spectrum the first domain axis is the frequency and the second domain axis is the direction.
     * The domain axis index is zero based.
     * For performance reasons do not parse the values when {@link #isCurrentTimeSeriesHeaderForCurrentTimeRejected()} returns true
     * For performance reasons do no recreate the values array for every time step again
     * When all values (e.g. all time steps) share the same domain axis values only call this method once
     *
     * @see #setValues
     */
    void setDomainAxesValues(float[]... domainAxesValues);

    /**
     * Puts the values for the specified domains. eg. For a spectrum these are the energy values for all the frequency bands
     * The number of values should equal the domain0.length * domain1.length * domain2.length etc {@link #setDomainAxesValues(float[]...)}
     * The index of one value when having two domains is domain1Index + domain0Index * domain1.length.
     * When having two domains the first domain axis represent the rows and the second domain axis represent the columns
     * The size of each domain axis may change every time step
     * For performance reasons do not parse the values when {@link #isCurrentTimeSeriesHeaderForCurrentTimeRejected()} returns true
     * For performance reasons do not call when the domain axes values are not changes. Most of the time all time steps share the same domain values
     * @see #setDomainAxesValues(float[]...)
     */
    void setValues(float[] values);

    /**
     * Puts the string values for the specified domains. Equivalent of the function {@link #setValues(float[] values)}
     * When one or more values can not be parsed an error will be logged, no exception is thrown
     * Add missing value tags before calling this function {@link #addMissingValue(String)}
     * e.g. addMissingValue('?')
     *
     * @param values leading and trailing spaces are ignored
     */
    void setValues(char decimalSeparator, String[] values);

    /**
     *  Puts the coverage values for the last set time and and last set header with the last set flag and last set geometry
     *  When {@link #getOverrulingGeometry ()} returns not null there is no need to to set the geometry
     *  When the active geometry does not have the same number of rows and cols an error message is logged.
     *  For performance reasons do not parse the values when {@link #isCurrentTimeSeriesHeaderForCurrentTimeRejected()} returns true
     *  For performance reasons do no recreate the values array for every time step again
     */
    void setCoverageValues(float[] values);

    void setCoverageValues(ExternalFloatsReference externalFloatsReference, float minValue, float maxValue, Missings missings);

    void setRatingCurveStageValues(float[] values);

    void setRatingCurveDischargeValues(float[] values);

    void setRatingCurveMinStageValue(float min);

    void setRatingCurveMaxStageValue(float max);

    void setRatingCurveInterpolationMethod(RatingCurveInterpolationMethod interpolationMethod);

    void setRatingCurveLogScaleStraightLineStageOffsets(float[] values);

    /**
     * Sets the rating curve table flags for each row, null is allowed
     */
    void setRatingCurveFlags(int[] flags);

    default void addRatingCurveEquation(RatingCurveEquation equation) {

    }

    void setRatingCurveSeason(Season season);

    void setRatingCurvePeriod(Period period);

    /**
     * When value is marked manual it can not be overwritten with an value that is marked automatic
     */
    void setValueSource(ValueSource flag);

    /**
     * Saves the current fields for the current time series header and current time.
     * The current fields are not cleared so it is only required
     * to update the changed fields for the next call to {@link #applyCurrentFields()}
     */
    void applyCurrentFields();

    /**
     * This optional method flushes the time series to the storage and when successful writes a new  savePointInfoText. With the {@link #getLastSavePointInfoText()} the parser knows where to continue. The savePointInfo text can be any text that is usually only interpretable by a specific parser.
     */
    void createSavePoint(String savePointInfoText);

    /**
     * Returns the last save point text so the parser knows where to continue
     */
    String getLastSavePointInfoText();
}

  • No labels