/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.io;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import net.ranides.assira.events.Event;
import net.ranides.assira.text.StringEncoder;

/**
 *
 * @author ranides
 */
@SuppressWarnings("PMD")
public abstract class IOEvent implements Event, Serializable {

    private static final long serialVersionUID = 1L;

    private static final class Li { // NOPMD lazy init idiom
        static final Close CLOSE = new Close();
        static final Flush FLUSH = new Flush();
        static final Open OPEN = new Open();
        static final Reset RESET = new Reset();
    }

    public static Close close() {
        return Li.CLOSE;
    }

    public static Flush flush() {
        return Li.FLUSH;
    }

    public static Open open() {
        return Li.OPEN;
    }
    
    public static Skip skip(long delta) {
        return new Skip(delta);
    }
    
    public static Reset reset() {
        return Li.RESET;
    }
    
    public static Mark mark(int limit) {
        return new Mark(limit);
    }

    public static Failure failure(IOException cause) {
        return new Failure(cause);
    }

    public static ReadByte read(int value) {
        return new ReadByte(value);
    }

    public static ReadByteArray read(byte[] array) {
        return new ReadByteArray(Arrays.copyOf(array, array.length));
    }

    public static ReadByteArray read(byte[] array, int offset, int length) {
        return new ReadByteArray(Arrays.copyOfRange(array, offset, offset + length));
    }

    public static WriteByte write(int value) {
        return new WriteByte(value);
    }

    public static WriteByteArray write(byte[] array) {
        return new WriteByteArray(Arrays.copyOf(array, array.length));
    }

    public static WriteByteArray write(byte[] array, int offset, int length) {
        return new WriteByteArray(Arrays.copyOfRange(array, offset, offset + length));
    }

    public static LocalProgress progress(File file, long current, long max) {
        return new LocalProgress(file, current, max);
    }

    public static class Failure extends IOEvent {

        private static final long serialVersionUID = 1L;
        
        private final IOException cause;

        protected Failure(IOException cause) {
            this.cause = cause;
        }
        
        public IOException cause() {
            return cause;
        }
        
        @Override
        public String toString() {
            return "IOEvent.Failure: " + cause;
        }
    }

    public static class Close extends IOEvent {

        private static final long serialVersionUID = 1L;

        protected Close() {
            // do nothing
        }
        
        @Override
        public String toString() {
            return "IOEvent.Close";
        }
    }

    public static class Flush extends IOEvent {

        private static final long serialVersionUID = 1L;

        protected Flush() {
            // do nothing
        }
        
        @Override
        public String toString() {
            return "IOEvent.Flush";
        }
    }
    
    public static class Skip extends IOEvent {

        private static final long serialVersionUID = 1L;
        
        private final long delta;

        protected Skip(long delta) {
            this.delta = delta;
        }

        public long delta() {
            return delta;
        }
        
        @Override
        public String toString() {
            return "IOEvent.Skip";
        }
    }
    
    public static class Reset extends IOEvent {

        private static final long serialVersionUID = 1L;
        
        protected Reset() {
            // do nothing
        }

        @Override
        public String toString() {
            return "IOEvent.Reset";
        }
    }
    
    public static class Mark extends IOEvent {

        private static final long serialVersionUID = 1L;
        
        private final int limit;

        protected Mark(int limit) {
            this.limit = limit;
        }

        public int limit() {
            return limit;
        }
        
        @Override
        public String toString() {
            return "IOEvent.Mark";
        }
    }

    public static class Open extends IOEvent {

        private static final long serialVersionUID = 1L;

        protected Open() {
            // do nothing
        }
        
        @Override
        public String toString() {
            return "IOEvent.Open";
        }
    }

    public static abstract class Read extends IOEvent {

        private static final long serialVersionUID = 1L;

        protected Read() {
            // do nothing
        }

        public abstract int head();

        public abstract byte[] array();

        @Override
        public String toString() {
            return "IOEvent.Read";
        }
    }

    public static class ReadByte extends Read {

        private static final long serialVersionUID = 1L;
        
        private final int data;

        protected ReadByte(int data) {
            this.data = data;
        }

        @Override
        public final int head() {
            return data;
        }

        @Override
        public final byte[] array() {
            return new byte[]{(byte) data};
        }

        @Override
        public String toString() {
            return "IOEvent.ReadByte: " + data;
        }
    }

    @SuppressWarnings({"EI_EXPOSE_REP", "PMD"})
    public static class ReadByteArray extends Read {

        private static final long serialVersionUID = 1L;
        
        private final byte[] data;

        protected ReadByteArray(byte[] data) {
            this.data = data;
        }

        @Override
        public final int head() {
            return data[0];
        }

        @Override
        public final byte[] array() {
            return data;
        }

        @Override
        public String toString() {
            return "IOEvent.ReadByteArray: " + data.length + ":" + StringEncoder.bytesToHex(data," ");
        }
    }

    public static abstract class Write extends IOEvent {

        private static final long serialVersionUID = 1L;

        protected Write() {
            // do nothing
        }
        
        public abstract int head();

        public abstract byte[] array();

        @Override
        public String toString() {
            return "IOEvent.Write";
        }
    }

    public static class WriteByte extends Write {

        private static final long serialVersionUID = 1L;
        
        private final int data;

        protected WriteByte(int data) {
            this.data = data;
        }

        @Override
        public final int head() {
            return data;
        }

        @Override
        public final byte[] array() {
            return new byte[]{ (byte)data };
        }

        @Override
        public String toString() {
            return "IOEvent.WriteByte: " + data;
        }
    }

    public static class WriteByteArray extends Write {

        private static final long serialVersionUID = 1L;
        
        private final byte[] data;

        protected WriteByteArray(byte[] data) {
            this.data = data;
        }

        @Override
        public final int head() {
            return data[0];
        }

        @Override
        public final byte[] array() {
            return data;
        }

        @Override
        public String toString() {
            return "IOEvent.WriteByteArray: " + data.length;
        }
    }

    public static class Local extends IOEvent {

        private static final long serialVersionUID = 1L;
        
        private final File file;

        protected Local(File file) {
            this.file = file;
        }

        public File file() {
            return file;
        }

        @Override
        public String toString() {
            return "IOEvent.Local: " + file;
        }
    }

    public static class LocalProgress extends Local {

        private static final long serialVersionUID = 1L;
        
        private final long current;
        private final long max;

        protected LocalProgress(File file, long current, long max) {
            super(file);
            this.current = current;
            this.max = max;
        }

        public long current() {
            return current;
        }

        public long max() {
            return max;
        }

        @Override
        public String toString() {
            return "IOEvent.LocalProgress: [" + current + " / " + max + "] " + file();
        }
    }
}
