/*
 * Copyright 2011, 2012 Odysseus Software GmbH
 *
 * Licensed 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.synapse.commons.staxon.core.json.stream.util;

import java.io.IOException;

import javax.xml.XMLConstants;
import javax.xml.namespace.QName;

import org.apache.synapse.commons.staxon.core.json.stream.JsonStreamSource;
import org.apache.synapse.commons.staxon.core.json.stream.JsonStreamToken;

public class AddRootSource extends StreamSourceDelegate {
    private enum State {
        START_DOC,
        ROOT_NAME,
        ROOT_XMLNS_NAME,
        ROOT_XMLNS_VALUE,
        DELEGATE,
        END_DOC
    }

    private final QName root;
    private final char namespaceSeparator;

    private State state = State.START_DOC;
    private int depth = 0;

    public AddRootSource(JsonStreamSource delegate, QName root, char namespaceSeparator) {
        super(delegate);
        this.root = root;
        this.namespaceSeparator = namespaceSeparator;
    }

    @Override
    public String name() throws IOException {
        if (state == State.ROOT_NAME) {
            state = State.DELEGATE;
            if (XMLConstants.DEFAULT_NS_PREFIX.equals(root.getPrefix())) {
                return root.getLocalPart();
            } else {
                return root.getPrefix() + namespaceSeparator + root.getLocalPart();
            }
        } else if (state == State.ROOT_XMLNS_NAME) {
            state = State.ROOT_XMLNS_VALUE;
            if (XMLConstants.DEFAULT_NS_PREFIX.equals(root.getPrefix())) {
                return '@' + XMLConstants.XMLNS_ATTRIBUTE;
            } else {
                return '@' + XMLConstants.XMLNS_ATTRIBUTE + namespaceSeparator + root.getLocalPart();
            }
        }
        return super.name();
    }

    @Override
    public Value value() throws IOException {
        if (state == State.ROOT_XMLNS_VALUE) {
            state = State.DELEGATE;
            return new Value(root.getNamespaceURI());
        }
        return super.value();
    }

    @Override
    public void startObject() throws IOException {
        if (state == State.START_DOC) {
            state = State.ROOT_NAME;
        } else {
            if (depth == 1 && !XMLConstants.NULL_NS_URI.equals(root.getNamespaceURI())) {
                state = State.ROOT_XMLNS_NAME;
            }
            super.startObject();
        }
        depth++;
    }

    @Override
    public void endObject() throws IOException {
        if (state == State.END_DOC) {
            state = null;
            return;
        }
        if (depth == 1 && state == State.DELEGATE && super.peek() == JsonStreamToken.NONE) {
            state = State.END_DOC;
        }
        if (state != State.END_DOC) {
            super.endObject();
        }
        depth--;
    }

    @Override
    public JsonStreamToken peek() throws IOException {
        if (state == null) {
            return JsonStreamToken.NONE;
        }
        switch (state) {
            case START_DOC:
                return JsonStreamToken.START_OBJECT;
            case ROOT_NAME:
                return JsonStreamToken.NAME;
            case ROOT_XMLNS_NAME:
                return JsonStreamToken.NAME;
            case ROOT_XMLNS_VALUE:
                return JsonStreamToken.VALUE;
            case END_DOC:
                return JsonStreamToken.END_OBJECT;
            case DELEGATE:
                JsonStreamToken result = super.peek();
                if (depth == 1 && result == JsonStreamToken.NONE) {
                    result = JsonStreamToken.END_OBJECT;
                }
                return result;
            default:
                throw new IllegalStateException("Unexpected state: " + state);
        }
    }
}
