/*
 * 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.
 */

/*
 * "Line.java"
 * Line.java 1.12 01/07/24
 */

package org.netbeans.lib.terminalemulator;

final class Line {
    public int glyph_glyph;
    public int glyph_rendition;	// Background color for the whole line
    				// This is independent of per-character
				// rendition.

    private char buf[];		// actual characters
    private int attr[];		// attributes (allocated on demand)
    
    // SHOULD use shorts?
    private int capacity;	// == buf.length == attr.length
    private int length;		// how much of buf and attr is filled
    
    
    public Line() {
        reset();
    }
    
    public void reset() {
        length = 0;
        capacity = 32;
        buf = new char[capacity];
        attr = null;
        glyphId = 0;
        backgroundColor = 0;
        wrapped = false;
        about_to_wrap = false;
    }

    public void reset(Term term, int end, int eraseAttr) {
        reset();
        clearToEndFrom(term, 0, end, eraseAttr);
    }
    
    public int capacity() {
        return capacity;
    }
    
    /**
     * Number of characters in the line.
     * charArray()[length()] is where the cursor or newline would be.
     *
     */
    public int length() {
        return length;
    }
    
    public boolean hasAttributes() {
        return attr != null;
    }
    
    private int glyphId;
    public void setGlyphId(int glyphId) {
        this.glyphId = glyphId;
    }
    public int getGlyphId() {
        return glyphId;
    }
    
    private int backgroundColor;// Background color for the whole line
    // This is independent of per-character
    // rendition.
    public void setBackgroundColor(int bgColor) {
        backgroundColor = bgColor;
    }
    public int getBackgroundColor() {
        return backgroundColor;
    }
    
    public void setWrapped(boolean wrapped) {
        this.wrapped = wrapped;
    }
    public boolean isWrapped() {
        return wrapped;
    }
    // SHOULD collapse wrapped with about_to_wrap into a bitfield
    private boolean wrapped;
    
    
    
    public boolean setAboutToWrap(boolean about_to_wrap) {
        boolean old_about_to_wrap = about_to_wrap;
        this.about_to_wrap = about_to_wrap;
        return old_about_to_wrap;
    }
    public boolean isAboutToWrap() {
        return about_to_wrap;
    }
    // Perhaps SHOULD have a state: normal, about-to-wrap, wrapped.
    private boolean about_to_wrap;
    
    
    /**
     * Return true if we've already seen attributes for this line
     * or 'a' is the first one, in which case we allocate the 'attr' array.
     */
    private boolean haveAttributes(int a) {
        if (attr == null && a != 0) {
            attr = new int[capacity];
        }
        return attr != null;
    }
    
    
    public void accumulateInto(int bcol, int ecol, StringBuffer buf) {
        buf.append(this.buf, bcol, ecol-bcol+1);
    }
    
    public char charAt(int col) {
        return buf[col];
    }
    
    private void charAtPut(int col, char c) {
        buf[col] = c;
    }
    
    @Override
    public String toString() {
        assert false;
        return new String(buf);
    }
    
    public char[] getChars(char[] array) {
        System.arraycopy(buf, 0, array, 0, length);
        return array;
    }
    
    public int [] attrArray() {
        return attr;
    }
    
    
    public byte width(MyFontMetrics metrics, int at) {
        if (at >= capacity)
            return 1;
        return (byte) metrics.wcwidth(charAt(at));
    }
    
    /*
     * Convert a cell column to a buffer column.
     */
    public int cellToBuf(MyFontMetrics metrics, int target_col) {
        if (metrics.isMultiCell()) {
            int bc = 0;
            int vc = 0;
            for(;;) {
                int w = width(metrics, bc);
                if (vc + w - 1 >= target_col)
                    break;
                vc += w;
                bc++;
            }
            return bc;
        } else {
            return target_col;
        }
    }
    
    /*
     * Convert a buffer column to a cell column.
     */
    public int bufToCell(MyFontMetrics metrics, int target_col) {
        if (metrics.isMultiCell()) {
            int vc = 0;
            for (int bc = 0; bc < target_col; bc++) {
                vc += width(metrics, bc);
            }
            return vc;
        } else {
            return target_col;
        }
    }
    
    
    
    public StringBuffer stringBuffer() {
        // only used for word finding
        // Grrr, why don't we have: new StringBuffer(buf, 0, length);

        // This gets passed on to WordDelineator which is part of the public API
        @SuppressWarnings("StringBufferMayBeStringBuilder")

        StringBuffer sb = new StringBuffer(length);
        return sb.append(buf, 0, length);
    }
    
    /*
     * Ensure that we have at least 'min_capacity'.
     */
    private void ensureCapacity(Term term, int min_capacity) {
        
        term.noteColumn(this, min_capacity);
        
        if (min_capacity <= capacity)
            return;	// nothing to do
        
        // doubling
        int new_capacity = (length+1) * 2;
        if (new_capacity < 0)
            new_capacity = Integer.MAX_VALUE;
        else if (min_capacity > new_capacity)
            new_capacity = min_capacity;
        
        
        char new_buf[] = new char[new_capacity];
        System.arraycopy(buf, 0, new_buf, 0, length);
        buf = new_buf;
        
        if (attr != null) {
            int new_attr[] = new int[new_capacity];
            System.arraycopy(attr, 0, new_attr, 0, length);
            attr = new_attr;
        }
        
        capacity = new_capacity;
    }

    private void fillGap(int col, int eraseAttr) {
        // fill any newly opened gap (between length and col) with SP
        for (int cx = length; cx < col; cx++)
            charAtPut(cx, ' ');
        if (haveAttributes(eraseAttr)) {
            for (int cx = length; cx < col; cx++)
                attr[col] = eraseAttr;
        }
    }
    
    /**
     * Insert character and attribute at 'column' and shift everything
     * past 'column' right.
     */
    public void insertCharAt(Term term, char c, int column, int a) {
        int new_length = length + 1;
        
        if (column >= length) {
            new_length = column+1;
            ensureCapacity(term, new_length);
            fillGap(column, 0);                 // SHOULD be eraseAttr?
        } else {
            ensureCapacity(term, new_length);
            System.arraycopy(buf, column, buf, column + 1, length - column);
            if (haveAttributes(a))
                System.arraycopy(attr, column, attr, column + 1, length - column);
        }
        
        term.checkForMultiCell(c);
        
        charAtPut(column, c);
        if (haveAttributes(a))
            attr[column] = a;
        
        length = new_length;
    }
    
    /*
     * Generic addition and modification.
     * Line will grow to accomodate column.
     */
    public void setCharAt(Term term, char c, int column, int a, int eraseAttr) {
        if (column >= length) {
            ensureCapacity(term, column+1);
            fillGap(column, eraseAttr);
            length = column+1;
        }
        term.checkForMultiCell(c);
        if (column < 0) {
            throw new IllegalArgumentException(String.format("column=%d; buf_length=%d; buf_capacity=%d", column, length, capacity));   //NOI18N
        }
        charAtPut(column, c);
        if (haveAttributes(a)) {
            attr[column] = a;
        }
    }
    
    public void deleteCharAt(int column) {
        if (column < 0 || column >= length)
            return;
        System.arraycopy(buf, column+1, buf, column, length-column-1);
        charAtPut(length-1, (char) 0);
        if (attr != null) {
            System.arraycopy(attr, column+1, attr, column, length-column-1);
            attr[length-1] = 0;
        }
        // SHOULD move this up
        length--;
    }
    
    public void clearToEndFrom(Term term, int from, int end, int eraseAttr) {
        ensureCapacity(term, end+1);
        fillGap(end, eraseAttr);

        // Grrr, why is there a System.arrayCopy() but no System.arrayClear()?
        for (int cx = from; cx <= end; cx++)
            charAtPut(cx, ' ');
        if (haveAttributes(eraseAttr)) {
            for (int cx = from; cx <= end; cx++)
                attr[cx] = eraseAttr;
        }
        length = end+1;
    }

    public void clearTo(Term term, int col, int eraseAttr) {
        ensureCapacity(term, col+1);
        fillGap(col, eraseAttr);

        for (int cx = 0; cx <= col; cx++)
            charAtPut(cx, ' ');
        if (haveAttributes(eraseAttr)) {
            for (int cx = 0; cx <= col; cx++)
                attr[cx] = eraseAttr;
        }
        if (length < col+1)
            length = col+1;
    }

    public void clearFromTo(Term term, int from, int to, int eraseAttr) {
        if (from > to)
            return;
        ensureCapacity(term, to+1);
        fillGap(to, eraseAttr);

        for (int cx = from; cx <= to; cx++)
            charAtPut(cx, ' ');
        if (haveAttributes(eraseAttr)) {
            for (int cx = from; cx <= to; cx++)
                attr[cx] = eraseAttr;
        }
        if (length < to+1)
            length = to+1;
    }
    
    /*
     * Used for selections
     * If the ecol is past the actual line length a "\n" is appended.
     */
    @SuppressWarnings("AssignmentToMethodParameter")
    public String text(int bcol, int ecol) {
        /* DEBUG
        System.out.println("Line.text(bcol " + bcol + " ecol " + ecol + ")");	// NOI18N
        System.out.println("\tlength " + length);	// NOI18N
         */
        
        
        String newline = "";	// NOI18N
        
        // This only happens for 'empty' lines below the cursor.
        // Actually it also happens for 'empty' lines in the middle.
        // See issue 31951 for example.
        // So in order to get newlines in selected text we will also get
        // newlines from the 'empty' lines below the cursor.
        // This is in fact what xterm does.
        // DtTerm doesn't allow selection of the 'empty' lines below the
        // cursor, but that is issue 21577 and is not easy to solve.
        
        if (length == 0)
	    return "\n"; // NOI18N
        
        if (ecol >= length) {
            // The -1 snuffs out the newline.
            ecol = length-1;
            newline = "\n";	// NOI18N
            
            if (bcol >= length)
                bcol = length-1;
        }
        int count = ecol - bcol + 1;
        if (bcol < 0 ||  count < 0) {
            throw new IllegalArgumentException(String.format("offset=%d; count=%d; buf_length=%d; buf_capacity=%d", bcol, count, length, capacity));    //NOI18N
        }
        return new String(buf, bcol, count) + newline;
    }
    
    public void setCharacterAttribute(int bcol, int ecol,
            int value, boolean on) {
        // HACK: value is the ANSI code, haveAttributes takes out own
        // compact encoding, but it only checks for 0 so it's OK.
        if (!haveAttributes(value))
            return;
        
	try {
	    if (on) {
		for (int c = bcol; c <= ecol; c++)
		    attr[c] = Attr.setAttribute(attr[c], value);
	    } else {
		for (int c = bcol; c <= ecol; c++)
		    attr[c] = Attr.unsetAttribute(attr[c], value);
	    }
	} catch (ArrayIndexOutOfBoundsException x) {
	    System.out.printf("bcol %d  ecol %d  capacity %d  length %d buf %d  attr %d", // NOI18N
		    bcol, ecol, capacity, length, buf.length, attr.length);
	    throw x;
	}
    }
}
