package com.jsoniter.any;

import com.jsoniter.*;
import com.jsoniter.output.JsonStream;
import com.jsoniter.spi.JsonException;
import com.jsoniter.spi.TypeLiteral;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

class ArrayLazyAny extends LazyAny {

    private final static TypeLiteral<List<Any>> typeLiteral = new TypeLiteral<List<Any>>() {
    };
    private List<Any> cache;
    private int lastParsedPos;

    public ArrayLazyAny(byte[] data, int head, int tail) {
        super(data, head, tail);
        lastParsedPos = head;
    }

    @Override
    public ValueType valueType() {
        return ValueType.ARRAY;
    }

    @Override
    public Object object() {
        fillCache();
        return cache;
    }

    @Override
    public boolean toBoolean() {
        try {
            return CodegenAccess.readArrayStart(parse());
        } catch (IOException e) {
            throw new JsonException(e);
        }
    }

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

    @Override
    public long toLong() {
        return size();
    }

    @Override
    public float toFloat() {
        return size();
    }

    @Override
    public double toDouble() {
        return size();
    }

    @Override
    public int size() {
        fillCache();
        return cache.size();
    }

    @Override
    public Iterator<Any> iterator() {
        if (lastParsedPos == tail) {
            return cache.iterator();
        } else {
            return new LazyIterator();
        }
    }

    @Override
    public Any get(int index) {
        try {
            return fillCacheUntil(index);
        } catch (IndexOutOfBoundsException e) {
            return new NotFoundAny(index, object());
        }
    }

    @Override
    public Any get(Object[] keys, int idx) {
        if (idx == keys.length) {
            return this;
        }
        Object key = keys[idx];
        if (isWildcard(key)) {
            fillCache();
            ArrayList<Any> result = new ArrayList<Any>();
            for (Any element : cache) {
                Any mapped = element.get(keys, idx + 1);
                if (mapped.valueType() != ValueType.INVALID) {
                    result.add(mapped);
                }
            }
            return Any.rewrap(result);
        }
        try {
            return fillCacheUntil((Integer) key).get(keys, idx + 1);
        } catch (IndexOutOfBoundsException e) {
            return new NotFoundAny(keys, idx, object());
        } catch (ClassCastException e) {
            return new NotFoundAny(keys, idx, object());
        }
    }

    private void fillCache() {
        if (lastParsedPos == tail) {
            return;
        }
        if (cache == null) {
            cache = new ArrayList<Any>(4);
        }
        try {
            JsonIterator iter = JsonIterator.tlsIter.get();
            iter.reset(data, lastParsedPos, tail);
            if (lastParsedPos == head) {
                if (!CodegenAccess.readArrayStart(iter)) {
                    lastParsedPos = tail;
                    return;
                }
                cache.add(iter.readAny());
            }
            while (CodegenAccess.nextToken(iter) == ',') {
                cache.add(iter.readAny());
            }
            lastParsedPos = tail;
        } catch (IOException e) {
            throw new JsonException(e);
        }
    }

    private Any fillCacheUntil(int target) {
        if (lastParsedPos == tail) {
            return cache.get(target);
        }
        if (cache == null) {
            cache = new ArrayList<Any>(4);
        }
        int i = cache.size();
        if (target < i) {
            return cache.get(target);
        }
        try {
            JsonIterator iter = JsonIterator.tlsIter.get();
            iter.reset(data, lastParsedPos, tail);
            if (lastParsedPos == head) {
                if (!CodegenAccess.readArrayStart(iter)) {
                    lastParsedPos = tail;
                    throw new IndexOutOfBoundsException();
                }
                Any element = iter.readAny();
                cache.add(element);
                if (target == 0) {
                    lastParsedPos = CodegenAccess.head(iter);
                    return element;
                }
                i = 1;
            }
            while (CodegenAccess.nextToken(iter) == ',') {
                Any element = iter.readAny();
                cache.add(element);
                if (i++ == target) {
                    lastParsedPos = CodegenAccess.head(iter);
                    return element;
                }
            }
            lastParsedPos = tail;
        } catch (IOException e) {
            throw new JsonException(e);
        }
        throw new IndexOutOfBoundsException();
    }

    private class LazyIterator implements Iterator<Any> {

        private Any next;
        private int index;

        public LazyIterator() {
            index = 0;
            next = fillCacheUntil(index);
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean hasNext() {
            return next != null;
        }

        @Override
        public Any next() {
            if (next == null) {
                throw new IndexOutOfBoundsException();
            }
            Any current = next;
            try {
                index++;
                next = fillCacheUntil(index);
            } catch (IndexOutOfBoundsException e){
                next = null;
            }
            return current;
        }
    }

    @Override
    public void writeTo(JsonStream stream) throws IOException {
        if (lastParsedPos == head) {
            super.writeTo(stream);
        } else {
            // there might be modification
            fillCache();
            stream.writeVal(typeLiteral, cache);
        }
    }

    @Override
    public String toString() {
        if (lastParsedPos == head) {
            return super.toString();
        } else {
            fillCache();
            return JsonStream.serialize(cache);
        }
    }
}
