// (c) 2012 B Smith-Mannschott -- Distributed under the Eclipse Public License
package bpsm.edn.parser;

import static bpsm.edn.parser.Parser.Config.BIG_DECIMAL_TAG;
import static bpsm.edn.parser.Parser.Config.BIG_INTEGER_TAG;
import static bpsm.edn.parser.Parser.Config.DOUBLE_TAG;
import static bpsm.edn.parser.Parser.Config.EDN_INSTANT;
import static bpsm.edn.parser.Parser.Config.EDN_UUID;
import static bpsm.edn.parser.Parser.Config.LONG_TAG;

import java.io.Closeable;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.CharBuffer;
import java.util.HashMap;
import java.util.Map;

import bpsm.edn.Tag;
import bpsm.edn.parser.CollectionBuilder.Factory;
import bpsm.edn.parser.Parser.Config;
import bpsm.edn.parser.Parser.Config.Builder;
import bpsm.edn.parser.inst.InstantToDate;

public class Parsers {

    static final CollectionBuilder.Factory DEFAULT_LIST_FACTORY = new DefaultListFactory();

    static final CollectionBuilder.Factory DEFAULT_VECTOR_FACTORY = new DefaultVectorFactory();

    static final CollectionBuilder.Factory DEFAULT_SET_FACTORY = new DefaultSetFactory();

    static final CollectionBuilder.Factory DEFAULT_MAP_FACTORY = new DefaultMapFactory();

    static final TagHandler INSTANT_TO_DATE = new InstantToDate();

    static final TagHandler UUID_HANDLER = new UuidHandler();

    static final TagHandler IDENTITY = new TagHandler() {
        public Object transform(Tag tag, Object value) {
            return value;
        }
    };

    public static Parser newParser(Parser.Config cfg) {
        return new ParserImpl(cfg, new Scanner(cfg));
    }

    static final int BUFFER_SIZE = 4096;

    static boolean readIntoBuffer(CharBuffer b, Readable r) throws IOException {
        b.clear();
        int n = r.read(b);
        b.flip();
        return n > 0;
    }

    static CharBuffer emptyBuffer() {
        CharBuffer b = CharBuffer.allocate(BUFFER_SIZE);
        b.limit(0);
        return b;
    }

    public static Parseable newParseable(final CharSequence cs) {
        return new Parseable() {
            int i = 0;

            public void close() throws IOException {
            }

            public int read() throws IOException {
                try {
                    return cs.charAt(i++);
                } catch (IndexOutOfBoundsException _) {
                    return -1;
                }
            }

            public void unread() throws IOException {
                i--;
            }
        };
    }

    public static Parseable newParseable(final Readable r) {
        return new Parseable() {
            CharBuffer buff = emptyBuffer();
            int curr = Integer.MIN_VALUE;
            boolean unread = false;
            boolean end = false;
            boolean closed = false;

            public void close() throws IOException {
                closed = true;
                if (r instanceof Closeable) {
                    ((Closeable) r).close();
                }
            }

            public int read() throws IOException {
                if (closed) {
                    throw new IOException("Can not read from closed Parseable");
                }
                if (unread) {
                    unread = false;
                    return curr;
                }
                if (end) {
                    return curr = -1;
                }
                try {
                    return curr = buff.get();
                } catch (BufferUnderflowException _) {
                    if (readIntoBuffer(buff, r)) {
                        return curr = buff.get();
                    } else {
                        end = true;
                        return curr = -1;
                    }
                }
            }

            public void unread() throws IOException {
                if (unread) {
                    throw new IOException("Can't unread after unread.");
                }
                if (curr == Integer.MIN_VALUE) {
                    throw new IOException("Can't unread before read.");
                }
                unread = true;
            }
        };
    }

    public static Builder newParserConfigBuilder() {
        return new Builder() {
            boolean used = false;
            CollectionBuilder.Factory listFactory = DEFAULT_LIST_FACTORY;
            CollectionBuilder.Factory vectorFactory = DEFAULT_VECTOR_FACTORY;
            CollectionBuilder.Factory setFactory = DEFAULT_SET_FACTORY;
            CollectionBuilder.Factory mapFactory = DEFAULT_MAP_FACTORY;
            Map<Tag, TagHandler> tagHandlers = defaultTagHandlers();

            public Builder setListFactory(CollectionBuilder.Factory listFactory) {
                checkState();
                this.listFactory = listFactory;
                return this;
            }

            public Builder setVectorFactory(CollectionBuilder.Factory vectorFactory) {
                checkState();
                this.vectorFactory = vectorFactory;
                return this;
            }

            public Builder setSetFactory(CollectionBuilder.Factory setFactory) {
                checkState();
                this.setFactory = setFactory;
                return this;
            }

            public Builder setMapFactory(CollectionBuilder.Factory mapFactory) {
                checkState();
                this.mapFactory = mapFactory;
                return this;
            }

            public Builder putTagHandler(Tag tag, TagHandler handler) {
                checkState();
                this.tagHandlers.put(tag, handler);
                return this;
            }

            public Config build() {
                checkState();
                used = true;
                return new Config() {
                    public Factory getListFactory() {
                        return listFactory;
                    }

                    public Factory getVectorFactory() {
                        return vectorFactory;
                    }

                    public Factory getSetFactory() {
                        return setFactory;
                    }

                    public Factory getMapFactory() {
                        return mapFactory;
                    }

                    public TagHandler getTagHandler(Tag tag) {
                        return tagHandlers.get(tag);
                    }
                };
            }

            private void checkState() {
                if (used) {
                    throw new IllegalStateException("Builder is single-use. Not usable after build()");
                }
            }
        };
    }

    static Map<Tag, TagHandler> defaultTagHandlers() {
        Map<Tag, TagHandler> m = new HashMap<Tag, TagHandler>();
        m.put(EDN_UUID, UUID_HANDLER);
        m.put(EDN_INSTANT, INSTANT_TO_DATE);
        m.put(BIG_DECIMAL_TAG, IDENTITY);
        m.put(DOUBLE_TAG, IDENTITY);
        m.put(BIG_INTEGER_TAG, IDENTITY);
        m.put(LONG_TAG, IDENTITY);
        return m;
    }

    public static Config defaultConfiguration() {
        return DEFAULT_CONFIGURATION;
    }

    static Config DEFAULT_CONFIGURATION = newParserConfigBuilder().build();

}

@@ -3390,35 +3390,67 @@
                 
-try
+if (buff.position() %3C buff.limit())
  %7B%0A             
@@ -3503,49 +3503,9 @@
    %7D
- catch (BufferUnderflowException _) %7B%0A   
+%0A
     
@@ -3512,25 +3512,24 @@
             
- 
 if (readInto
@@ -3543,28 +3543,24 @@
 buff, r)) %7B%0A
-    
             
@@ -3609,20 +3609,16 @@
         
-    
 %7D else %7B
@@ -3638,20 +3638,16 @@
         
- 
-   
 end = tr
@@ -3642,36 +3642,32 @@
     end = true;%0A
-    
                 
@@ -3688,38 +3688,16 @@
 r = -1;%0A
-                    %7D%0A
         

save time with check on position and limit rather than try/catch