package org.drools.decisiontable;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;

import org.drools.builder.DecisionTableConfiguration;
import org.drools.compiler.DecisionTableProvider;
import org.drools.util.StringUtils;

public class DecisionTableProviderImpl
    implements
    DecisionTableProvider {

    public String loadFromInputStream(InputStream is,
                                      DecisionTableConfiguration configuration) {

        return compileStream( is,
                              configuration );
    }
    
    private String compileStream(InputStream is,
                                 DecisionTableConfiguration configuration) {
        SpreadsheetCompiler compiler = new SpreadsheetCompiler();
        switch ( configuration.getInputType() ) {
            case XLS : {
                if ( StringUtils.isEmpty( configuration.getWorksheetName() ) ) {
                    return compiler.compile( is,
                                             InputType.XLS );
                } else {
                    return compiler.compile( is,
                                             configuration.getWorksheetName() );
                }
            }
            case CSV : {
                return compiler.compile( is,
                                         InputType.CSV );
            }
        }

        return null;
    }

    /**
     * Adapts a <code>Reader</code> as an <code>InputStream</code>.
     * Adapted from <CODE>StringInputStream</CODE>.
     *
     */
    public static class ReaderInputStream extends InputStream {

        /** Source Reader */
        private Reader in;

        private String encoding = System.getProperty( "file.encoding" );

        private byte[] slack;

        private int    begin;

        /**
         * Construct a <CODE>ReaderInputStream</CODE>
         * for the specified <CODE>Reader</CODE>.
         *
         * @param reader   <CODE>Reader</CODE>.  Must not be <code>null</code>.
         */
        public ReaderInputStream(Reader reader) {
            in = reader;
        }

        /**
         * Construct a <CODE>ReaderInputStream</CODE>
         * for the specified <CODE>Reader</CODE>,
         * with the specified encoding.
         *
         * @param reader     non-null <CODE>Reader</CODE>.
         * @param encoding   non-null <CODE>String</CODE> encoding.
         */
        public ReaderInputStream(Reader reader,
                                 String encoding) {
            this( reader );
            if ( encoding == null ) {
                throw new IllegalArgumentException( "encoding must not be null" );
            } else {
                this.encoding = encoding;
            }
        }

        /**
         * Reads from the <CODE>Reader</CODE>, returning the same value.
         *
         * @return the value of the next character in the <CODE>Reader</CODE>.
         *
         * @exception IOException if the original <code>Reader</code> fails to be read
         */
        public synchronized int read() throws IOException {
            if ( in == null ) {
                throw new IOException( "Stream Closed" );
            }

            byte result;
            if ( slack != null && begin < slack.length ) {
                result = slack[begin];
                if ( ++begin == slack.length ) {
                    slack = null;
                }
            } else {
                byte[] buf = new byte[1];
                if ( read( buf,
                           0,
                           1 ) <= 0 ) {
                    result = -1;
                }
                result = buf[0];
            }

            if ( result < -1 ) {
                result += 256;
            }

            return result;
        }

        /**
         * Reads from the <code>Reader</code> into a byte array
         *
         * @param b  the byte array to read into
         * @param off the offset in the byte array
         * @param len the length in the byte array to fill
         * @return the actual number read into the byte array, -1 at
         *         the end of the stream
         * @exception IOException if an error occurs
         */
        public synchronized int read(byte[] b,
                                     int off,
                                     int len) throws IOException {
            if ( in == null ) {
                throw new IOException( "Stream Closed" );
            }

            while ( slack == null ) {
                char[] buf = new char[len]; // might read too much
                int n = in.read( buf );
                if ( n == -1 ) {
                    return -1;
                }
                if ( n > 0 ) {
                    slack = new String( buf,
                                        0,
                                        n ).getBytes( encoding );
                    begin = 0;
                }
            }

            if ( len > slack.length - begin ) {
                len = slack.length - begin;
            }

            System.arraycopy( slack,
                              begin,
                              b,
                              off,
                              len );

            if ( (begin += len) >= slack.length ) {
                slack = null;
            }

            return len;
        }

        /**
         * Marks the read limit of the StringReader.
         *
         * @param limit the maximum limit of bytes that can be read before the
         *              mark position becomes invalid
         */
        public synchronized void mark(final int limit) {
            try {
                in.mark( limit );
            } catch ( IOException ioe ) {
                throw new RuntimeException( ioe.getMessage() );
            }
        }

        /**
         * @return   the current number of bytes ready for reading
         * @exception IOException if an error occurs
         */
        public synchronized int available() throws IOException {
            if ( in == null ) {
                throw new IOException( "Stream Closed" );
            }
            if ( slack != null ) {
                return slack.length - begin;
            }
            if ( in.ready() ) {
                return 1;
            } else {
                return 0;
            }
        }

        /**
         * @return false - mark is not supported
         */
        public boolean markSupported() {
            return false; // would be imprecise
        }

        /**
         * Resets the StringReader.
         *
         * @exception IOException if the StringReader fails to be reset
         */
        public synchronized void reset() throws IOException {
            if ( in == null ) {
                throw new IOException( "Stream Closed" );
            }
            slack = null;
            in.reset();
        }

        /**
         * Closes the Stringreader.
         *
         * @exception IOException if the original StringReader fails to be closed
         */
        public synchronized void close() throws IOException {
            if ( in != null ) {
                in.close();
                slack = null;
                in = null;
            }
        }
    }

}
