package org.xbib.datastructures.json.noggit;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.CharBuffer;

// CharArr origins

// Java5 version could look like the following:
// public class CharArr implements CharSequence, Appendable, Readable, Closeable {

public class CharArr implements CharSequence, Appendable {
    protected char[] buf;
    protected int start;
    protected int end;

    public CharArr() {
        this(32);
    }

    public CharArr(int size) {
        buf = new char[size];
    }

    public CharArr(char[] arr, int start, int end) {
        set(arr, start, end);
    }

    public void setStart(int start) {
        this.start = start;
    }

    public void setEnd(int end) {
        this.end = end;
    }

    public void set(char[] arr, int start, int end) {
        this.buf = arr;
        this.start = start;
        this.end = end;
    }

    public char[] getArray() {
        return buf;
    }

    public int getStart() {
        return start;
    }

    public int getEnd() {
        return end;
    }

    public int size() {
        return end - start;
    }

    @Override
    public int length() {
        return size();
    }

    /**
     * The capacity of the buffer when empty (getArray().size())
     */
    public int capacity() {
        return buf.length;
    }


    @Override
    public char charAt(int index) {
        return buf[start + index];
    }

    @Override
    public CharArr subSequence(int start, int end) {
        return new CharArr(buf, this.start + start, this.start + end);
    }

    public int read() throws IOException {
      if (start >= end) {
        return -1;
      }
        return buf[start++];
    }

    public int read(char[] cbuf, int off, int len) {
        //TODO
        return 0;
    }

    public void unsafeWrite(char b) {
        buf[end++] = b;
    }

    public void unsafeWrite(int b) {
        unsafeWrite((char) b);
    }

    public void unsafeWrite(char[] b, int off, int len) {
        System.arraycopy(b, off, buf, end, len);
        end += len;
    }

    protected void resize(int len) {
        char[] newbuf = new char[Math.max(buf.length << 1, len)];
        System.arraycopy(buf, start, newbuf, 0, size());
        buf = newbuf;
    }

    public void reserve(int num) {
      if (end + num > buf.length) {
        resize(end + num);
      }
    }

    public void write(char b) {
        if (end >= buf.length) {
            resize(end + 1);
        }
        unsafeWrite(b);
    }

    public final void write(int b) {
        write((char) b);
    }

    public final void write(char[] b) {
        write(b, 0, b.length);
    }

    public void write(char[] b, int off, int len) {
        reserve(len);
        unsafeWrite(b, off, len);
    }

    public final void write(CharArr arr) {
        write(arr.buf, arr.start, arr.end - arr.start);
    }

    public final void write(String s) {
        write(s, 0, s.length());
    }

    public void write(String s, int stringOffset, int len) {
        reserve(len);
        s.getChars(stringOffset, len, buf, end);
        end += len;
    }

    public void flush() {
    }

    public final void reset() {
        start = end = 0;
    }

    public void close() {
    }

    public char[] toCharArray() {
        char[] newbuf = new char[size()];
        System.arraycopy(buf, start, newbuf, 0, size());
        return newbuf;
    }


    @Override
    public String toString() {
        return new String(buf, start, size());
    }

    public int read(CharBuffer cb) throws IOException {

        /***
         int sz = size();
         if (sz<=0) return -1;
         if (sz>0) cb.put(buf, start, sz);
         return -1;
         ***/

        int sz = size();
      if (sz > 0) {
        cb.put(buf, start, sz);
      }
        start = end;
        while (true) {
            fill();
            int s = size();
          if (s == 0) {
            return sz == 0 ? -1 : sz;
          }
            sz += s;
            cb.put(buf, start, s);
        }
    }


    public int fill() throws IOException {
        return 0;  // or -1?
    }

    //////////////// Appendable methods /////////////
    @Override
    public final Appendable append(CharSequence csq) throws IOException {
        return append(csq, 0, csq.length());
    }

    @Override
    public Appendable append(CharSequence csq, int start, int end) throws IOException {
        write(csq.subSequence(start, end).toString());
        return null;
    }

    @Override
    public final Appendable append(char c) throws IOException {
        write(c);
        return this;
    }
}


class NullCharArr extends CharArr {
    public NullCharArr() {
        super(new char[1], 0, 0);
    }

    @Override
    public void unsafeWrite(char b) {
    }

    @Override
    public void unsafeWrite(char[] b, int off, int len) {
    }

    @Override
    public void unsafeWrite(int b) {
    }

    @Override
    public void write(char b) {
    }

    @Override
    public void write(char[] b, int off, int len) {
    }

    @Override
    public void reserve(int num) {
    }

    @Override
    protected void resize(int len) {
    }

    @Override
    public Appendable append(CharSequence csq, int start, int end) throws IOException {
        return this;
    }

    @Override
    public char charAt(int index) {
        return 0;
    }

    @Override
    public void write(String s, int stringOffset, int len) {
    }
}


// IDEA: a subclass that refills the array from a reader?
class CharArrReader extends CharArr {
    protected final Reader in;

    public CharArrReader(Reader in, int size) {
        super(size);
        this.in = in;
    }

    @Override
    public int read() throws IOException {
      if (start >= end) {
        fill();
      }
        return start >= end ? -1 : buf[start++];
    }

    @Override
    public int read(CharBuffer cb) throws IOException {
        // empty the buffer and then read direct
        int sz = size();
      if (sz > 0) {
        cb.put(buf, start, end);
      }
        int sz2 = in.read(cb);
      if (sz2 >= 0) {
        return sz + sz2;
      }
        return sz > 0 ? sz : -1;
    }

    @Override
    public int fill() throws IOException {
        if (start >= end) {
            reset();
        } else if (start > 0) {
            System.arraycopy(buf, start, buf, 0, size());
            end = size();
            start = 0;
        }
        /***
         // fill fully or not???
         do {
         int sz = in.read(buf,end,buf.length-end);
         if (sz==-1) return;
         end+=sz;
         } while (end < buf.length);
         ***/

        int sz = in.read(buf, end, buf.length - end);
      if (sz > 0) {
        end += sz;
      }
        return sz;
    }

}


class CharArrWriter extends CharArr {
    protected Writer sink;

    @Override
    public void flush() {
        try {
            sink.write(buf, start, end - start);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        start = end = 0;
    }

    @Override
    public void write(char b) {
        if (end >= buf.length) {
            flush();
        }
        unsafeWrite(b);
    }

    @Override
    public void write(char[] b, int off, int len) {
        int space = buf.length - end;
        if (len < space) {
            unsafeWrite(b, off, len);
        } else if (len < buf.length) {
            unsafeWrite(b, off, space);
            flush();
            unsafeWrite(b, off + space, len - space);
        } else {
            flush();
            try {
                sink.write(b, off, len);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    @Override
    public void write(String s, int stringOffset, int len) {
        int space = buf.length - end;
        if (len < space) {
            s.getChars(stringOffset, stringOffset + len, buf, end);
            end += len;
        } else if (len < buf.length) {
            // if the data to write is small enough, buffer it.
            s.getChars(stringOffset, stringOffset + space, buf, end);
            flush();
            s.getChars(stringOffset + space, stringOffset + len, buf, 0);
            end = len - space;
        } else {
            flush();
            // don't buffer, just write to sink
            try {
                sink.write(s, stringOffset, len);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }
    }
}

