/*
 * @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.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import net.ranides.assira.annotations.Meta;
import net.ranides.assira.events.Event;
import net.ranides.assira.events.EventBinding;
import net.ranides.assira.events.EventListener;
import net.ranides.assira.events.EventReactor;
import net.ranides.assira.events.EventRouter;
import net.ranides.assira.events.ReflectEventListener;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class AsyncStream {
    
    private AsyncStream() {
        // utility class
    }
    
    public static OStream wrap(OutputStream stream) {
        return new OStream(stream);
    }
    
    public static IStream wrap(InputStream stream) {
        return new IStream(stream);
    }
    
    public static class IStream extends InputStream implements EventRouter {

        private final InputStream stream;
        private final EventReactor router;

        protected IStream(InputStream stream) {
            this.stream = stream;
            this.router = EventReactor.newInstance("AsyncStream.IStream.Router", 0, 0);
        }
        
        @Override
        public <T extends Event> void addEventListener(Class<T> event, EventListener<? super T> listener) {
            router.addEventListener(event, listener);
        }

        @Override
        public <T extends Event> void removeEventListener(Class<T> event, EventListener<? super T> listener) {
            router.removeEventListener(event, listener);
        }

        @Override
        public void removeAllEventListeners() {
            router.removeAllEventListeners();
        }

        @Override
        public Collection<EventBinding<?>> getEventListeners() {
            return router.getEventListeners();
        }

        @Override
        public int getEventListenersCount() {
            return router.getEventListenersCount();
        }

        @Override
        public boolean signalEvent(Event event) {
            return router.signalEvent(event);
        }

        @Override
        public void handleEvent(Event event) {
            router.handleEvent(event);
        }
        
        private boolean sendEvents() {
            return getEventListenersCount() > 0;
        }
        
        @Override
        public void dispose() {
            try {
                close();
                if(sendEvents()) {
                    router.signalEvent(IOEvent.close());
                }
            } catch (IOException ex) {
                router.signalEvent(IOEvent.failure(ex));
            } finally {
                router.dispose();
            }
        }

        @Override
        public int read() throws IOException {
            try {
                int ret = stream.read();
                if( sendEvents() ) {
                    router.signalEvent(IOEvent.read(ret));
                }
                return ret;
            } catch(IOException ex) {
                router.signalEvent(IOEvent.failure(ex));
                throw ex;
            }
        }

        @Override
        public boolean markSupported() {
            return stream.markSupported();
        }

        @Override
        public void reset() throws IOException {
            try {
                stream.reset();
                if( sendEvents() ) {
                    router.signalEvent(IOEvent.reset());
                }
            } catch(IOException ex) {
                router.signalEvent(IOEvent.failure(ex));
                throw ex;
            } 
        }

        @Override
        public void mark(int readlimit) {
            stream.mark(readlimit);
            if( sendEvents() ) {
                router.signalEvent(IOEvent.mark(readlimit));
            }
        }

        @Override
        public void close() throws IOException {
            try {
                stream.close();
                if( sendEvents() ) {
                    router.signalEvent(IOEvent.close());
                }
            } catch(IOException ex) {
                router.signalEvent(IOEvent.failure(ex));
                throw ex;
            } finally {
                router.dispose();
            }
        }

        @Override
        public int available() throws IOException {
            return stream.available();
        }

        @Override
        public long skip(long n) throws IOException {
            try {
                long ret = stream.skip(n);
                if( sendEvents() ) {
                    router.signalEvent(IOEvent.skip(ret));
                }
                return ret;
            } catch(IOException ex) {
                router.signalEvent(IOEvent.failure(ex));
                throw ex;
            }
        }

        @Override
        public int read(byte[] data, int off, int len) throws IOException {
            try {
                int ret = stream.read(data, off, len);
                if( sendEvents() ) {
                    router.signalEvent(IOEvent.read(data, off, ret));
                }
                return ret;
            } catch(IOException ex) {
                router.signalEvent(IOEvent.failure(ex));
                throw ex;
            }
        }

        @Override
        public int read(byte[] data) throws IOException {
            try {
                int ret = stream.read(data);
                if( sendEvents() ) {
                    router.signalEvent(IOEvent.read(data, 0, ret));
                }
                return ret;
            } catch(IOException ex) {
                router.signalEvent(IOEvent.failure(ex));
                throw ex;
            }
        }
        
        
        
    }
    
    public static class OStream extends OutputStream implements EventRouter {
        
        private final OutputStream stream;
        private final EventReactor worker;
        
        protected OStream(OutputStream ostream) {
            this.stream = ostream;
            this.worker = EventReactor.newInstance("AsyncStream.OStream.Router", 0, 0);
            this.worker.addEventListener(IORequest.class, new ASHandler(this));
        }

        @Override
        public void write(int data) throws IOException {
            worker.signalEvent(IORequest.write(data));
        }

        @Override
        public void write(byte[] data, int off, int len) throws IOException {
            try {
                worker.signalEvent(IORequest.write(data, off, len));
            } catch(Exception cause) {
                worker.signalEvent(IOEvent.failure(new IOException(cause)));
            }
        }

        @Override
        public void write(byte[] data) throws IOException {
            worker.signalEvent(IORequest.write(data));
        }

        @Override
        public void close() throws IOException {
            worker.signalEvent(IORequest.close());
        }

        @Override
        public void flush() throws IOException {
            worker.signalEvent(IORequest.flush());
        }

        @Override
        public <T extends Event> void addEventListener(Class<T> event, EventListener<? super T> listener) {
            worker.addEventListener(event, listener);
        }

        @Override
        public <T extends Event> void removeEventListener(Class<T> event, EventListener<? super T> listener) {
            worker.removeEventListener(event, listener);
        }

        @Override
        public void removeAllEventListeners() {
            worker.removeAllEventListeners();
        }

        @Override
        public Collection<EventBinding<?>> getEventListeners() {
            return worker.getEventListeners();
        }

        @Override
        public int getEventListenersCount() {
            return worker.getEventListenersCount();
        }

        @Override
        public boolean signalEvent(Event event) {
            return worker.signalEvent(event);
        }
        
        @Override
        public void handleEvent(Event event) {
            worker.handleEvent(event);
        }
        
        @Override
        public void dispose() {
            try {
                stream.close();
                if(sendEvents()) {
                    worker.signalEvent(IOEvent.close());
                }
            } catch (IOException ex) {
                worker.signalEvent(IOEvent.failure(ex));
            } finally {
                worker.dispose();
            }
        }

        private boolean sendEvents() {
            return worker.getEventListenersCount() > 1;
        }
        
    }
    
    private static class ASHandler extends ReflectEventListener<Event> {
        
        private final OStream that;

        public ASHandler(OStream that) {
            this.that = that;
        }

        @Meta.EventHandler
        void write(IORequest.WriteByte event) throws IOException {
            that.stream.write(event.data());
            if(that.sendEvents()) {
                that.worker.signalEvent(IOEvent.write(event.data()));
            }
        }
        
        @Meta.EventHandler
        void write(IORequest.WriteByteArray event) throws IOException {
            that.stream.write(event.data());
            if(that.sendEvents()) {
                that.worker.signalEvent(IOEvent.write(event.data()));
            }
        }
        
        @Meta.EventHandler
        void close(IORequest.Close event) throws IOException {
            that.stream.close();
            if(that.sendEvents()) {
                that.worker.signalEvent(IOEvent.close());
            }
            that.worker.dispose();
        }
        
        @Meta.EventHandler
        void flush(IORequest.Flush event) throws IOException {
            that.stream.flush();
            if(that.sendEvents()) {
                that.worker.signalEvent(IOEvent.flush());
            }
        }
        
        @Meta.ErrorHandler
        void handle(IOException cause) {
            if(that.sendEvents()) {
                that.worker.signalEvent(IOEvent.failure(cause));
            }
        }
        
    }
    
}
