package uk.ac.starlink.ttools.plot2.layer;

import uk.ac.starlink.ttools.plot2.data.TupleSequence;
import uk.ac.starlink.util.SplitCollector;

/**
 * Partial SplitCollector implementation for accumulating data in BinLists.
 *
 * @author   Mark Taylor
 * @since    27 Sep 2019
 */
public abstract class BinListCollector
        implements SplitCollector<TupleSequence,BinList> {

    private final Combiner combiner_;
    private final long nbin_;

    /**
     * Constructor.
     *
     * @param  combiner  combination mode
     * @param  nbin      bin index limit
     */
    public BinListCollector( Combiner combiner, long nbin ) {
        combiner_ = combiner;
        nbin_ = nbin;
    }

    public BinList createAccumulator() {
        return createDefaultBinList( combiner_, nbin_ );
    }

    public BinList combine( BinList binList1, BinList binList2 ) {
        return mergeBinLists( binList1, binList2 );
    }

    /**
     * Returns a BinList implementation suitable for a given number of
     * bins and a given combiner.
     * This may return an implementation based on a hash, or an array,
     * or some combination.
     *
     * @param  combiner  combiner
     * @param  size    maximum number of bins
     */
    public static BinList createDefaultBinList( Combiner combiner, long size ) {
        if ( size < 1e6 ) {
            BinList binList = combiner.createArrayBinList( (int) size );
            if ( binList != null ) {
                return binList;
            }
        }
        if ( size < Integer.MAX_VALUE ) {
            return new AdaptiveBinList( (int) size, combiner, 8 );
        }
        else {
            return new HashBinList( size, combiner );
        }
    }

    /**
     * Returns a BinList with contents corresponding to the aggregation
     * of two input BinLists.  The result is the same as if all the
     * data submitted to the input bin lists had instead been submitted
     * to the output one.  The result may or may not be the same object
     * as one of the inputs.  The inputs should not be used following
     * this operation.
     *
     * <p>This method will work for any BinList instances, but it is
     * intended for use with BinLists generated by
     * {@link #createDefaultBinList createDefaultBinList}.
     *
     * @param  binList1  first input
     * @param  binList2  second input
     * @return  aggregation of the inputs
     */
    public static BinList mergeBinLists( BinList binList1, BinList binList2 ) {
        BinList bl1 = binList1 instanceof AdaptiveBinList
                    ? ((AdaptiveBinList) binList1).getBaseBinList()
                    : binList1;
        BinList bl2 = binList2 instanceof AdaptiveBinList
                    ? ((AdaptiveBinList) binList2).getBaseBinList()
                    : binList2;
        if ( bl1 instanceof ArrayBinList && bl2 instanceof ArrayBinList ) {
            ((ArrayBinList) bl1).addBins( (ArrayBinList) bl2 );
            return bl1;
        }
        else if ( bl1 instanceof HashBinList ) {
            ((HashBinList) bl1).addBins( bl2 );
            return bl1;
        }
        else if ( bl2 instanceof HashBinList ) {
            ((HashBinList) bl2).addBins( bl1 );
            return bl2;
        }
        else {
            HashBinList bl =
                new HashBinList( bl1.getSize(), bl1.getCombiner() );
            bl.addBins( bl1 );
            bl.addBins( bl2 );
            return bl;
        }
    }
}
