package com.allendowney.thinkdast;

import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import org.jsoup.select.Elements;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

/**
 * Reprezentuje indeks wyszukiwania sieciowego obsługiwany przez Redisa.
 *
 */
public class JedisIndex {

   private Jedis jedis;

   /**
    * Konstruktor.
    *
    * @param jedis
    */
   public JedisIndex(Jedis jedis) {
      this.jedis = jedis;
   }

   /**
    * Zwraca klucz Redisa dla danego wyszukiwanego słowa.
    *
    * @return Redis key.
    */
   private String urlSetKey(String term) {
      return "URLSet:" + term;
   }

   /**
    * Zwraca klucz Redisa dla obiektu klasy TermCounter związanego z URL-em.
    *
    * @return Redis key.
    */
   private String termCounterKey(String url) {
      return "TermCounter:" + url;
   }

   /**
    * Sprawdza, czy dla danego URL-a mamy obiekt klasy TermCounter.
    *
    * @param url
    * @return
    */
   public boolean isIndexed(String url) {
      String redisKey = termCounterKey(url);
      return jedis.exists(redisKey);
   }
   
   /**
    * Dodaje URL do zbioru związanego ze słowem.
    * 
    * @param term
    * @param tc
    */
   public void add(String term, TermCounter tc) {
      jedis.sadd(urlSetKey(term), tc.getLabel());
   }

   /**
    * Odnajduje wyszukiwane słowo i zwraca zbiór URL-i.
    * 
    * @param term
    * @return Set of URLs.
    */
   public Set<String> getURLs(String term) {
        // TODO: UZUPEŁNIJ TEN KOD!
      return null;
   }

    /**
    * Odnajduje wyszukiwane słowo i zwraca mapę odworowującą URL na liczbę wystąpień.
    * 
    * @param term
    * @return obiekt typu Map odwzorowujący URL na licznik.
    */
   public Map<String, Integer> getCounts(String term) {
        // TODO: UZUPEŁNIJ TEN KOD!
      return null;
   }

    /**
    * Zwraca liczbę wystąpień podanego słowa pod danym URL-em.
    * 
    * @param url
    * @param term
    * @return
    */
   public Integer getCount(String url, String term) {
        // TODO: UZUPEŁNIJ TEN KOD!
      return null;
   }

   /**
    * Dodaje stronę do indeksu.
    *
    * @param url         URL strony.
    * @param paragraphs  Kolekcja elementów, które powinny zostać zindeksowane.
    */
   public void indexPage(String url, Elements paragraphs) {
      // TODO: UZUPEŁNIJ TEN KOD!
   }

   /**
    * Wyświetla zawartość indeksu.
    *
    * Metoda powinna być wykorzystywana w czasie programowania i testowania, nie w środowisku produkcyjnym.
    */
   public void printIndex() {
      // przejdź w pętli przez wyszukiwane słowa
      for (String term: termSet()) {
         System.out.println(term);

         // dla każdego słowa wyświetl strony, na których się ono pojawia
         Set<String> urls = getURLs(term);
         for (String url: urls) {
            Integer count = getCount(url, term);
            System.out.println("    " + url + " " + count);
         }
      }
   }

   /**
    * Zwraca zbiór słów, które zostały zindeksowane.
    *
    * Metoda powinna być wykorzystywana w czasie programowania i testowania, nie w środowisku produkcyjnym.
    *
    * @return
    */
   public Set<String> termSet() {
      Set<String> keys = urlSetKeys();
      Set<String> terms = new HashSet<String>();
      for (String key: keys) {
         String[] array = key.split(":");
         if (array.length < 2) {
            terms.add("");
         } else {
            terms.add(array[1]);
         }
      }
      return terms;
   }

   /**
    * Zwraca klucze URLSet dla słów, które zostały zindeksowane.
    *
    * Metoda powinna być wykorzystywana w czasie programowania i testowania, nie w środowisku produkcyjnym.
    *
    * @return
    */
   public Set<String> urlSetKeys() {
      return jedis.keys("URLSet:*");
   }

   /**
    * Zwraca klucze TermCounter dla URL-i, które zostały zindeksowane.
    *
    * Metoda powinna być wykorzystywana w czasie programowania i testowania, nie w środowisku produkcyjnym.
    *
    * @return
    */
   public Set<String> termCounterKeys() {
      return jedis.keys("TermCounter:*");
   }

   /**
    * Usuwa wszystkie obiekty URLSet z bazy danych.
    *
    * Metoda powinna być wykorzystywana w czasie programowania i testowania, nie w środowisku produkcyjnym.
    *
    * @return
    */
   public void deleteURLSets() {
      Set<String> keys = urlSetKeys();
      Transaction t = jedis.multi();
      for (String key: keys) {
         t.del(key);
      }
      t.exec();
   }

   /**
    * Usuwa wszystkie obiekty TermCounter z bazy danych.
    *
    * Metoda powinna być wykorzystywana w czasie programowania i testowania, nie w środowisku produkcyjnym.
    *
    * @return
    */
   public void deleteTermCounters() {
      Set<String> keys = termCounterKeys();
      Transaction t = jedis.multi();
      for (String key: keys) {
         t.del(key);
      }
      t.exec();
   }

   /**
    * Usuwa wszystkie klucze z bazy danych.
    *
    * Metoda powinna być wykorzystywana w czasie programowania i testowania, nie w środowisku produkcyjnym.
    *
    * @return
    */
   public void deleteAllKeys() {
      Set<String> keys = jedis.keys("*");
      Transaction t = jedis.multi();
      for (String key: keys) {
         t.del(key);
      }
      t.exec();
   }

   /**
    * @param args
    * @throws IOException
    */
   public static void main(String[] args) throws IOException {
      Jedis jedis = JedisMaker.make();
      JedisIndex index = new JedisIndex(jedis);

      //index.deleteTermCounters();
      //index.deleteURLSets();
      //index.deleteAllKeys();
      loadIndex(index);

      Map<String, Integer> map = index.getCounts("the");
      for (Entry<String, Integer> entry: map.entrySet()) {
         System.out.println(entry);
      }
   }

   /**
    * Zapisuje dwie strony w indeksie w celach testowych.
    *
    * @return
    * @throws IOException
    */
   private static void loadIndex(JedisIndex index) throws IOException {
      WikiFetcher wf = new WikiFetcher();

      String url = "https://en.wikipedia.org/wiki/Java_(programming_language)";
      Elements paragraphs = wf.readWikipedia(url);
      index.indexPage(url, paragraphs);

      url = "https://en.wikipedia.org/wiki/Programming_language";
      paragraphs = wf.readWikipedia(url);
      index.indexPage(url, paragraphs);
   }
}