/*
 * 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.netbeans.modules.java.guards;

import java.io.CharArrayWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.netbeans.api.editor.guards.GuardedSection;
import org.netbeans.api.editor.guards.InteriorSection;
import org.netbeans.api.editor.guards.SimpleSection;

/**
 *
 * @author Jan Pokorsky
 */
final class JavaGuardedWriter {

    private Iterator<SectionDescriptor> descs;
    
    private CharArrayWriter writer;
    
    /** Current section from the previous iterator. For filling this
    * field is used method nextSection.
    */
    private SectionDescriptor current;

    /** Current offset in the original document (NOT in the encapsulated
    * output stream.
    */
    private int offsetCounter;

    /** This flag is used during writing. It is complicated to explain. */
    boolean wasNewLine;

    private StringBuilder currentLine;

    /** number of consecutive spaces */
    int spaces;
    
    /** Creates a new instance of JavaGuardedWriter */
    public JavaGuardedWriter() {
    }

    public void setGuardedSection(List<GuardedSection> sections) {
        assert this.descs == null; // should be invoked just once
        this.descs = prepareSections(sections).iterator();
    }

    public char[] translate(char[] writeBuff) {
        if (this.descs == null || !this.descs.hasNext()) {
            return writeBuff;
        }
        this.writer = new CharArrayWriter(writeBuff.length);
        this.offsetCounter = 0;
        this.wasNewLine = false;
        this.currentLine = new StringBuilder(100);

        nextSection();
        
        try {
            for (char c : writeBuff) {
                writeOneChar(c);
            }
            writer.append(currentLine);
            return this.writer.toCharArray();
        } catch (IOException ex) {
            // it hardly occurs since we write to CharArrayWriter, but for sure
            throw new IllegalStateException(ex);
        } finally {
            this.writer = null;
            this.current = null;
        }
        
    }

    /** Write one character. If there is a suitable place,
    * some special comments are written to the underlaying stream.
    * @param b char to write.
    */
    void writeOneChar(int b) throws IOException {
        if (b == '\r')
            return;

        if (current != null) {
            if (offsetCounter == current.getBegin()) {
                wasNewLine = false;
            }
            if (current.getBegin() <= offsetCounter && b == '\n') {
                switch(current.getType()) {
                    case LINE:

                        if (!wasNewLine) {
                            if (offsetCounter + 1 >= current.getEnd()) {
                                writeMagic(GuardTag.LINE, current.getName());
                                nextSection();
                            }
                            else {
                                writeMagic(GuardTag.BEGIN, current.getName());
                                wasNewLine = true;
                            }
                        }
                        else {
                            if (offsetCounter + 1 >= current.getEnd()) {
                                writeMagic(GuardTag.END, current.getName());
                                nextSection();
                            }
                        }

                        break;
                    case FIRST:
                    case HEADER:

                        if (!wasNewLine) {
                            if (offsetCounter + 1 >= current.getEnd()) {
                                writeMagic(GuardTag.FIRST, current.getName());
                                nextSection();
                            }
                            else {
                                writeMagic(GuardTag.FIRST, current.getName());
                                wasNewLine = true;
                            }
                        }
                        else {
                            if (offsetCounter + 1 >= current.getEnd()) {
                                writeMagic(GuardTag.HEADEREND, current.getName());
                                nextSection();
                            }
                        }

                        break;
                    case LAST:
                    case END:

                        writeMagic(GuardTag.LAST, current.getName());

                        nextSection();

                        break;
                }
            }
        }
        if (b == ' ') {
            spaces++;
        } else {
            while (spaces > 0) {
                currentLine.append(' ');
                spaces--;
            }
            currentLine.append((char)b);
            if (b == '\n') {
                writer.append(currentLine);
                currentLine.delete(0, currentLine.length());
            }
        }
        offsetCounter++;
    }

    /** Try to get next sectionDesc from the 'sections'
    * If there is no more section the 'current' will be set to null.
    */
    private void nextSection() {
        current = descs.hasNext() ? descs.next() : null;
    }

    /** Writes the magic to the underlaying stream.
    * @param type The type of the magic section - T_XXX constant.
    * @param name name of the section.
    */
    private void writeMagic(GuardTag type, String name) throws IOException {
        // XXX see #73805 to resolve this hack
//        if (!shouldReload) {
//            shouldReload = spaces != SECTION_MAGICS[type].length()  + name.length();
//        }
        spaces = 0;
        String magic = JavaGuardedReader.MAGIC_PREFIX + type.name() + ':';
        if (JavaGuardedReader.getKeepGuardedComments()) {
            int i = currentLine.lastIndexOf(magic);
            if (i >= 0) { // after section rename there could still be a comment with the previous name
                currentLine.delete(i, currentLine.length());
            }
        }
        currentLine.append(magic);
        currentLine.append(name);
    }

    /** This method prepares the iterator of the SectionDesc classes
    * @param list The list of the GuardedSection classes.
    * @return iterator of the SectionDesc
    */
    private List<SectionDescriptor> prepareSections(List<? extends GuardedSection> list) {
        List<SectionDescriptor> dest = new ArrayList<SectionDescriptor>(list.size());

        for (GuardedSection o: list) {
            if (o instanceof SimpleSection) {
                SectionDescriptor desc = new SectionDescriptor(
                        GuardTag.LINE,
                        o.getName(),
                        o.getStartPosition().getOffset(),
                        o.getEndPosition().getOffset()
                        );
                dest.add(desc);
            } else {
                SectionDescriptor desc = new SectionDescriptor(
                        GuardTag.HEADER,
                        o.getName(),
                        o.getStartPosition().getOffset(),
                        ((InteriorSection) o).getBodyStartPosition().getOffset() - 1
                        );
                dest.add(desc);

                desc = new SectionDescriptor(
                        GuardTag.END,
                        o.getName(),
                        ((InteriorSection) o).getBodyEndPosition().getOffset() + 1,
                        o.getEndPosition().getOffset()
                        );
                dest.add(desc);
            }
        }
        return dest;
    }
    
}
