package org.das2.qds.examples; import java.text.ParseException; import java.util.logging.Level; import java.util.logging.Logger; import org.das2.datum.EnumerationUnits; import org.das2.datum.Units; import org.das2.datum.UnitsUtil; import org.das2.qds.ArrayDataSet; import org.das2.qds.DDataSet; import org.das2.qds.DataSetUtil; import org.das2.qds.JoinDataSet; import org.das2.qds.MutablePropertyDataSet; import org.das2.qds.QDataSet; import org.das2.qds.QubeDataSetIterator; import org.das2.qds.SemanticOps; import org.das2.qds.SparseDataSetBuilder; import org.das2.qds.WritableDataSet; import org.das2.qds.ops.Ops; import static org.das2.qds.ops.Ops.PI; import static org.das2.qds.ops.Ops.linspace; import static org.das2.qds.ops.Ops.ripples; import org.das2.util.ColorUtil; /** * For the various QDataSet schemes, show examples and documentation for * each. This was motivated when trying to describe the output of * org.das2.graph.ContoursRenderer.doAutorange() * * Note all QDataSets are "duck-typed," meaning if they happen to meet the * requirements of an interface then they are an instance of the interface. * * All schemes can be executed using reflection, looking for methods starting * with "is". This Jython code scans through the class looking for all the * methods which make a dataset: *
{@code
 * mm= dir( Schemes )
 * i=0
 * for m in mm:
 * if ( m[0:2]=='is' ):
 *     continue
 * else:
 *     i=i+1
 * }
 * 
 * @see https://github.com/autoplot/dev/blob/master/bugs/2022/20220125/showAllRanks.md
 * @author jbf 
 */
public class Schemes {
    
    private static Logger logger= Logger.getLogger("qdataset.schemes");
    
    /**
     * return a bounding box for the data.  This is a rank 2 dataset where
     * ds[0,:] shows the bounds for the first dimension and ds[1,:] shows the
     * bounds for the second dimension.  Therefor ds[0,0] is the minumum extent
     * of the first dimension, and ds[0,1] is the maximum.
     * Note this can be extended to any number
     * of dimensions (cube or hypercube).
     * 
     * Note,
     *
     *from org.das2.qds.examples import Schemes
     *ds= Schemes.boundingBox()
     *print asDatumRange(ds.slice(0))
     *
     *from org.das2.qds.examples import Schemes
     *ds= Schemes.rank2Waveform()
     *deltaT= ds.property( QDataSet.DEPEND_1 )
     *ddeltaT= diffs(dep1)
     *print ddeltaT[0], ddeltT[-1] # should be the same
     *
     *from org.das2.qds.examples import Schemes
     *ds= Schemes.vectorTimeSeries()
     *plot( magnitude( ds ) )
     *plot( unbundle( ds, 0 ) )
     *
     * dataset→rank2bundle→vectorTimeSeries.
     * @return rank 2 vector time series.
     */
    public static QDataSet vectorTimeSeries( ) {
        return Ops.ripplesVectorTimeSeries(1440);
    }
    
    /**
     * return true if the data is a vector time series.
     * @param ds a dataset
     * @return  true if the data is a vector time series.
     */
    public static boolean isVectorTimeSeries( QDataSet ds ) {
        return ds.rank()==2 && ( Ops.isLegacyBundle(ds) || Ops.isBundle(ds) ) && isTimeSeries(ds);
    }
    
    /**
     * return a rank 2 simple spectrogram, which has two indeces.
     * @return rank 2 simple spectrogram
     */
    public static QDataSet simpleSpectrogram() {
        return Ops.ripples(40,30);
    }
        
    /**
     * return true if the data is a simple spectrogram, which is 
     * rank 2, and not a small bundle.
     * @param ds a dataset
     * @return  true if the data is a simple spectrogram.
     */
    public static boolean isSimpleSpectrogram( QDataSet ds ) {
        if ( ds.rank()==2 ) {
            return !(ds.length(0)<4 && ( Ops.isBundle(ds) || Ops.isLegacyBundle(ds) ));
        } else {
            return false;
        }
    }
    /**
     * return a rank 1 scalar time series.
     * @return a rank 1 scalar time series.
     */
    public static QDataSet scalarTimeSeries() {
        try {
            QDataSet density= Ops.add( Ops.ripples(20), Ops.randomn(0,20) );
            density= Ops.putProperty( density, QDataSet.UNITS, Units.pcm3 );
            QDataSet t = Ops.timegen("2011-10-24", "20 sec", 20 );        
            return Ops.link( t, density );
        } catch (ParseException ex) {
            throw new RuntimeException(ex);
        }
    }
    
    /**
     * return true if the data is a simple time series of scalars.
     * @param ds a dataset
     * @return  true if the data is a simple spectrogram.
     */
    public static boolean isScalarTimeSeries( QDataSet ds ) {
        return ds.rank()==1 && isTimeSeries(ds);
    }
    
    /**
     * return a rank 1 scalar series with errors.
     * @return  a rank 1 scalar series with errors.
     */
    public static QDataSet scalarSeriesWithErrors() {
        QDataSet x= Ops.add( 1, Ops.divide( Ops.findgen(41),2 ) );
        QDataSet y= Ops.exp( Ops.multiply( -1, Ops.pow( Ops.subtract(x,10), 2 ) ) );
        MutablePropertyDataSet result= Ops.maybeCopy( Ops.link( x, y ) );
        result.putProperty( QDataSet.DELTA_PLUS, Ops.replicate(0.04,41) );
        result.putProperty( QDataSet.DELTA_MINUS, Ops.replicate(0.04,41) );
        return result;
    }
    
    /**
     * return true is the data is a simple series of scalars with errors.
     * @param ds dataset
     * @return true is the data is a simple series of scalars with errors.
     */
    public static boolean isScalarSeriesWithErrors( QDataSet ds ) {
        return ds.rank()==1
                && ds.property(QDataSet.DELTA_PLUS)!=null 
                && ds.property(QDataSet.DELTA_MINUS)!=null;
    }
    
    /**
     * return a rank 2 simple spectrogram, which has two indeces
     * and is a TimeSeries.
     * @return simple spectrogram time series
     */
    public static QDataSet simpleSpectrogramTimeSeries() {
        return Ops.ripplesSpectrogramTimeSeries(1440);
    }
        
    /**
     * return true if the data is a simple spectrogram.
     * @param ds a dataset
     * @return  true if the data is a simple spectrogram.
     */
    public static boolean isSimpleSpectrogramTimeSeries( QDataSet ds ) {
        return isSimpleSpectrogram(ds) && isTimeSeries(ds);
    }
    
    /**
     * returns true if the dataset is a time series.  This is either something 
     * that has DEPEND_0 as a dataset with time location units, or a join of 
     * other datasets that are time series.
     * @param ds a dataset
     * @return true if the dataset is a time series.
     * @see SemanticOps#isTimeSeries(org.das2.qds.QDataSet) 
     */
    public static boolean isTimeSeries( QDataSet ds ) {
        return SemanticOps.isTimeSeries(ds);
    }
    
    /**
     * uniform cadence is when each tag is the same distance apart, within a reasonable threshold.
     * @return dataset with uniform cadence
     */
    public static QDataSet uniformCadence() {
        return Ops.linspace( 0., 4., 100 );
    }
    
    /**
     * return true of the data has a uniform cadence.  Note that 
     * the CADENCE property is ignored.
     * @param ds a rank 1 dataset
     * @return true if the data has uniform cadence.
     */
    public static boolean isUniformCadence( QDataSet ds ) {
        if ( ds.rank()!=1 ) return false;
        double dv= ds.value(1)-ds.value(0);
        double manyDv= ( ds.value(ds.length()-1)-ds.value(0) ) / ( ds.length()-1)  ;
        return Math.abs( ( manyDv - dv ) / dv ) < 0.001;
    }
    /**
     * uniform ratiometric cadence is when the tags are uniform in log space.
     * @return dataset with uniform ratiometric cadence.
     */
    public static QDataSet uniformRatiometricCadence() {
        return Ops.pow( 10, Ops.linspace( 0., 4., 100 ) );
    }   
    /**
     * return true of the data has a uniform cadence in log space.  Note that 
     * the CADENCE property is ignored.
     * @param ds a rank 1 dataset
     * @return true if the data has uniform cadence.
     */
    public static boolean isUniformRatiometricCadence( QDataSet ds ) {
        if ( ds.rank()!=1 ) return false;
        if ( !UnitsUtil.isRatioMeasurement( SemanticOps.getUnits(ds) ) ) return false;
        double dv= Math.log( ds.value(1)/ds.value(0) );
        double manyDv= Math.log( ds.value(ds.length()-1) / ds.value(0) ) / ( ds.length()-1 );
        return Math.abs( ( manyDv - dv ) / dv ) < 0.001;
    }
    
    /**
     * return an example of a compositeImage.
     * @return image[320,240,3]
     */    
    public static QDataSet compositeImage() {
        WritableDataSet rgb= Ops.zeros( 320, 240, 3 );
        for ( int i=0; i<320; i++ ) {
            for ( int j=0; j<240; j++ ) {
               if ( ( Math.pow((i-160),2) +  Math.pow((j-120),2) ) <2500 ) rgb.putValue( i,j,0, 255 );
               if ( i<160 ) rgb.putValue( i,j,1, 255 );
               if ( j<120 ) rgb.putValue( i,j,2, 255 );
            }
        }
        rgb.putProperty( QDataSet.DEPEND_0, Ops.linspace(0,4,rgb.length() ) );
        rgb.putProperty( QDataSet.DEPEND_1, Ops.pow( 10, Ops.linspace(0,4,rgb.length(0) ) ) );
        rgb.putProperty( QDataSet.RENDER_TYPE, QDataSet.VALUE_RENDER_TYPE_COMPOSITE_IMAGE );
                
        return rgb;
    }
    
    /**
     * return true if the dataset is a composite image, and is plottable
     * with the RGBImageRenderer
     * @param ds a dataset
     * @return true if the dataset is a composite image.
     */
    public static boolean isCompositeImage( QDataSet ds ) {
        QDataSet dep0= (QDataSet) ds.property(QDataSet.DEPEND_0);
        QDataSet dep1= (QDataSet) ds.property(QDataSet.DEPEND_1);
        return ds.rank()==3 && DataSetUtil.checkQube(ds) 
                && ( dep0==null || isUniformCadence(dep0) || isUniformRatiometricCadence(dep0) ) 
                && ( dep1==null || isUniformCadence(dep1) || isUniformRatiometricCadence(dep1) );
    }
    
    /**
     * return example events list.  This is a four-column rank 2 dataset with
     * start time, end time, RGB color, and ordinal data for the message.
     * @return example events list.
     */
    public static QDataSet eventsList( ) {
        try {
            QDataSet xx= Ops.timegen( "2015-01-01", "60s", 1440 );
            QDataSet dxx= Ops.putProperty( Ops.replicate( 45, 1440 ), QDataSet.UNITS, Units.seconds );
            QDataSet color= Ops.replicate( 0xFFAAAA, 1440 );
            for ( int i=100; i<200; i++ ) ((WritableDataSet)color).putValue( i, 0xFFAAFF );
            EnumerationUnits eu= EnumerationUnits.create("default");
            QDataSet msgs= Ops.putProperty( Ops.replicate( eu.createDatum("on1").doubleValue(eu), 1440 ), QDataSet.UNITS, eu );
            QDataSet result= Ops.bundle( xx, Ops.add(xx,dxx), color, msgs );
            return result;
        } catch (ParseException ex) {
            throw new IllegalArgumentException(ex);
        }
    }
    
    /**
     * return a canonical event
     * @return a canonical event
     */
    public static QDataSet canonicalEvent() {
        QDataSet ds= Ops.bundle( Ops.dataset("2025-05-06T14:29"), 
                Ops.dataset("2025-05-06T14:31"), 
                Ops.dataset(Units.rgbColor.createDatum(ColorUtil.SKY_BLUE.getRGB())),
                Ops.dataset(Units.nominal().createDatum("Sunny and Warm")) );
        
        DataSetUtil.toString(ds);
        return ds;
    }
    
    /**
     * returns true if the dataset is a rank 1 canonical event, having 
     * three or four elements, the first two are times or the same units and
     * the last is a nominal datum describing the event.
     * @param ds dataset
     * @return true if is a canonical event.
     */
    public static boolean isCanonicalEvent( QDataSet ds ) {
        if ( ds.rank()!=1 ) return false;
        QDataSet bundle0= (QDataSet) ds.property(QDataSet.BUNDLE_0);
        if ( bundle0!=null ) {
            if ( bundle0.length()==3 || bundle0.length()==4 || bundle0.length()==5 ) {
                Units u0= (Units) bundle0.property(QDataSet.UNITS,0);
                if ( u0==null ) u0= Units.dimensionless;
                Units u1= (Units) bundle0.property(QDataSet.UNITS,1);
                if ( u1==null ) u1= Units.dimensionless;
                Units u3= (Units) bundle0.property(QDataSet.UNITS,bundle0.length()-1);
                boolean t1t2= UnitsUtil.isTimeLocation(u0) && UnitsUtil.isTimeLocation(u1);
                if ( t1t2 || ( u3!=null && UnitsUtil.isOrdinalMeasurement(u3) && u0.getOffsetUnits().isConvertibleTo(u1) ) ) {
                    if ( u0.isConvertibleTo(u1) ) {
                        QDataSet isge= Ops.ge( Ops.slice0( ds, 1 ), Ops.slice0( ds, 0 ) );
                        return Ops.total(isge) == Ops.total( Ops.valid( Ops.slice0(ds,0) ) );
                    } else {
                        QDataSet isge= Ops.ge( Ops.abs( Ops.slice0( ds, 1 ) ), 0. );
                        return Ops.total(isge) == Ops.total( Ops.valid( Ops.slice0(ds,0) ) );
                    }
                } else if ( u3!=null  && UnitsUtil.isOrdinalMeasurement(u3) && u0.isConvertibleTo(u1) ) {
                    QDataSet isge= Ops.ge( Ops.slice0( ds, 1 ), Ops.slice0( ds, 0 ) );
                    return Ops.total(isge) == Ops.total( Ops.valid( Ops.slice0(ds,0) ) );
                }
            } else {
                Units u3= (Units) bundle0.property(QDataSet.UNITS,bundle0.length()-1);
                if ( u3!=null && UnitsUtil.isOrdinalMeasurement(u3) ) {
                    return true;
                }
            }
        } 
        return false; //TODO: this means a slice of some events lists are not events!
        
    }
    /**
     * return true if the data is an events list.
     * @param ds a dataset
     * @return true if the data is an events list.
     */
    public static boolean isEventsList( QDataSet ds ) {
        QDataSet bundle1= (QDataSet) ds.property(QDataSet.BUNDLE_1);
        if ( bundle1!=null ) {
            if ( bundle1.length()==3 || bundle1.length()==4 || bundle1.length()==5 ) {
                Units u0= (Units) bundle1.property(QDataSet.UNITS,0);
                if ( u0==null ) u0= Units.dimensionless;
                Units u1= (Units) bundle1.property(QDataSet.UNITS,1);
                if ( u1==null ) u1= Units.dimensionless;
                Units u3= (Units) bundle1.property(QDataSet.UNITS,bundle1.length()-1);
                boolean t1t2= UnitsUtil.isTimeLocation(u0) && UnitsUtil.isTimeLocation(u1);
                if ( t1t2 || ( u3!=null && UnitsUtil.isOrdinalMeasurement(u3) && u0.getOffsetUnits().isConvertibleTo(u1) ) ) {
                    if ( u0.isConvertibleTo(u1) ) {
                        QDataSet isge= Ops.ge( Ops.slice1( ds, 1 ), Ops.slice1( ds, 0 ) );
                        return Ops.total(isge) == Ops.total( Ops.valid( Ops.slice1(ds,0) ) );
                    } else {
                        QDataSet isge= Ops.ge( Ops.abs( Ops.slice1( ds, 1 ) ), 0. );
                        return Ops.total(isge) == Ops.total( Ops.valid( Ops.slice1(ds,0) ) );
                    }
                } else if ( u3!=null  && UnitsUtil.isOrdinalMeasurement(u3) && u0.isConvertibleTo(u1) ) {
                    QDataSet isge= Ops.ge( Ops.slice1( ds, 1 ), Ops.slice1( ds, 0 ) );
                    return Ops.total(isge) == Ops.total( Ops.valid( Ops.slice1(ds,0) ) );
                }
            } else {
                Units u3= (Units) bundle1.property(QDataSet.UNITS,bundle1.length()-1);
                if ( u3!=null && UnitsUtil.isOrdinalMeasurement(u3) ) {
                    return true;
                }            
            }
        } else {
            if ( SemanticOps.getUnits(ds) instanceof EnumerationUnits ) {
                QDataSet dep0= (QDataSet) ds.property(QDataSet.DEPEND_0);
                if ( dep0!=null ) {
                    return true;
                }
            }
        }
        return false;
    }
    
    /**
     * return example angle distribution.
     * @return example angle distribution.
     */
    public static QDataSet angleDistribution( ) {
        ArrayDataSet rip= ArrayDataSet.maybeCopy( ripples( 30, 15 ) );
        QDataSet angle= linspace( PI/30/2, PI-PI/30/2, 30 );
        angle= Ops.putProperty( angle, QDataSet.UNITS, Units.radians );
        QDataSet rad= linspace( 1, 5, 15 );
        rip.putProperty( QDataSet.DEPEND_0, angle );
        rip.putProperty( QDataSet.DEPEND_1, rad );
        rip.putProperty( QDataSet.RENDER_TYPE, "pitchAngleDistribution" );
        return rip;
    }
    
    /**
     * return example angle distribution.
     * @param i the example number.  0..n-1.
     * @return example angle distribution.
     */
    public static QDataSet angleDistribution( int i ) {
        if ( i==0 ) {
            ArrayDataSet rip= ArrayDataSet.maybeCopy( Ops.randn( 24, 15 ) );
            for ( int j= 0; j<15; j++ ) {
                rip.putValue( 0, j, 20. );
                rip.putValue( 4, j, 20. );
            }
            
            QDataSet angle= Ops.multiply( linspace( 0.5, 23.5, 24 ), 15 );
            angle= Ops.putProperty( angle, QDataSet.UNITS, Units.degrees );
            QDataSet rad= linspace( 1, 5, 15 );
            rip.putProperty( QDataSet.DEPEND_0, angle );
            rip.putProperty( QDataSet.DEPEND_1, rad );
            rip.putProperty( QDataSet.RENDER_TYPE, "pitchAngleDistribution" );
            return rip;
            
        } else {
            return null;
        }
    }
    
    /**
     * return true if the data is an angle distribution.
     * @param ds a dataset
     * @return true if the data is an angle distribution.
     */
    public static boolean isAngleDistribution( QDataSet ds ) {
        if ( ds.rank()!=2 ) return false;
        QDataSet ads= (QDataSet)ds.property(QDataSet.DEPEND_0);
        //QDataSet rds= (QDataSet)ds.property(QDataSet.DEPEND_1);
        Units au= (Units) ads.property(QDataSet.UNITS);
        if ( au!=null && !( au==Units.dimensionless || au.isConvertibleTo(Units.degrees) ) ) {
            return false;
        }
        return true;
    }
    
    /**
     * A complexBundleDataSet is a set of datasets which have different rank.  The
     * rank 2 datasets take multiple columns of the dataset, and rank 3 and 4 datasets
     * are unrolled to make them rank 2 and the property ELEMENT_DIMENSIONS is used
     * to re-form them.  This returns an example bundle dataset that bundles timetags, density, velocity, and flux.
     * Yes, this was coded this twice, not realizing it was already done.  This code
     * is probably easier to read, so it is left in.
     * 
     * @return an example bundle dataset 
     * @see #complexBundleDataSet() 
     */
    public static QDataSet complexBundleDataSet2() {
        DDataSet data= DDataSet.createRank2( 12, 9 );
        QDataSet ttags= Ops.linspace( "2019-03-07T00:30Z", "2019-03-07T11:30Z", 12 );
        QDataSet vel= vectorTimeSeries();
        QDataSet dens= scalarTimeSeries();
        QDataSet flux= simpleSpectrogram();
        for ( int i=0; i