SquareChunk.java

/*******************************************************************************
 * Copyright (c) 2021 Handy Tools for Distributed Computing (HanDist) project.
 *
 * This program and the accompanying materials are made available to you under
 * the terms of the Eclipse Public License 1.0 which accompanies this
 * distribution,
 * and is available at https://www.eclipse.org/legal/epl-v10.html
 *
 * SPDX-License-Identifier: EPL-1.0
 ******************************************************************************/
package handist.collections;

import handist.collections.function.LongTBiConsumer;
import handist.collections.function.SquareIndexTConsumer;
import handist.collections.function.SquareIndexTFunction;

import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.Iterator;
import java.util.List;
import java.util.function.*;


public class SquareChunk<T> /* extends SquareRangedList<T>*/ implements Serializable, SquareRangedList<T> /*, KryoSerializable*/ {

    private static final long serialVersionUID = -8282656629771334491L;
    /** Array containing the T objects */
    private final Object[] a;

    /** Range on which this instance is defined */
    private final SquareRange range;
    private final long innerSize;

//    /**
//     * Builds a Chunk with the given range and no mapping.
//     * <p>
//     * The given LongRange should have a strictly positive size. Giving a
//     * {@link LongRange} instance with identical lower and upper bounds will result
//     * in a {@link IllegalArgumentException} being thrown.
//     * <p>
//     * If the {@link LongRange} provided has a range that exceeds
//     * {@value Config#maxChunkSize}, an
//     * {@link IllegalArgumentException} will be be thrown.
//     *
//     * @param range the range of the chunk to build
//     * @throws IllegalArgumentException if a {@link SquareChunk} cannot be built with the
//     *                                  provided range.
//     */
    public SquareChunk(SquareRange range) {
        long outerSize = range.outer.size();
        this.innerSize = range.inner.size();
        long size = outerSize * innerSize;
        if(range.isUpperTriangle) throw new IllegalArgumentException("Rectangle ranges are not supported by SquareChunk yet.");
        // FIXME
        if(size > Integer.MAX_VALUE) throw new RuntimeException("array size overflow");
        a = new Object[(int) size];
        this.range = range;
    }

//    /**
//     * Builds a {@link SquareChunk} with the provided {@link LongRange}. The provided
//     * initializer generates the initial value of the element for each index. The
//     * given LongRange should have a strictly positive size. Giving a
//     * {@link LongRange} instance with identical lower and upper bounds will result
//     * in a {@link IllegalArgumentException} being thrown.
//     * <p>
//     * If the {@link LongRange} provided has a range that exceeds
//     * {@value Config#maxChunkSize}, an
//     * {@link IllegalArgumentException} will be be thrown.
//     *
//     * @param range       the range of the chunk to build
//     * @param initializer generates the initial value of the element for each index.
//     * @throws IllegalArgumentException if a {@link SquareChunk} cannot be built with the
//     *                                  provided range.
//     */
    public SquareChunk(SquareRange range, SquareIndexTFunction<T> initializer) {
        this(range);
        range.forEach((long index, long index2) -> {
            set(index, index2, initializer.apply(index, index2));
        });
    }

//    /**
//     * Builds a {@link SquareChunk} with the provided {@link LongRange} and an initial
//     * mapping for each long in the object array. The provided {@link LongRange} and
//     * Object array should have the same size. An {@link IllegalArgumentException}
//     * will be thrown otherwise.
//     * <p>
//     * The given {@link LongRange} should have a strictly positive size. Giving a
//     * {@link LongRange} instance with identical lower and upper bounds will result
//     * in a {@link IllegalArgumentException} being thrown.
//     * <p>
//     * If the {@link LongRange} provided has a range that exceeds
//     * {@value Config#maxChunkSize}, an
//     * {@link IllegalArgumentException} will be be thrown.
//     *
//     * @param range the range of the chunk to build
//     * @param a     array with the initial mapping for every long in the provided
//     *              range
//     * @throws IllegalArgumentException if a {@link SquareChunk} cannot be built with the
//     *                                  provided range and object array.
//     */
//    public SquareChunk(LongRange range, Object[] a) {
//        this(range);
//        if (a.length != range.size()) {
//            throw new IllegalArgumentException("The length of the provided " + "array <" + a.length
//                    + "> does not match the size of the " + "LongRange <" + range.size() + ">");
//        }
//        // TODO Do we check for objects in array a that are not of type T?
//        // We can leave as is and let the code fail later in methods get and
//        // others where a ClassCastException should be thrown.
//        this.a = a;
//    }

//    /**
//     * Builds a {@link SquareChunk} with the provided {@link LongRange} with each long in
//     * the provided range mapped to object t. The given LongRange should have a
//     * strictly positive size. Giving a {@link LongRange} instance with identical
//     * lower and upper bounds will result in a {@link IllegalArgumentException}
//     * being thrown.
//     * <p>
//     * If the {@link LongRange} provided has a range that exceeds
//     * {@value Config#maxChunkSize}, an
//     * {@link IllegalArgumentException} will be be thrown.
//     *
//     * @param range the range of the chunk to build
//     * @param t     initial mapping for every long in the provided range
//     * @throws IllegalArgumentException if a {@link SquareChunk} cannot be built with the
//     *                                  provided range.
//     */
//    public SquareChunk(LongRange range, T t) {
//        this(range);
//        // TODO Is this what we really want to do?
//        // The mapping will be on the SAME OBJECT for every long in LongRange.
//        // Don't we need a Generator<T> generator as argument and create an
//        // instance for each key with Arrays.setAll(a, generator) ?
//        Arrays.fill(a, t);
//    }

//    /**
//     * Returns a new Chunk defined on the same {@link LongRange} and with the same
//     * contents as this instance.
//     *
//     * @return a copy of this instance
//     */
//    @Override
//    public SquareChunk<T> clone() {
//        // Object[] aClone = a.clone();
//        final Object[] aClone = new Object[a.length];
//
//        //// FIXME: 2018/09/19 Need deep copy?
//        // for (int i = 0; i < a.length; i++) {
//        // try {
//        // aClone[i] = ((Cloneable) a[i]).clone();
//        // } catch (CloneNotSupportedException e) {
//        // e.printStackTrace();
//        // }
//        // }
//
//        Arrays.fill(aClone, a[0]);
//        System.arraycopy(a, 0, aClone, 0, a.length);
//
//        return new SquareChunk<>(this.range, aClone);
//    }

//    @Override
//    public SquareChunk<T> cloneRange(LongRange newRange) {
//        return range == newRange ? clone() : toChunk(newRange);
//    }
//
//    @Override
//    public boolean contains(Object v) {
//        for (final Object e : a) {
//            if (v == null ? e == null : v.equals(e)) {
//                return true;
//            }
//        }
//        return false;
//    }

//    @Override
//    public boolean equals(Object o) {
//        return RangedList.equals(this, o);
//    }

    // TODO
//    @Override
//    public <U> void forEach(LongRange range, BiConsumer<? super T, Consumer<? super U>> action,
//            Consumer<? super U> receiver) {
//        rangeCheck(range);
//        // IntStream.range(begin, end).forEach();
//        for (long i = range.from; i < range.to; i++) {
//            action.accept(get(i), receiver);
//        }
//    }

    static class MySquareSiblingAccessor<T> implements SquareSiblingAccessor<T> {
        int offset;
        int innerSize;
        Object[] a;

        public MySquareSiblingAccessor(int offset, int innerSize, Object[] a) {
            this.offset = offset;
            this.innerSize = innerSize;
            this.a = a;
        }

        @Override
        public T get(int x, int y) {
            assert(x<=1 && x>=-1);
            assert(y<=1 && y>=-1);
            return (T)a[offset + x*innerSize + y];
        }
        @Override
        public void put(T v) {
            a[offset] = v;
        }
    }

    static abstract class MyView<T> extends RangedList<T> {
        @Override
        public RangedList<T> cloneRange(LongRange range) {
            throw new UnsupportedOperationException("not implemented yet. may be shared abst class will be needed");
        }

        @Override
        public boolean contains(Object o) {
            throw new UnsupportedOperationException("not implemented yet. may be shared abst class will be needed");
        }

        @Override
        public T get(long index) {
            throw new UnsupportedOperationException("not implemented yet. may be shared abst class will be needed");
        }

        @Override
        public T set(long index, T value) {
            throw new UnsupportedOperationException("not implemented yet. may be shared abst class will be needed");
        }

        @Override
        public Object[] toArray() {
            throw new UnsupportedOperationException("not implemented yet. may be shared abst class will be needed");
        }

        @Override
        public Object[] toArray(LongRange r) {
            throw new UnsupportedOperationException("not implemented yet. may be shared abst class will be needed");
        }

        @Override
        public Chunk<T> toChunk(LongRange r) {
            throw new UnsupportedOperationException("not implemented yet. may be shared abst class will be needed");
        }

        @Override
        public List<T> toList(LongRange r) {
            throw new UnsupportedOperationException("not implemented yet. may be shared abst class will be needed");
        }
    }

    static class RowView<T> extends MyView<T> {
        int offset;
        LongRange baseRange;
        Object[] a;
        // TODO sublist based impl of iterators.

        public RowView(int offset, LongRange baseRange, Object[] a) {
            this.offset = offset;
            this.baseRange = baseRange;
            this.a = a;
        }
        public LongRange getRange() { return baseRange; }

        @Override
        public <U> void forEachImpl(LongRange range, BiConsumer<? super T, Consumer<? super U>> action,
                                Consumer<? super U> receiver) {
            // TODO rangeCheck(range);
            long current = range.from;
            int index = offset + (int)(range.from - baseRange.from);
            while(current++ < range.to) {
                action.accept((T)a[index++], receiver);
            }
        }

        @Override
        public void forEachImpl(LongRange range, Consumer<? super T> action) {
            // TODO rangeCheck(range);
            long current = range.from;
            int index = offset + (int)(range.from - baseRange.from);
            while(current++ < range.to) {
                action.accept((T)a[index++]);
            }
        }

        @Override
        public void forEachImpl(LongRange range, LongTBiConsumer<? super T> action) {
            // TODO rangeCheck(range);
            long current = range.from;
            int index = offset + (int)(range.from - baseRange.from);
            while(current < range.to) {
                action.accept(current++, (T)a[index++]);
            }
        }

        @Override
        public Iterator<T> iterator() {
            return new ChunkIterator<>(offset, baseRange, a);
        }

        @Override
        public RangedListIterator<T> listIterator() {
            return new ChunkListIterator<>(offset, baseRange, a);
        }

        @Override
        public RangedListIterator<T> listIterator(long from) {
            return new ChunkListIterator<>(offset, baseRange, from, a);
        }

        @Override
        protected Iterator<T> subIterator(LongRange range) {
            //TODO rangecheck
            int newOffset = offset+(int)(range.from-baseRange.from);
            return new ChunkIterator<>(newOffset, range, a);
        }

        @Override
        protected RangedListIterator<T> subListIterator(LongRange range) {
            //TODO range check
            int newOffset = offset+(int)(range.from-baseRange.from);
            return new ChunkListIterator<>(newOffset, range, a);
        }

        @Override
        protected RangedListIterator<T> subListIterator(LongRange range, long from) {
            //TODO range check
            int newOffset = offset+(int)(range.from-baseRange.from);
            return new ChunkListIterator<>(newOffset, range, from, a);
        }
    }

    static class ColumnView<T> extends MyView<T> {
        int offset;
        LongRange baseRange;
        int stride;
        Object[] a;
        // TODO sublist based impl of iterators.

        public ColumnView(int offset, int stride, LongRange baseRange, Object[] a) {
            this.offset = offset;
            this.baseRange = baseRange;
            this.stride = stride;
            this.a = a;
        }
        public LongRange getRange() { return baseRange; }
        @Override
        public <U> void forEachImpl(LongRange range, BiConsumer<? super T, Consumer<? super U>> action,
                                Consumer<? super U> receiver) {
            // TODO rangeCheck(range);
            long current = range.from;
            int index = offset + (int)(range.from - baseRange.from);
            while(current++ < range.to) {
                action.accept((T)a[index], receiver);
                index += stride;
            }
        }

        @Override
        public void forEachImpl(LongRange range, Consumer<? super T> action) {
            // TODO rangeCheck(range);
            long current = range.from;
            int index = offset + (int)(range.from - baseRange.from);
            while(current++ < range.to) {
                action.accept((T)a[index]);
                index+= stride;
            }
        }

        @Override
        public void forEachImpl(LongRange range, LongTBiConsumer<? super T> action) {
            // TODO rangeCheck(range);
            long current = range.from;
            int index = offset + (int)(range.from - baseRange.from)*stride;
            while(current < range.to) {
                action.accept(current++, (T)a[index]);
                index += stride;
            }
        }
        @Override
        public Iterator<T> iterator() {
            return new ColumnIterator<>(offset, baseRange, a, stride);
        }

        @Override
        public RangedListIterator<T> listIterator() {
            return new ColumnListIterator<>(offset, baseRange, a, stride);
        }

        @Override
        public RangedListIterator<T> listIterator(long from) {
            return new ColumnListIterator<>(offset, baseRange, from, a, stride);
        }

        @Override
        protected Iterator<T> subIterator(LongRange range) {
            // TODO check
            int newOffset = offset + (int)(range.from-baseRange.from)* stride;
            return new ColumnIterator<>(newOffset, range, a, stride);
        }

        @Override
        protected RangedListIterator<T> subListIterator(LongRange range) {
            int newOffset = offset + (int)(range.from-baseRange.from)* stride;
            return new ColumnListIterator<>(newOffset, range, a, stride);
        }

        @Override
        protected RangedListIterator<T> subListIterator(LongRange range, long from) {
            int newOffset = offset + (int)(range.from-baseRange.from)* stride;
            return new ColumnListIterator<>(newOffset, range, from, a, stride);
        }
    }
    @Override
    public RangedList<T> getRowView(long row) {
        // TODO range check
        int offset = (int)(innerSize * (row - getRange().outer.from));
        return new RowView<>(offset, getRange().inner, a); // TODO filter dependent
    }

    @Override
    public RangedList<T> getColumnView(long column) {
        // TODO range check
        int offset = (int)(column - getRange().inner.from);
        return new ColumnView<>(offset, (int)innerSize, getRange().outer, a); // TODO filter dependent
    }
    @Override
    public void forEachColumn(LongTBiConsumer<RangedList<T>> columnAction) {
        for(long index = getRange().inner.from; index < getRange().inner.to; index++) {
            RangedList<T> cView = getColumnView(index);
            columnAction.accept(index, cView);
        }
    }
    @Override
    public void forEachColumn(LongRange range, LongTBiConsumer<RangedList<T>> columnAction) {
        //TODO filter support
        range = getRange().inner.intersection(range);
        for(long index = range.from; index < range.to; index++) {
            RangedList<T> cView = getColumnView(index);
            columnAction.accept(index, cView);
        }
    }

    @Override
    public void forEachRow(LongTBiConsumer<RangedList<T>> rowAction) {
        for(long index = getRange().outer.from; index < getRange().outer.to; index++) {
            RangedList<T> rView = getRowView(index);
            rowAction.accept(index, rView);
        }
    }
    @Override
    public void forEachRow(LongRange rRange, LongTBiConsumer<RangedList<T>> rowAction) {
        rRange = getRange().outer.intersection(rRange);
        for(long index = rRange.from; index < rRange.to; index++) {
            RangedList<T> rView = getRowView(index);
            rowAction.accept(index, rView);
        }
    }


    @Override
    public RangedList<RangedList<T>> asRowList() {
        // TODO view style implementation
        return new Chunk<>(range.outer, (Long rowIndex)->{
            return getRowView(rowIndex); // OK filter dependet
        });
    }
    @Override
    public RangedList<RangedList<T>> asColumnList() {
        // TODO view style implementation
        return new Chunk<>(range.inner, (Long columnIndex)->{
            return getColumnView(columnIndex);
        });
    }


    @Override
    public void forEachWithSiblings(SquareRange range, final Consumer<SquareSiblingAccessor<T>> action) {
        // TODO
        // rangeCheck(range);
        // TODO
        // IntStream.range(begin, end).forEach();
        long index1 = range.outer.from;
        long index2 = range.inner.from;
        long offset1 = index1 - getRange().outer.from;
        long offset2 = index2 - getRange().inner.from;
        //TODO overflow assert
        int offset = (int) (offset1 * innerSize + offset2);
        while (true) {
            // TODO overflow assert
            System.out.println("index:"+ index1 +","+index2 + ", offset" + offset1 + ","+offset2 + ","+offset);
            SquareSiblingAccessor<T> acc = new MySquareSiblingAccessor<>(offset, (int) innerSize, a);
            action.accept(acc);
            index2++;
            offset++;
            if(index2==range.inner.to) { // TODO filter dependent
                index2 = range.inner.from;
                index1++;
                if(index1==range.outer.to) return;
                offset1++;
                //TODO overflow assert
                offset = (int)(offset1 * innerSize + offset2);
            }
        }
    }

    @Override
    public void forEach(final SquareIndexTConsumer<? super T> action) {
        // TODO
        // rangeCheck(range);
        // TODO
        // IntStream.range(begin, end).forEach();
        long index = range.outer.from;
        long index2 = range.inner.from;
        int offset = 0;
        while (true) {
            T v = (T)a[offset];
            action.accept(index, index2, v);
            index2++;
            offset++;
            if(index2==range.inner.to) {
                index2 = range.inner.from;
                index++;
                if(index==range.outer.to) return;
            }
        }
    }

    @Override
    public void forEach(SquareRange range, final SquareIndexTConsumer<? super T> action) {
        // TODO
        // rangeCheck(range);
        // TODO
        // IntStream.range(begin, end).forEach();
        long index1 = range.outer.from;
        long index2 = range.inner.from;
        long offset1 = index1 - getRange().outer.from;
        long offset2 = index2 - getRange().inner.from;
        //TODO overflow assert
        int offset = (int) (offset1 * innerSize + offset2);
        while (true) {
            // TODO overflow assert
            System.out.println("index:"+ index1 +","+index2 + ", offset" + offset1 + ","+offset2 + ","+offset);
            action.accept(index1, index2, (T)a[offset]);
            index2++;
            offset++;
            if(index2==range.inner.to) { // TODO filter dependent
                index2 = range.inner.from;
                index1++;
                if(index1==range.outer.to) return;
                offset1++;
                offset = (int)(offset1 * innerSize + offset2);
            }
        }
    }

    @Override
    public void forEach(final Consumer<? super T> action) {
        // TODO
        // rangeCheck(range);
        // TODO
        // IntStream.range(begin, end).forEach();
        for (Object o: a) {
            action.accept((T)o); // TODO filter dependent
        }
    }

    @Override
    public void forEach(SquareRange range, final Consumer<? super T> action) {
        // TODO
        // rangeCheck(range);
        // TODO
        // IntStream.range(begin, end).forEach();
        long index1 = range.outer.from;
        long index2 = range.inner.from;
        long offset1 = index1 - getRange().outer.from;
        long offset2 = index2 - getRange().inner.from;
        //TODO overflow assert
        int offset = (int) (offset1 * innerSize + offset2);
        while (true) {
            // TODO overflow assert
            action.accept((T)a[offset]);
            index2++;
            offset++;
            if(index2==range.inner.to) {
                index2 = range.inner.from; // TODO filter dependent
                index1++;
                if(index1==range.outer.to) return;
                offset1++;
                offset = (int)(offset1 * innerSize + offset2);
            }
        }
    }

    public <S> void setupFrom(SquareChunk<S> source, Function<? super S, ? extends T> func) {
        // TODO 'source' should be SquareRangedList
        //    and this method also should be declared in SquareRangedList
        // TODO rangeCheck // TODO filter dependent
        final SquareIndexTConsumer<S> consumer = (long index1, long index2, S s) -> {
            final T r = func.apply(s);
            long offset1 = index1 - getRange().outer.from;
            long offset2 = index2 - getRange().inner.from;
            long offset = offset1 * innerSize + offset2;
            a[(int) offset ] = r;
        };
        source.forEach(consumer);
    }



    /**
     * {@inheritDoc}
     */
    @Override
    public T get(long index, long index2) { // TODO filter dependent
        if (!getRange().outer.contains(index)) {
            throw new IndexOutOfBoundsException(/*rangeMsg(index)*/ index + " is outof "+getRange().outer);
        }
        if (!getRange().inner.contains(index2)) {
            throw new IndexOutOfBoundsException(/*rangeMsg(index2)*/ index2  + " is outof " + getRange().inner);
        }
        return getUnsafe(index, index2);
    }

    @Override
    public SquareRange getRange() {
        return range;
    }




    /**
     * Returns the element located at the provided index. The provided index is
     * presumed valid and as such, no bound checking is done.
     *
     * @param index index whose value should be returned
     * @return the object stored at the provided index, possibly {@code null}
     */
    @SuppressWarnings("unchecked")
    final T getUnsafe(long index, long index2) { // when range check was done
        final long offset1 = index - range.outer.from;
        final long offset2 = index2 - range.inner.from;
        return (T) a[(int) (offset1 * innerSize + offset2)];
    }

//    /**
//     * {@inheritDoc}
//     */
//    @Override
//    public int hashCode() {
//        return RangedList.hashCode(this);
//    }


//    @Override
//    public void read(Kryo kryo, Input input) {
//
//    }
//
//    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
//
//    }

    /**
     * {@inheritDoc}
     */
    @Override
    public T set(long index, long index2, T value) {
        if (!getRange().outer.contains(index)) {
            throw new IndexOutOfBoundsException(/*rangeMsg(index)*/ index + " is outof "+getRange().outer);
        }
        if (!getRange().inner.contains(index2)) {
            throw new IndexOutOfBoundsException(/*rangeMsg(index2)*/ index2  + " is outof " + getRange().inner);
        }
        return setUnsafe(index, index2, value);
    }

    @Override
    public SquareRangedList<T> subView(SquareRange range) {
        return new SquareRangedListView<>(this, range);
    }

    @SuppressWarnings("unchecked")
    private T setUnsafe(long index, long index2, T v) { // when range check was done
        final long offset1 = index - range.outer.from;
        final long offset2 = index2 - range.inner.from;
        final long offset = offset1 * innerSize + offset2;
        final T prev = (T) a[(int) offset];
        a[(int) offset] = v;
        return prev;
    }

//    /**
//     * {@inheritDoc}
//     */
//    public Object[] toArray() {
//        return a;
//    }

//    @Override
//    public Object[] toArray(LongRange newRange) {
//    }

//    @Override
//    public SquareChunk<T> toChunk(LongRange newRange) {
//    }

//    @Override
//    public List<T> toList(LongRange r) {
//    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        final StringWriter out = new StringWriter();
        debugPrint("  ", new PrintWriter(out));
        return out.toString();
    }
    void debugPrint(String tag, PrintWriter out) {
        forEachRow((long index, RangedList<T> row)->{
            out.print("("+tag+ "), row:" + index +"->");
            row.forEach((long i, T e)->{
                out.print("["+i+":"+e+"]");
            });
            out.println();
        });
    }
    void debugPrint(String tag) {
        debugPrint(tag, new PrintWriter(System.out));
    }

//    @Override
//    public void write(Kryo kryo, Output output) {
//        kryo.writeClassAndObject(output, range);
//        kryo.writeClassAndObject(output, a);
//    }
//
//    private void writeObject(ObjectOutputStream out) throws IOException {
//        // System.out.println("writeChunk:"+this);
//        out.writeObject(range);
//        out.writeObject(a);
//    }


}