import java.io.*;
import java.util.*;
// Name: Niklas Johnson
// Date: 4-2-2024

// This class allows users to find and rate books within BOOK_DIRECTORY
// containing certain terms. Also handles omitting items if the query is prefixed
// with a '-'
public class SearchClient {
    public static final String BOOK_DIRECTORY = "./books";

    public static void main(String[] args) throws FileNotFoundException {
        Scanner console = new Scanner(System.in);
        List<Book> books = loadBooks();
        List<Media> media = new ArrayList<>(books);

        Map<String, Set<Media>> index = createIndex(media);

        System.out.println("Welcome to the CSE 123 Search Engine!");
        String command = "";
        while (!command.equalsIgnoreCase("quit")) {
            System.out.println("What would you like to do? [Search, Rate, Quit]");
            System.out.print("> ");
            command = console.nextLine();

            if (command.equalsIgnoreCase("search")) {
                searchQuery(console, index);
            } else if (command.equalsIgnoreCase("rate")) {
                addRating(console, media);
            } else if (!command.equalsIgnoreCase("quit")) {
                System.out.println("Invalid command, please try again.");
            }
        }
        System.out.println("See you next time!");
    }

    // Produces and returns a "deep copy" of the parameter map, which has the same
    //   structure and values as the parameter, but with all internal data structures
    //   and values copied. After calling this method, modifying the parameter or 
    //   return value should NOT affect the other.
    //
    // Parameters:
    //   inputMap - the map to duplicate
    //
    // Returns:
    //   A deep copy of the parameter map.
    // TODO: Copy fixed version and update to use Media
    public static Map<String, Set<Media>> deepCopy(Map<String, Set<Media>> inputMap) {
        Map<String, Set<Media>> deepCopy = new TreeMap<>();

        for (String key : inputMap.keySet()) {
            Set<Media> inputSet = new HashSet<>(inputMap.get(key));
            deepCopy.put(key, inputSet);
        }
        return deepCopy;
    }
    // creates a directory of words that appear in a given list of Strings
    // takes a list of strings to process
    // returns sorted map containing the index
    // no thrown exceptions
    public static Map<String, Set<Media>> createIndex(List<Media> media) throws FileNotFoundException {
        Map<String, Set<Media>> index = new TreeMap<>();
        Set<String> uniqueWords = getUniqueWords(media);

        for (String uniqueWord : uniqueWords) {
            index.put(uniqueWord.toLowerCase(), new HashSet<Media>());
        }

        for (String word : index.keySet()) {
            for (int i = 0; i < media.size(); i++) {
                Scanner titleScanner = new Scanner(media.get(i).getTitle());
                while (titleScanner.hasNext()) {
                    if (titleScanner.next().equalsIgnoreCase(word)) {
                        index.get(word).add(media.get(i));
                    }
                }
                titleScanner.close();

                for (String artists : media.get(i).getArtists()) {
                    Scanner artistScanner = new Scanner(artists);
                    while (artistScanner.hasNext()) {
                        if (artistScanner.next().equalsIgnoreCase(word)) {
                            index.get(word).add(media.get(i));
                        }
                    }
                    artistScanner.close();
                }
                

            }
        }
        return index;
    }

    // helper method for createIndex. looks through given list of strings,
    // keeping track of only unique words
    // takes list of words to prune
    // returns pruned set of words
    // no thrown exceptions
    public static Set<String> getUniqueWords(List<Media> media) throws FileNotFoundException {
        Set<String> uniqueWords = new HashSet<>();
        for (Media thing : media) {
            Scanner mediaScanner = new Scanner(new File(BOOK_DIRECTORY + "/" + thing.getTitle() + ".txt"));
            while (mediaScanner.hasNext()) {
                uniqueWords.add(mediaScanner.next());
            }
            mediaScanner.close();
        }
        return uniqueWords;
    }

    // Allows the user to search a specific query using the provided 'index' to find appropraite
    //  Media entries.
    //
    // Parameters:
    //   console - the Scanner to get user input from
    //   index - invertedIndex mapping terms to the Set of media containing those terms
    public static void searchQuery(Scanner console, Map<String, Set<Media>> index) {
        System.out.println("Enter query:");
        System.out.print("> ");
        String query = console.nextLine();
        Optional<Set<Media>> result = search(deepCopy(index), query);
        
        if (result.isEmpty()) {
            System.out.println("\tNo results!");
        } else {
            for (Media m : result.get()) {
                System.out.println("\t" + m.toString());
            }
        }
    }

    // Allows the user to add a rating to one of the options wthin 'media'
    //
    // Parameters:
    //   console - the Scanner to get user input from
    //   media - list of all media options loaded into the search engine
    public static void addRating(Scanner console, List<Media> media) {
        System.out.print("[" + media.get(0).getTitle());
        for (int i = 1; i < media.size(); i++) {
            System.out.print(", " + media.get(i).getTitle());
        }
        System.out.println("]");
        System.out.println("What would you like to rate (enter index)?");
        System.out.print("> ");
        int choice = Integer.parseInt(console.nextLine());
        if (choice < 0 || choice >= media.size()) {
            System.out.println("Invalid choice");
        } else {
            System.out.println("Rating [" + media.get(choice).getTitle() + "]");
            System.out.println("What rating would you give?");
            System.out.print("> ");
            int rating = Integer.parseInt(console.nextLine());
            media.get(choice).addRating(rating);
        }
    }

    // Searches a specific query using the provided 'index' to find appropraite Media entries.
    //  terms are determined by whitespace separation. If a term is proceeded by '-' any entry
    //  containing that term will be removed from the result.
    //
    // Parameters:
    //   index - invertedIndex mapping terms to the Set of media containing those terms
    //   query - user's entered query string to use in searching
    //
    // Returns:
    //   An optional set of all Media containing the requirested terms. If none, Optional.Empty()
    public static Optional<Set<Media>> search(Map<String, Set<Media>> index, String query) {
        Optional<Set<Media>> ret = Optional.empty();

        Scanner tokens = new Scanner(query);
        while (tokens.hasNext()) {
            boolean minus = false;
            String token = tokens.next().toLowerCase();
            if (token.startsWith("-")) {
                minus = true;
                token = token.substring(1);
            }

            if (index.containsKey(token)) {
                if (ret.isEmpty() && !minus) {
                    ret = Optional.of(index.get(token));
                } else if (!ret.isEmpty() && minus) {
                    ret.get().removeAll(index.get(token));
                } else if (!ret.isEmpty() && !minus) {
                    ret.get().retainAll(index.get(token));
                }
            }
        }
        return ret;
    }

    // Loads all books from BOOK_DIRECTORY. Assumes that each book starts with two lines -
    //      "Title: " which is followed by the book's title
    //      "Author: " which is followed by the book's author
    //
    // Returns:
    //   A list of all book objects corresponding to the ones located in BOOK_DIRECTORY
    public static List<Book> loadBooks() throws FileNotFoundException {
        List<Book> ret = new ArrayList<>();
        
        File dir = new File(BOOK_DIRECTORY);
        for (File f : dir.listFiles()) {
            Scanner sc = new Scanner(f);
            String title = sc.nextLine().substring("Title: ".length());
            String author = sc.nextLine().substring("Author: ".length());

            ret.add(new Book(title, author));
        }

        return ret;
    }
}
