/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   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.bodington.util;

import java.util.Arrays;

/**
 * Class to allow us to get the column and line number for an index in a string.
 * We only scan the string for newlines if {@link #getColumn(int)} or
 * {@link #getLine(int)} are called. 
 * @author buckett
 */
public class NumberedCharSequence implements CharSequence
{
    private String string;
    private int newlines[];
    
    public NumberedCharSequence(String string)
    {
        this.string = string;
    }

    public int length()
    {
        return string.length();
    }

    public char charAt(int index)
    {
        return string.charAt(index);
    }

    public CharSequence subSequence(int start, int end)
    {
        return string.subSequence(start, end);
    }
    
    /**
     * Gets the line in which the passed index occurs.
     * @param index The index to look for.
     * @return The line number (zero based).
     */
    public int getLine(int index)
    {
        if (newlines == null)
            calculateNewlines();
        
        int newline = Arrays.binarySearch(newlines, index);
        if ( newline < 0)
        {
          return (newline * -1)-1;  
        }
        else
        {
            return newline;
        }
        
    }
    
    /**
     * Gets the column of the current line this index is in.
     * @param index The idnex to look for.
     * @return The column number (zero based).
     */
    public int getColumn(int index)
    {
        int line = getLine(index);
        if (line > 0)
            return index - newlines[line-1] - 1;
        return index;
    }

    private void calculateNewlines()
    {
        IntStack stack = new IntStack();
        int found = 0;
        while ((found = string.indexOf('\n', found)) != -1)
        {
            stack.push(found++);
        }
        newlines = stack.toArray();
    }
    
    /**
     * A tiny stack class which we can push ints into and it should grow
     * automatically. Saves wrapping ints into Integers.
     * @author buckett
     */
    private class IntStack
    {
        int stack[] = new int[10];
        private int last = 0;
        
        /**
         * Push a new element onto the stack.
         * @param value
         */
        public void push(int value)
        {
            if (!(last < stack.length))
                resize();
            stack[last++] = value;
        }
        
        /**
         * Dumps out an array the size of the stack.
         * @return An array.
         */
        public int[] toArray()
        {
            int temp[] = new int[last];
            System.arraycopy(stack, 0, temp, 0, last);
            return temp;
        }

        /**
         * Double the size of the internal array.
         */
        private void resize()
        {
            int temp[] = new int[stack.length*2];
            System.arraycopy(stack,0,temp,0,stack.length);
            stack = temp;
        }
    }

}
