package org.academiadecodigo.bootcamp.algorithms;

/**
 * MIT License
 * (c) 2017 Ricardo Constantino
 */
public class Algorithms {
    public static void main(String[] args) {
        int max_num_of_cards = 15;
        int card_to_find = (int) Math.floor(Math.random() * (max_num_of_cards-1));
        if (args.length > 0) {
            if (args[0].equals("-h") || args[0].equals("--help")) {
                printUsage(0);
            } else {
                try {
                    card_to_find = Integer.parseInt(args[0]);
                    if (card_to_find > max_num_of_cards-1) {
                        throw new NumberFormatException();
                    }
                } catch (NumberFormatException error) {
                    System.out.println("Error: the first argument is the number to look for and should be from 0 to "
                            + (max_num_of_cards-1) + ".");
                    printUsage(1);
                }
            }
        }

        System.out.println("Let's start a round of magic tricks.\n");
        // create array with numbers from 0 to 14
        System.out.println("First, let's grab a bunch of cards:");

        int[] sorted_cards = generateArray(max_num_of_cards);
        System.out.println(prettyPrint(sorted_cards));

        System.out.println("\nNow, let's shuffle them:");
        // shuffle them
        int[] shuffled_cards = shuffleArray(sorted_cards);
        System.out.println(prettyPrint(shuffled_cards));

        System.out.println("\nWe're looking for the number '" + card_to_find + "'.");

        int linearPosition = linearSearch(shuffled_cards, card_to_find);
        if (linearPosition >= 0) {
            System.out.println("We've used linear search to determine the card"+
                    " was in the position " + linearPosition + ".");
        } else {
            System.out.println("We couldn't find the card, someone stole it!");
        }

        String word_to_check = "abca";
        if (args.length > 1) {
            word_to_check = args[1];
        }
        System.out.println("\nNow let's see if " + word_to_check + " is a palindrome:");
        if (isPalindromeRecursive(word_to_check) && isPalindromeIterative(word_to_check)) {
            System.out.println(word_to_check + " is definitely a palindrome.");
        } else {
            System.out.println(word_to_check + " is definitely not a palindrome.");
        }

        System.out.println("\nNext, let's factorize the number " + card_to_find + ":");
        int factorial_iter = factorialIterative(card_to_find);
        int factorial_rec  = factorialRecursive(card_to_find);
        if (factorial_iter == factorial_rec) {
            System.out.println("Using both iterative and recursive ways of "+
                    "factorializing, the result is " + factorial_iter + ".");
        } else {
            System.out.println("We messed up. iter: "+factorial_iter +
                    " rec: "+factorial_rec);
        }

        System.out.println("\nFinally, let's calculate the "+
                "same number as before but to the power of 3.");
        int exponential_iter = exponentialIterative(card_to_find, 3);
        int exponential_rec  = exponentialRecursive(card_to_find, 3);
        if (exponential_iter == exponential_rec) {
            System.out.println("Using both iterative and recursive ways of "+
                    "exponentiation, the result is " + exponential_iter);
        } else {
            System.out.println("We messed up. iterative gave us: "+exponential_iter +
                    " and recursive gave us: "+exponential_rec);
        }

    }

    private static int exponentialIterative (int num, int power) {
        if (power == 0) {
            return 1;
        }
        int result = num;
        while (power-- > 1) {
            result *= num;
        }
        return result;
    }

    private static int exponentialRecursive (int num, int power) {
        if (power == 0) {
            return 1;
        } else if (power == 1) {
            return num;
        } else {
            return num * exponentialRecursive(num, --power);
        }
    }

    private static int factorialIterative (int num) {
        int result = num;
        while (num > 1) {
            result *= --num;
        }
        return result;
    }

    private static int factorialRecursive (int num) {
        if (num == 1) {
            return 1;
        }
        return num * factorialRecursive(num-1);
    }

    private static boolean isPalindromeRecursive(String word) {
        if (word.length() < 2) {
            return true;
        }
        int start = 0;
        int end = word.length()-1;
        if (word.charAt(start) == word.charAt(end)) {
            return isPalindromeRecursive(word.substring(start+1, end));
        } else {
            return false;
        }
    }

    private static boolean isPalindromeIterative(String word) {
        boolean isPalindrome = true;
        for (int i=0; i<word.length()/2; i++) {
            String start = word.substring(i, i+1);
            String end = word.substring(word.length()-1-i);
            if (word.charAt(i) != word.charAt(word.length()-i-1)) {
                isPalindrome = false;
                break;
            }
        }
        return isPalindrome;
    }

    private static int linearSearch(int[] array, int num) {
        for (int i=0; i<array.length; i++) {
            if (array[i] == num) {
                return i;
            }
        }
        return -1;
    }

    private static int[] generateArray(int length) {
        int[] result = new int[length];
        for (int i = 0; i < length; i++) {
            result[i] = i;
        }
        return result;
    }

    private static int[] shuffleArray(int[] array) {
        int count = array.length;
        int[] result = copyArray(array);

        for (int i = 0; i < count; i++) {
            int j = (int) ((double)(count - i) * Math.random() / 2.0);
            swap(result, i, i+j);
        }
        return result;
    }

    private static void swap(int[] array, int left, int right) {
        int temp = array[left];
        array[left] = array[right];
        array[right] = temp;
    }

    private static int[] copyArray(int[] array) {
        int[] result = new int[array.length];
        for (int i=0; i<array.length; i++) {
            result[i] = array[i];
        }
        return result;
    }

    private static String prettyPrint(int[] array) {
        String result = "[ ";
        for (int i = 0; i < array.length - 1; i++) {
            result += array[i] + ", ";
        }
        return result + array[array.length - 1] + " ]";
    }

    private static void printUsage(int ret) {
        System.out.println("Usage: java -jar Algorithms.jar [number_to_look_for word_to_check_if_palindrome]");
        System.exit(ret);
    }
}
