package Tools;

/**
 * Perform a binary search over bounded or unbounded integer intervals.
 */
public class BinarySearch {
	/**
	 * Users of BinarySearch must implement the Test class to report
	 * the true/false values for any given integer.
	 */
	public interface Test {
		/**
		 * return false for smaller values and true for values larger than
		 * or equal to the desired value.
		 */
		public boolean test(int value);
	}

	/**
	 * Search a bounded interval.
	 * The interval includes <code>min</code> and <code>max</code>.
	 * @returns the smallest integer that makes the test succeed, or
	 * <code>min</code> if the test always returns true, or <code>max</code>
	 * if the test never returns true.
	 */
	public static int interval(int min, int max, Test test) {
		// System.out.println("interval: "+min+" ... "+max);
		if (min>=max) {
			return max;
		}
		if (min+1==max) {
			if (test.test(min)) {
				return min;
			} else {
				return max;
			}
		}
		// now middle has to be between min and max, exclusive.
		// so we are guaranteed to make progress.
		int middle = (min+max)/2;
		if (test.test(middle)) {
			// middle is now an upper bound, since it returns true.
			return interval(min, middle, test);
		} else {
			// middle+1 is now a lower bound, since it returned false.
			return interval(middle+1, max, test);
		}
	}

	/**
	 * @param bound The known bound
	 * @param searchAbove When true,
	 * <code>bound</code> represents the min bound of the search space.
	 * @param test The closure object that knows the truth value
	 * at any given int. test.test(x)==false && test.test(y)==true
	 * should always imply x<y.
	 */
	public static int unbounded(int bound, boolean searchAbove, Test test) {
		// we have a lower bound. To find an upper bound, find a spot
		// where test.test(spot)==true.
		int inner = bound;
		for (long dist = 1;
			dist<=Integer.MAX_VALUE;
			dist = Math.min(dist << 1, (long) Integer.MAX_VALUE)) {

			int outer;
			if (searchAbove) {
				outer = bound + ((int) dist);
				// System.out.println("bound "+inner+" -> "+outer);
				if (test.test(outer)) {
					// found an outside bound (upper, in this case)
					return interval(inner, outer, test);
				}
			} else {
				outer = bound - ((int) dist);
				// System.out.println("bound "+outer+" <- "+inner);
				if (!test.test(outer)) {
					// found an outside bound (upper, in this case)
					return interval(outer, inner, test);
				}
			}
			// if test.test() fails, keep growing the distance.
			// But outer now also serves
			// as a handy inner bound, saving one round of interval() calls.
			inner = outer;
		}
		return bound;
	}

	public static void main(String args[]) {
		if (args.length==3) {
			int lo = Integer.parseInt(args[0]);
			int hi = Integer.parseInt(args[1]);
			int find = Integer.parseInt(args[2]);

			System.out.println("Found: "+interval(lo, hi, new TestTest(find)));
		} else if (args.length==2) {
			// search up
			int lo = Integer.parseInt(args[0]);
			int find = Integer.parseInt(args[1]);

			System.out.println("Found: "+unbounded(lo, true, new TestTest(find)));
		}
	}

	static class TestTest implements Test {
		int magicValue;
		TestTest(int value) {
			this.magicValue = value;
		}
		public boolean test(int value) {
			return value>=magicValue;
		}
	}
}
