/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.johnzon.core;


import java.util.AbstractMap;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonValue;

/**
 * Base parser which handles higher level operations which are
 * mixtures of Reader and Parsers like {@code getObject(), getValue(), getArray()}
 */
public abstract class JohnzonJsonParserImpl implements JohnzonJsonParser {

    /**
     * @return {@code true} if we are currently inside an array
     */
    protected abstract boolean isInArray();
    /**
     * @return {@code true} if we are currently inside an object
     */
    protected abstract boolean isInObject();

    protected abstract BufferStrategy.BufferProvider<char[]> getCharArrayProvider();

    private boolean manualNext = false;

    @Override
    public Event next() {
        manualNext = true;
        return internalNext();
    }

    protected abstract Event internalNext();

    @Override
    public JsonObject getObject() {
        Event current = current();
        if (current != Event.START_OBJECT) {
            throw new IllegalStateException(current + " doesn't support getObject()");
        }

        JsonReaderImpl jsonReader = new JsonReaderImpl(this, true, getCharArrayProvider(), RejectDuplicateKeysMode.DEFAULT);
        return jsonReader.readObject();
    }


    @Override
    public JsonArray getArray() {
        Event current = current();
        if (current != Event.START_ARRAY) {
            throw new IllegalStateException(current + " doesn't support getArray()");
        }

        JsonReaderImpl jsonReader = new JsonReaderImpl(this, true, getCharArrayProvider(), RejectDuplicateKeysMode.DEFAULT);
        return jsonReader.readArray();
    }

    @Override
    public JsonValue getValue() {
        Event current = current();
        switch (current) {
            case START_ARRAY:
            case START_OBJECT:
                JsonReaderImpl jsonReader = new JsonReaderImpl(this, true, getCharArrayProvider(), RejectDuplicateKeysMode.DEFAULT);
                return jsonReader.readValue();
            case VALUE_TRUE:
                return JsonValue.TRUE;
            case VALUE_FALSE:
                return JsonValue.FALSE;
            case VALUE_NULL:
                return JsonValue.NULL;
            case VALUE_STRING:
            case KEY_NAME:
                return new JsonStringImpl(getString());
            case VALUE_NUMBER:
                if (isFitLong()) {
                    return new JsonLongImpl(getLong());
                }
                return new JsonNumberImpl(getBigDecimal());
            default:
                throw new IllegalStateException(current + " doesn't support getValue()");
        }
    }

    @Override
    public void skipObject() {
        if (isInObject()) {
            int level = 1;
            do {
                Event event = internalNext();
                if (event == Event.START_OBJECT) {
                    level++;
                } else if (event == Event.END_OBJECT) {
                    level--;
                }
            } while (level > 0 && hasNext());
        }
    }

    @Override
    public void skipArray() {
        if (isInArray()) {
            int level = 1;
            do {
                Event event = internalNext();
                if (event == Event.START_ARRAY) {
                    level++;
                } else if (event == Event.END_ARRAY) {
                    level--;
                }
            } while (level > 0 && hasNext());
        }
    }

    private static class ArrayStreamSpliterator extends Spliterators.AbstractSpliterator<JsonValue> {

        private final JohnzonJsonParserImpl parser;

        ArrayStreamSpliterator(JohnzonJsonParserImpl parser) {
            super(Long.MAX_VALUE, Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.ORDERED);
            this.parser = parser;
        }

        @Override
        public boolean tryAdvance(Consumer<? super JsonValue> action) {
            Event next = parser.next();

            if (next == Event.END_ARRAY) {
                return false;
            }

            action.accept(parser.getValue());
            return true;
        }
    }

    @Override
    public Stream<JsonValue> getArrayStream() {
        Event current = current();
        if (current != Event.START_ARRAY) {
            throw new IllegalStateException(current + " doesn't support getArrayStream()");
        }

        return StreamSupport.stream(new ArrayStreamSpliterator(this), false);
    }

    private static class ObjectStreamSpliterator extends Spliterators.AbstractSpliterator<Map.Entry<String,JsonValue>> {
        
        private final JohnzonJsonParserImpl parser;

        ObjectStreamSpliterator(JohnzonJsonParserImpl parser) {
            super(Long.MAX_VALUE, Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.ORDERED);
            this.parser = parser;
        }

        @Override
        public boolean tryAdvance(Consumer<? super Entry<String, JsonValue>> action) {
            Event next = parser.next();

            if (next == Event.END_OBJECT) {
                return false;
            }

            if (next != Event.KEY_NAME) {
                throw new IllegalStateException("Expected key name event but got " + next + " instead.");
            }

            String key = parser.getString();
            parser.next();
            JsonValue value = parser.getValue();
            action.accept(new AbstractMap.SimpleImmutableEntry<>(key, value));
            return true;
        }

    }

    @Override
    public Stream<Map.Entry<String, JsonValue>> getObjectStream() {
        Event current = current();
        if (current != Event.START_OBJECT) {
            throw new IllegalStateException(current + " doesn't support getObjectStream()");
        }

        return StreamSupport.stream(new ObjectStreamSpliterator(this), false);
    }

    @Override
    public Stream<JsonValue> getValueStream() {
        if (manualNext) {
            throw new IllegalStateException("JsonStream already got propagated manually");
        }

        Event event = internalNext();
        switch (event) {
            case START_ARRAY:
            case START_OBJECT:
            case VALUE_STRING:
            case VALUE_NUMBER:
            case VALUE_TRUE:
            case VALUE_FALSE:
            case VALUE_NULL:
                    return Collections.singletonList(getValue()).stream();
            default:
                throw new IllegalStateException(event + " doesn't support getValueStream");
        }
    }
}
