/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */

package net.ranides.assira.io;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import net.ranides.assira.events.Event;
import net.ranides.assira.events.Events;

/**
 * I/O events, signalled by other assira classes.
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public interface IOEvent extends Event {

    /**
     * Event: stream was flushed.
     *
     * @param source source
     * @return event
     */
    static Flush flush(OutputStream source) {
        return new Flush(source);
    }

    /**
     * Event: stream was resetted.
     *
     * @param source source
     * @return event
     */
    static Reset reset(InputStream source) {
        return new Reset(source);
    }

    /**
     * Event: one byte was read from stream.
     *
     * @param source source
     * @param data byte
     * @return event
     */
    static Read read(InputStream source, int data) {
        if(data == -1) {
            return new Read(source);
        } else {
            return new Read(source, new byte[]{(byte)data}, 0, 1);
        }
    }

    /**
     * Event: bytes were read from stream.
     * Please note: only bytes limited by offset and length are meaningfull.
     *
     * @param source source
     * @param data data
     * @param offset offset
     * @param length length
     * @return event
     */
    static Read read(InputStream source, byte[] data, int offset, int length) {
        return new Read(source, data, offset, length);
    }

    /**
     * Event: bytes were read from stream.
     *
     * @param source source
     * @param data data
     * @return event
     */
    static Read read(InputStream source, byte[] data) {
        return new Read(source, data, 0, data.length);
    }

    /**
     * Event: one byte was written to stream.
     *
     * @param source source
     * @param data data
     * @return event
     */
    static Write write(OutputStream source, int data) {
        return new Write(source, new byte[]{(byte)data}, 0, 1);
    }

    /**
     * Event: bytes were written to stream.
     * Please note: only bytes limited by offset and length are meaningfull.
     *
     * @param source source
     * @param data data
     * @param offset offset
     * @param length length
     * @return event
     */
    static Write write(OutputStream source, byte[] data, int offset, int length) {
        return new Write(source, data, offset, length);
    }

    /**
     * Event: bytes were written to stream.
     *
     * @param source source
     * @param data data
     * @return event
     */
    static Write write(OutputStream source, byte[] data) {
        return new Write(source, data, 0, data.length);
    }

    /**
     * Event: stream was closed
     *
     * @param source source
     * @return event
     */
    static Close close(Closeable source) {
        return new Close(source);
    }

    /**
     * Event: some bytes were skipped.
     *
     * @param source source
     * @param count count
     * @return event
     */
    static Skip skip(InputStream source, long count) {
        return new Skip(source, count);
    }

    /**
     * Event: position of stream was changed.
     *
     * @param source source
     * @param position position
     * @return event
     */
    static Seek seek(InputStream source, long position) {
        return new Seek(source, position);
    }

    /**
     * Event: there was I/O exception
     *
     * @param cause cause
     * @return event
     */
    static Failure failure(IOException cause) {
        return new Failure(cause);
    }

    /**
     * Event: reset
     */
    class Reset implements IOEvent {
        
        private final InputStream source;

        protected Reset(InputStream source) {
            this.source = source;
        }

        /**
         * Source stream
         *
         * @return InputStream
         */
        public InputStream source() {
            return source;
        }
    }

    /**
     * Event: flush
     */
    class Flush implements IOEvent {
        
        private final OutputStream source;

        protected Flush(OutputStream source) {
            this.source = source;
        }

        /**
         * Source stream
         *
         * @return OutputStream
         */
        public OutputStream source() {
            return source;
        }
    }

    /**
     * Event: read bytes
     */
    class Read implements IOEvent {
        
        private static final ByteBuffer NONE = ByteBuffer.wrap(new byte[0]);
        
        private final InputStream source;
        private final ByteBuffer data;
        
        protected Read(InputStream source) {
            this.source = source;
            this.data = NONE;
        }
        
        protected Read(InputStream source, byte[] data, int offset, int length) {
            this.source = source;
            this.data = ByteBuffer.wrap(data, offset, length);
        }

        /**
         * Source stream
         *
         * @return InputStream
         */
        public InputStream source() {
            return source;
        }

        /**
         * Bytes read from stream
         *
         * @return ByteBuffer
         */
        public ByteBuffer data() {
            return data;
        }
        
    }

    /**
     * Event: write
     */
    class Write implements IOEvent {
        
        private final OutputStream source;
        private final ByteBuffer data;
        
        protected Write(OutputStream source, byte[] data, int offset, int length) {
            this.source = source;
            this.data = ByteBuffer.wrap(data, offset, length);
        }

        /**
         * Target stream
         *
         * @return OutputStream
         */
        public OutputStream source() {
            return source;
        }

        /**
         * Bytes written to stream
         *
         * @return ByteBuffer
         */
        public ByteBuffer data() {
            return data;
        }
        
    }

    /**
     * Event: close
     */
    class Close implements IOEvent {
        
        private final Closeable source;

        protected Close(Closeable source) {
            this.source = source;
        }

        /**
         * Closed stream
         *
         * @return Closeable
         */
        public Closeable source() {
            return source;
        }
        
    }

    /**
     * Event: skip
     */
    class Skip implements IOEvent {
        
        private final InputStream source;
        private final long count;

        protected Skip(InputStream source, long count) {
            this.source = source;
            this.count = count;
        }

        /**
         * Source stream
         *
         * @return InputStream
         */
        public InputStream source() {
            return source;
        }

        /**
         * Number of skipped bytes
         *
         * @return long
         */
        public long count() {
            return count;
        }
        
    }

    /**
     * Event: seek
     */
    class Seek implements IOEvent {
        
        private final InputStream source;
        private final long position;

        protected Seek(InputStream source, long position) {
            this.source = source;
            this.position = position;
        }

        /**
         * Source stream
         *
         * @return InputStream
         */
        public InputStream source() {
            return source;
        }

        /**
         * New position in stream
         *
         * @return long
         */
        public long position() {
            return position;
        }
        
    }

    /**
     * Event: I/O exception
     */
    class Failure extends Events.Failure implements IOEvent {
		
        protected Failure(Throwable cause) {
            super(cause);
        }
        
        @Override
        public IOException cause() {
            return (IOException)super.cause();
        }
        
    }
}
