/**
 * 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.camel.converter.jaxp;

import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.Reader;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;

/**
 *
 */
class XMLStreamReaderReader extends Reader {
    private static final int BUFFER_SIZE = 4096;
    private XMLStreamReader reader;
    private XMLStreamWriter writer;
    private TrimmableCharArrayWriter chunk;
    private char[] buffer;
    private int bpos;

    XMLStreamReaderReader(XMLStreamReader reader, XMLOutputFactory outfactory) {
        this.reader = reader;
        this.buffer = new char[BUFFER_SIZE];
        this.chunk = new TrimmableCharArrayWriter();
        try {
            this.writer = outfactory.createXMLStreamWriter(chunk);
        } catch (XMLStreamException e) {
            //ignore
        }
    }

    @Override
    public void close() throws IOException {
        
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        int tlen = 0;
        while (len > 0) {
            int n = ensureBuffering(len);
            if (n < 0) {
                break;
            }
            int clen = len > n ? n : len;
            System.arraycopy(buffer, 0, cbuf, off, clen);
            System.arraycopy(buffer, clen, buffer, 0, buffer.length - clen);
            bpos -= clen;
            len -= clen;
            off += clen;
            tlen += clen;
        }
        return tlen > 0 ? tlen : -1;
    }

    private int ensureBuffering(int size) throws IOException {
        if (size < bpos) {
            return bpos;
        }
        // refill the buffer as more buffering is requested than the current buffer status
        try {

            // very first event
            if (XMLStreamConstants.START_DOCUMENT == reader.getEventType()) {
                writer.writeStartDocument("utf-8", "1.0");
            }
            if (chunk.size() < buffer.length) {
                while (reader.hasNext()) {
                    int code = reader.next();
                    switch (code) {
                    case XMLStreamConstants.END_DOCUMENT:
                        writer.writeEndDocument();
                        break;
                    case XMLStreamConstants.START_ELEMENT:
                        QName qname = reader.getName();
                        writer.writeStartElement(qname.getPrefix(), qname.getLocalPart(), qname.getNamespaceURI());
                        for (int i = 0; i < reader.getAttributeCount(); i++) {
                            writer.writeAttribute(
                                    reader.getAttributePrefix(i), reader.getAttributeNamespace(i), reader.getAttributeLocalName(i), 
                                    reader.getAttributeValue(i));
                        }
                        for (int i = 0; i < reader.getNamespaceCount(); i++) {
                            writer.writeNamespace(reader.getNamespacePrefix(i), reader.getNamespaceURI(i));
                        }
                        break;
                    case XMLStreamConstants.END_ELEMENT:
                        writer.writeEndElement();
                        break;
                    case XMLStreamConstants.CHARACTERS:
                        writer.writeCharacters(reader.getText());
                        break;
                    case XMLStreamConstants.COMMENT:
                        writer.writeComment(reader.getText());
                        break;
                    case XMLStreamConstants.CDATA:
                        writer.writeCData(reader.getText());
                        break;
                    default:
                        break;
                    }
                    
                    // check if the chunk is full
                    final int csize = buffer.length - bpos;
                    if (chunk.size() > csize) {
                        System.arraycopy(chunk.getCharArray(), 0, buffer, bpos, csize);
                        bpos = buffer.length;
                        chunk.trim(csize, 0);
                        return buffer.length;
                    }
                }
            }
            final int csize = chunk.size() < buffer.length - bpos ? chunk.size() : buffer.length - bpos; 
            if (csize > 0) {
                System.arraycopy(chunk.getCharArray(), 0, buffer, bpos, csize);
                bpos += csize;
                chunk.trim(csize, 0);
                return bpos;
            } else {
                return bpos > 0 ? bpos : -1;
            }
        } catch (XMLStreamException e) {
            throw new IOException(e);
        }
    }

    static class TrimmableCharArrayWriter extends CharArrayWriter {
        public void trim(int head, int tail) {
            System.arraycopy(buf, head, buf, 0, count - head - tail);
            count -= head + tail;
        }

        public char[] toCharArray(int len) {
            char[] c = new char[len];
            System.arraycopy(buf, 0, c, 0, len);
            return c;
        }

        char[] getCharArray() {
            return buf;
        }
    }
}
