/*
 * 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
 *
 *   https://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.
 *
 *   SPDX-License-Identifier: Apache-2.0
 */

package org.apache.jena.atlas.lib;

import java.nio.IntBuffer ;
import java.util.Comparator ;
import java.util.List ;

public class Alg
{
    // Linear search is really there to test binary search.
    static int linearSearch(IntBuffer buff, int key)
    { 
        return linearSearch(buff, 0, buff.limit(), key) ;
    }

    static int linearSearch(IntBuffer buff, int low, int high, int key)
    {
        int len = buff.limit(); // In int units.
        check(len, low, high) ;
        for ( int i = low ; i < high ; i++ )
        {
            int k2 = buff.get(i) ;
            if ( k2 == key )
                return i ;
            if ( k2 > key )
                return encodeIndex(i) ;
        }
        return encodeIndex(high) ;
    }
    
    // The encoded offset (insertion point) when not found.
    public static final int encodeIndex(int i) { return -(i+1) ; } 
    public static final int decodeIndex(int i) { return -(i+1) ; } 

    
    /* Both Arrays and Collections have a binary search implementation.
     * Arrays searches Object[] arrays, and Collections searches List<T>
     *
     * But sometime things are not so easy, and it isn't neat as to
     * what is being searched, like a slice of an NIO Buffer
     * 
     * http://en.wikipedia.org/wiki/Binary_search
     */
    
    public static int binarySearch(IntBuffer buff, int value)
    {
        return  binarySearch(buff, 0, buff.limit(), value) ;
    }
    
    public static int binarySearch(IntBuffer buff, int low, int high, int value)
    {
        // Low is inclusive, high is exclusive.
        check(buff.limit(), low, high) ;
        high -- ;   // Convert high to inclusive.

        // Non-tail-recursive form, because tail-recursion removal
        // is not required by java (unlike scheme).
        
        while (low <= high)
        {
            int mid = (low + high) >>> 1 ;  // int divide by 2 : better: mid = low + ((high - low) / 2)
            int k = buff.get(mid) ;
            
            // Two comparisons : see wikipedia for one comparison version.
            if (k < value)
                low = mid + 1 ;
            else if ( k > value)
                high = mid - 1 ;
            else
                return mid ;
        }
        // On exit, when not finding, low is the least value
        // above, including off the end of the array.  
        return encodeIndex(low) ;
    }
    
    // Alt form - no early termination.
//    public static int binarySearch2(IntBuffer buff, int value, int low, int high)
//    {
//        // Low is inclusive, high is exclusive
//        check(buff.limit(), low, high) ;
//        int N = high ;
//        
//        // Uses high as exclusive index
//        while (low < high)
//        {
//            int mid = (low + high) >>> 1 ;  // int divide by 2 : better: mid = low + ((high - low) / 2)
//            int k = buff.get(mid) ;
//            
//            // Two comparisons : see wikipedia for one comparison version.
//            if ( k < value)
//                low = mid + 1 ;
//            else 
//                //can't be high = mid-1: here A[mid] >= value,
//                //so high can't be < mid if A[mid] == value
//                high = mid;
//        }
//        if (low < N && buff.get(low) == value )
//            return low ;
//        else
//            return enc(low) ;
//    }
        
    private static void check(int len, int low, int high)
    {
        if ( low > high )
            throw new IllegalArgumentException("Low index ("+low+") is not less than high index ("+high+")") ; 
        if ( low < 0 )
            throw new ArrayIndexOutOfBoundsException("Low index is negative: "+low) ; 
        if ( high > len )
            throw new ArrayIndexOutOfBoundsException("High index is too large: "+high) ;
    }

    // Why isn't this in the java RT?
    public static <T> int binarySearch( List<T> array, int low, int high, T value, Comparator<T> comparator )
    {
        check(array.size(), low, high) ;
        high -- ;

        while( low <= high )
        {
            int mid = ( low + high ) >>> 1 ;

            T k = array.get(mid) ;
            
            int x = comparator.compare(k, value) ;
            if ( x < 0 )
                low = mid + 1 ;
            else if ( x > 0 )
                high = mid - 1 ;
            else
                return mid ;
        }
        return encodeIndex(low) ;
    }

    // Why isn't this in the java RT?
    public static <T extends Comparable<? super T>>
    int binarySearch(T[] array, int low, int high, T value)
    {
        check(array.length, low, high) ;
        high -- ;

        while( low <= high )
        {
            int mid = ( low + high ) >>> 1 ;

            T k = array[mid] ;
            
            int x = k.compareTo(value) ; // comparator.compare(k, value) ;
            if ( x < 0 )
                low = mid + 1 ;
            else if ( x > 0 )
                high = mid - 1 ;
            else
                return mid ;
        }
        return encodeIndex(low) ;
    }
    
    
    // Use Arrays.binarySearch functions
    
//    public static int binarySearch(int buff[], int value)
//    { return binarySearch(buff, value, 0, buff.length) ; } 
//
//    public static int binarySearch(int buff[], int low, int high, int value)
//    {
//        check(buff.length, low, high) ;
//        // Low is inclusive, high is exclusive.
//        high -- ;   // Convert high to inclusive.
//
//        // Non-tail-recursive form, because tail-recursion removal
//        // is not required by java (unlike scheme).
//        
//        while (low <= high)
//        {
//            int mid = (low + high) >>> 1 ;  // int divide by 2 
//            int k = buff[mid] ;
//
//            if (k < value)
//                low = mid + 1 ;
//            else if ( k > value)
//                high = mid - 1 ;
//            else
//                return mid ;
//        }
//        // On exit, when not finding, low is the least value
//        // above, including off the end of the array.  
//        return encodeIndex(low) ;
//    }
//
//    
//    public static int binarySearch(long buff[], int value)
//    { return binarySearch(buff, value, 0, buff.length) ; } 
//    
//    public static int binarySearch(long buff[], int low, int high, long value)
//    {
//        check(buff.length, low, high) ;
//        // Low is inclusive, high is exclusive.
//        high -- ;   // Convert high to inclusive.
//
//        // Non-tail-recursive form, because tail-recursion removal
//        // is not required by java (unlike scheme).
//        
//        while (low <= high)
//        {
//            int mid = (low + high) >>> 1 ;  // int divide by 2 
//            long k = buff[mid] ;
//
//            if (k < value)
//                low = mid + 1 ;
//            else if ( k > value)
//                high = mid - 1 ;
//            else
//                return mid ;
//        }
//        // On exit, when not finding, low is the least value
//        // above, including off the end of the array.  
//        return encodeIndex(low) ;
//    }
}
