/*
 * 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.openide.text;

import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;

import java.io.*;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;


/** Represents one line in a text document.
 * The line number may change
* when the text is modified, but the identity of the line is retained. It is designed to allow line-dependent
* modules of the IDE (such as the compiler and debugger) to make use of a line consistently even as the text is modified.
*
* @author Ales Novak, Petr Hamernik, Jan Jancura, Jaroslav Tulach, David Konecny
*/
public abstract class Line extends Annotatable implements Serializable {
    /** generated Serialized Version UID */
    private static final long serialVersionUID = 9113186289600795476L;

    static final Logger LOG = Logger.getLogger(Line.class.getName());

    /** Property name of the line number */
    public static final String PROP_LINE_NUMBER = "lineNumber"; // NOI18N

    /** Shows the line only if the editor is open.
     * @see #show(int) <code>show</code>
     * @deprecated Deprecated since 6.21. Use {@link ShowOpenType#NONE}
     * and {@link ShowVisibilityType#NONE} instead.
     */
    @Deprecated
    public final static int SHOW_TRY_SHOW = 0;

    /** Opens the editor if necessary and shows the line.
     * @see #show(int) <code>show</code>
     * @deprecated Deprecated since 6.21. Use {@link ShowOpenType#OPEN}
     * and {@link ShowVisibilityType#NONE} instead.
     */
    @Deprecated
    public final static int SHOW_SHOW = 1;

    /** Opens the editor if necessary, shows the line, and takes the focus.
     * @see #show(int) <code>show</code>
     * @deprecated Deprecated since 6.21. Use {@link ShowOpenType#OPEN}
     * and {@link ShowVisibilityType#FOCUS} instead.
     */
    @Deprecated
    public final static int SHOW_GOTO = 2;

    /** Same as SHOW_GOTO except that the Window Manager attempts to front the
     * editor window (i.e. make it the top most window).
     * @see #show(int) <code>show</code>
     * @see org.openide.windows.TopComponent#toFront()
     * @since 5.8
     * @deprecated Deprecated since 6.21. Use {@link ShowOpenType#OPEN}
     * and {@link ShowVisibilityType#FRONT} instead.
     */
    @Deprecated
    public final static int SHOW_TOFRONT = 3;

    /** Takes the focus in case the editor is already opened and shows the line.
     * Replaces (closes) the last editor opened using SHOW_REUSE in case 
     * the user haven't interacted with it much (e.g. haven't modified it).
     * Opens a new editor in case there is no such reusable editor
     * and marks it for editor reusal. 
     * @see #show(int) <code>show</code>
     * @since org.openide.text 6.14
     * @deprecated Deprecated since 6.21. Use {@link ShowOpenType#REUSE}
     * and {@link ShowVisibilityType#FOCUS} instead.
     */
    @Deprecated
    public final static int SHOW_REUSE = 4;

    /** Focuses or opens given editor, marking it as reusable editor if it
     * was not opened before. Similar to {@link #SHOW_REUSE} but ignores
     * currently reusable editor.
     * @see #show(int) <code>show</code>
     * @since org.openide.text 6.14
     * @deprecated Deprecated since 6.21. Use {@link ShowOpenType#REUSE_NEW}
     * and {@link ShowVisibilityType#FOCUS} instead.
     */
    @Deprecated
    public final static int SHOW_REUSE_NEW = 5;

    /** ShowOpenType and ShowVisibilityType is replacement for constants SHOW_TRY_SHOW, SHOW_SHOW,
     * SHOW_GOTO, SHOW_TOFRONT, SHOW_REUSE, SHOW_REUSE_NEW. It is to provide full control
     * over show method behavior without need to add new constant for missing flag combination.
     *
     * <br><br>Note: Any modification of editor marked for reuse resets reuse flag. There is one global static reference
     * so only one or none editor can be marked for reuse.
     *
     * @see #show(ShowOpenType, ShowVisibilityType) <code>show</code>
     * @see ShowVisibilityType <code>ShowVisibilityType</code>
     * @since org.openide.text 6.21
     *
     */
    public enum ShowOpenType {
    /** shows the line only if the editor is open */
        NONE,
    /** opens editor if necessary (editor was not opened) and shows the line */
        OPEN,
    /** replaces editor marked for reuse (last editor opened using {@link ShowOpenType#REUSE}
     * or {@link ShowOpenType#REUSE_NEW} and opens editor if necessary, if editor is being opened (editor was not opened)
     * marks it for reuse, shows the line */
        REUSE,
    /** ignores editor marked for reuse (resets reference to editor marked for reuse),
     * opens editor if necessary, if editor is being opened (editor was not opened)
     * marks it for reuse, shows the line */
        REUSE_NEW
    };

    /** ShowOpenType and ShowVisibilityType is replacement for constants SHOW_TRY_SHOW, SHOW_SHOW,
     * SHOW_GOTO, SHOW_TOFRONT, SHOW_REUSE, SHOW_REUSE_NEW. It is to provide full control
     * over show method behavior without need to add new constant for missing flag combination.
     *
     * @since org.openide.text 6.21
     * @see #show(ShowOpenType, ShowVisibilityType) <code>show</code>
     * @see ShowOpenType <code>ShowOpenType</code>
     */
    public enum ShowVisibilityType {
    /** no action */
        NONE,
    /** fronts editor component to become visible */
        FRONT,
    /** front editor component to become visible and activates/focuses it. It does
     * the same as {@link ShowVisibilityType#FRONT} plus activates/focuses editor */
        FOCUS
    };

    /** Instance of null implementation of Line.Part */
    static final private Line.Part nullPart = new Line.NullPart();

    /** context of this line */
    private org.openide.util.Lookup dataObject;

    /** Create a new line object based on a given data object.
     * This implementation is abstract, so the specific line number is not used here.
     * Subclasses should somehow specify the position.
     * <P>
     * The context argument shall contain information about the
     * producer of the Line, that can be then extracted by {@link Line#getLookup} call.
     *
     * @param context the context for this line
     */
    public Line(Lookup context) {
        if (context == null) {
            throw new NullPointerException();
        }

        dataObject = context;
    }

    /**
     * Create a new line object based on a given data object.
     * This implementation is abstract, so the specific line number is not used here. Subclasses should somehow specify the position.
     * @param source the object that is producing the Line
     */
    public Line(Object source) {
        this((source instanceof Lookup) ? (Lookup) source : Lookups.singleton(source));

        if (source == null) {
            throw new NullPointerException();
        }
    }

    /** Composes a human presentable name for the line. The default
    * implementation uses the name of data object and the line number
    * to create the display name.
    *
    * @return human presentable name that should identify the line
    */
    public String getDisplayName() {
        return getClass().getName() + ":" + getLineNumber(); // NOI18N
    }

    /** Provides access to the context passed into the line constructor.
     * For example lines produced by <code>DataEditorSupport</code>
     * provide <code>DataObject</code> as the content of the lookup.
     * One can use:
     * <PRE>
     *   dataObjectOrNull = (DataObject)line.getLookup ().lookup (DataObject.class);
     * </PRE>
     * to get the access.
     *
     * @return context associated with the line
     * @since 4.3
     */
    public final org.openide.util.Lookup getLookup() {
        return dataObject;
    }

    /** Get the line number. The last condition in following should
    * always be true:
    * <PRE>
    *   Line.Set lineSet = <line set>
    *   Line l = <some line from line set lineSet>
    *
    *   l.equals (lineSet.getCurrent (l.getLineNumber ()))
    * </PRE>
    *
    * @return current line number (may change as text is edited) (starting at 0)
    */
    public abstract int getLineNumber();

    /** Show the line.
    * @param kind one of {@link #SHOW_TRY_SHOW}, {@link #SHOW_SHOW}, or {@link #SHOW_GOTO}
    * @param column the column of this line which should be selected (starting at 0),
    * value -1 does not change previously selected column
    * @deprecated Deprecated since 6.21. Use {@link #show(ShowOpenType, ShowVisibilityType, int)} instead.
    */
    @Deprecated
    public abstract void show(int kind, int column);

    /** Shows the line (at the first column).
    * @param kind one of {@link #SHOW_TRY_SHOW}, {@link #SHOW_SHOW}, {@link #SHOW_GOTO},
    * {@link #SHOW_REUSE} or {@link #SHOW_REUSE_NEW}
    * @see #show(int, int)
    * @deprecated Deprecated since 6.21. Use {@link #show(ShowOpenType, ShowVisibilityType)} instead.
    */
    @Deprecated
    public void show(int kind) {
        show(kind, 0);
    }
    
    /** Show the line.
    * @param openType one of {@link ShowOpenType#NONE}, {@link ShowOpenType#OPEN},
    *   {@link ShowOpenType#REUSE} or {@link ShowOpenType#REUSE_NEW}
    * @param visibilityType one of {@link ShowVisibilityType#NONE},
    *   {@link ShowVisibilityType#FRONT} or {@link ShowVisibilityType#FOCUS}
    * @param column the column of this line which should be selected (starting at 0),
    *   value -1 does not change previously selected column
    * @since org.openide.text 6.21
    */
    public void show(ShowOpenType openType, ShowVisibilityType visibilityType, int column) {
        if (openType == ShowOpenType.NONE) {
            if (visibilityType == ShowVisibilityType.NONE) {
                show(SHOW_TRY_SHOW, column);
            } else {
                LOG.warning("Line.show(ShowOpenType, ShowVisibilityType, int) uses unsupported combination of parameters");
                show(SHOW_TRY_SHOW, column);
            }
        } else if (openType == ShowOpenType.OPEN) {
            if (visibilityType == ShowVisibilityType.NONE) {
                show(SHOW_SHOW, column);
            } else if (visibilityType == ShowVisibilityType.FOCUS) {
                show(SHOW_GOTO, column);
            } else if (visibilityType == ShowVisibilityType.FRONT) {
                show(SHOW_TOFRONT, column);
            }
        } else if (openType == ShowOpenType.REUSE) {
            if (visibilityType == ShowVisibilityType.FOCUS) {
                show(SHOW_REUSE, column);
            } else {
                LOG.warning("Line.show(ShowOpenType, ShowVisibilityType, int) uses unsupported combination of parameters");
                show(SHOW_REUSE, column);
            }
        } else if (openType == ShowOpenType.REUSE_NEW) {
            if (visibilityType == ShowVisibilityType.FOCUS) {
                show(SHOW_REUSE_NEW, column);
            } else {
                LOG.warning("Line.show(ShowOpenType, ShowVisibilityType, int) uses unsupported combination of parameters");
                show(SHOW_REUSE_NEW, column);
            }
        }
    }

    /** Shows the line (at the first column).
    * @param openType one of {@link ShowOpenType#NONE}, {@link ShowOpenType#OPEN},
    *   {@link ShowOpenType#REUSE} or {@link ShowOpenType#REUSE_NEW}
    * @param visibilityType one of {@link ShowVisibilityType#NONE},
    *   {@link ShowVisibilityType#FRONT} or {@link ShowVisibilityType#FOCUS}
    * @see #show(ShowOpenType, ShowVisibilityType, int)
    * @since org.openide.text 6.21
    */
    public void show(ShowOpenType openType, ShowVisibilityType visibilityType) {
        show(openType, visibilityType, 0);
    }

    /** Set or clear a (debugger) breakpoint at this line.
     * @param b <code>true</code> to turn on
     * @deprecated Deprecated since 1.20. Use {@link Annotation#attach} instead.
     */
    @Deprecated
    public abstract void setBreakpoint(boolean b);

    /** Test if there is a breakpoint set at this line.
     * @return <code>true</code> is there is
     * @deprecated Deprecated since 1.20. Use {@link Annotation} instead.
     */
    @Deprecated
    public abstract boolean isBreakpoint();

    /** Mark an error at this line.
     * @deprecated Deprecated since 1.20. Use {@link Annotation#attach} instead.
     */
    @Deprecated
    public abstract void markError();

    /** Unmark error at this line.
     * @deprecated Deprecated since 1.20. Use {@link Annotation#detach} instead.
     */
    @Deprecated
    public abstract void unmarkError();

    /** Mark this line as current.
     * @deprecated Deprecated since 1.20. Use {@link Annotation#attach} instead.
     */
    @Deprecated
    public abstract void markCurrentLine();

    /** Unmark this line as current.
     * @deprecated Deprecated since 1.20. Use {@link Annotation#detach} instead.
     */
    @Deprecated
    public abstract void unmarkCurrentLine();

    /** Method that should allow the debugger to communicate with lines that
    * wants to have a control over the current line of debugger. It allows the
    * line to refuse the current status and force the debugger to continue
    * over this line.
    * <P>
    * The default implementation simply returns true.
    *
    * @param action type of action that is trying to mark this line as current
    *    one of constants (Debugger.ACTION_BREAKPOINT_HIT,
    *    Debugger.ACTION_TRACE_OVER, etc.)
    * @param previousLine previous line (if any) or null
    *
    * @return true if this line accepts the "current" state or false if the
    *    line wants the debugger to proceed with next instruction
    *
    * @deprecated Deprecated since 1.20, as {@link #markCurrentLine} is deprecated by {@link Annotation#attach}.
    */
    @Deprecated
    public boolean canBeMarkedCurrent(int action, Line previousLine) {
        return true;
    }

    /** Create object which represent part of the text on the line. This part
     * of the line can be used for attaching of annotations.
     * @param column starting column of the part of the text (starting at 0)
     * @param length length of the part of the text
     * @return instance of the Line.Part which represent the part of the text
     * @since 1.20
     */
    public Line.Part createPart(int column, int length) {
        return nullPart;
    }

    public String getText() {
        return null;
    }

    /** Representation of the part of the Line's text. The part of the text is defined by
     * the starting column, length of the part and reference to Line. The length of the
     * part never cross the end of the line.
     * @since 1.20
     */
    public static abstract class Part extends Annotatable {
        /** Property name for the line attribute */
        public static final String PROP_LINE = "line"; // NOI18N

        /** Property name for the column attribute */
        public static final String PROP_COLUMN = "column"; // NOI18N

        /** Property name for the length attribute */
        public static final String PROP_LENGTH = "length"; // NOI18N

        /** Start column of annotation
         * @return column at which this part begining (starting at 0)
         */
        public abstract int getColumn();

        /** Length of the annotated text. The length does not cross line end. If the annotated text is
         * split during the editing, the annotation is shorten till the end of the line. Modules can listen on
         * changes of this value
         * @return length of the part
         */
        public abstract int getLength();

        /** Line can change during editting
         * @return reference to the Line to which this part belongs
         */
        public abstract Line getLine();
    }

    /** Implementation of Line.Part which is presenting empty part */
    static final private class NullPart extends Part {
        NullPart() {
        }

        public int getColumn() {
            return 0;
        }

        public int getLength() {
            return 0;
        }

        public Line getLine() {
            return null;
        }

        public String getText() {
            return null;
        }
    }

    /** Object that represents a snapshot of lines at the time it was created.
    * It is used to create a mapping from line
    * numbers to line objects, for example when the file is saved.
    * Such a mapping can then be used by the compiler, e.g., to find
    * the correct {@link Line} object, assuming it has a line number.
    * <P>
    * Mappings of line numbers to line objects will survive modifications
    * of the text, and continue to represent the original lines as close as possible.
    * For example: if a new line is inserted at the 10th line of a document
    * and the compiler module asks for the 25th line (because the compiler reports an error at line 25 in the saved file) via the line set, the 26th line
    * of the current document will be marked as being in error.
    */
    public static abstract class Set extends Object {
        /** date when the object has been created */
        private Date date;

        /**
         * Binary-searchable vector of lines.
         */
        private LineVector lineVector;

        /** Create a new snapshot. Remembers the date when it was created. */
        public Set() {
            date = new Date();
        }

        /** Returns a set of line objects sorted by their
        * line numbers. This immutable list will contains all lines held by this
        * line set.
        *
        * @return list of lines
        */
        public abstract List<? extends Line> getLines();

        /** Get creation time for this line set.
         * @return time
        */
        public final Date getDate() {
            return date;
        }

        /** Find line object in the line set corresponding to original line number.
         * That is, finds the line in the current document which originally had the indicated line number.
         * If there have been modifications of that line, find one as close as possible.
        *
        * @param line number of the line (starting at 0)
        * @return line object
        * @exception IndexOutOfBoundsException if <code>line</code> is an invalid index for the original set of lines
        */
        public abstract Line getOriginal(int line) throws IndexOutOfBoundsException;

        /** Find line object representing the line in current document.
        *
        *
        * @param line number of the line in current state of the document (starting at 0)
        * @return line object
        * @exception IndexOutOfBoundsException if <code>line</code> is an invalid index for the original set of lines
        */
        public abstract Line getCurrent(int line) throws IndexOutOfBoundsException;

        /** Finds an original line number for given line in this line set.
         * @param line the line to look for
         * @return the number (starting at 0) that best matches the line number of the line or -1
         *    if the line does seem to be produced by this line set
         * @since 4.38
         */
        public int getOriginalLineNumber(Line line) {
            return computeOriginal(this, line);
        }

        /**
         * Lazily creates or finds binary-searchable vector of registered lines.
         */
        LineVector findLineVector() {
            synchronized (date) {
                if (lineVector != null) {
                    return lineVector;
                }

                lineVector = new LineVector();

                return lineVector;
            }
        }

        /** Registers the line to this <code>Line.Set</code>.
         * @param line <code>Line</code> to register
         * @return registered <code>Line</code>. <em>Note:</em> the retruned
         * <code>Line</code> could be different (identityHashCode not equal)
         * from the one passed in */
        final Line findOrCreateLine(int lineIndex, LineVector.LineCreator lineCreator) {
            // beware of null argument
            if (lineCreator == null) {
                throw new NullPointerException();
            }

            LineVector lineVector = findLineVector();
            return lineVector.findOrCreateLine(lineIndex, lineCreator);
        }

        /** Finds whether a line equal to provided is already registered.
         * @param line the line to register
         * @return the registered line equal to line or null
         */
        final Line findLine(Line line) {
            LineVector lineVector = findLineVector();
            return lineVector.findOrCreateLine(line.getLineNumber(), null);
        }

        /** A method that for a given Line.Set and a line computes the best
         * original line number based on the querying the set. This is called
         * in default implementation of getOriginal (Line) to provide
         * inefficient (but better then most people would write) way to
         * compute the number. It is static so it can be tested from
         * tests working on DocumentLine objects that override the
         * getOriginal (Line) method.
         *
         * @param set the set to search in
         * @param line the line to look for
         * @return closest possible line number for given line
         */
        static int computeOriginal(Line.Set set, Line line) {
            int n = line.getLineNumber();
            Line current = null;

            try {
                current = set.getOriginal(n);

                if (line.equals(current)) {
                    return n;
                }
            } catch (IndexOutOfBoundsException ex) {
                // ok, few lines have been added and this one is now
                // bellow the end of the document
            }

            if (current == null) {
                return binarySearch(set, n, 0, findMaxLine(set));
            }

            if (n < current.getLineNumber()) {
                return binarySearch(set, n, 0, current.getLineNumber());
            } else {
                return binarySearch(set, n, current.getLineNumber(), findMaxLine(set));
            }
        }

        /** Does a search for a given line number in a given Line.Set.
         */
        private static int binarySearch(Line.Set set, int number, int from, int to) {
            while (from < to) {
                int middle = (from + to) / 2;

                Line l = set.getOriginal(middle);
                int ln = l.getLineNumber();

                if (ln == number) {
                    return middle;
                }

                if (ln < number) {
                    // try after the middle
                    from = middle + 1;
                } else {
                    // try before the middle
                    to = middle - 1;
                }
            }

            return from;
        }

        private static int findMaxLine(Line.Set set) {
            int from = 0;
            int to = 32000;

            // probably larger than any existing document
            for (;;) {
                try {
                    set.getOriginal(to);

                    // if the line exists, double the max number, but keep
                    // for reference that it exists
                    from = to;
                    to *= 2;
                } catch (IndexOutOfBoundsException ex) {
                    break;
                }
            }

            while (from < to) {
                int middle = (from + to + 1) / 2;

                try {
                    set.getOriginal(middle);

                    // line exists
                    from = middle;
                } catch (IndexOutOfBoundsException ex) {
                    // line does not exists, we have to search lower
                    to = middle - 1;
                }
            }

            return from;
        }
    }
     // End of class Line.Set.
}
