package com.manning.aip.mymoviesdatabase.data;

import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.SystemClock;
import android.util.Log;

import com.manning.aip.mymoviesdatabase.Constants;
import com.manning.aip.mymoviesdatabase.data.MovieTable.MovieColumns;
import com.manning.aip.mymoviesdatabase.model.Category;
import com.manning.aip.mymoviesdatabase.model.Movie;

import java.util.List;

/**
 * Klasa DataManagerImpl z operacjami SQL-a i bazodanowymi.
 * Obejmuje klasę SQLiteOpenHelper. Używamy tu obiektów DAO
 * do tworzenia, aktualizowania i usuwania danych oraz 
 * manipulowania nimi na inne sposoby.
 *
 * @author ccollins
 *
 */
public class DataManagerImpl implements DataManager {

   private Context context;

   private SQLiteDatabase db;

   private CategoryDao categoryDao;
   private MovieDao movieDao;
   private MovieCategoryDao movieCategoryDao;

   public DataManagerImpl(Context context) {

      this.context = context;

      SQLiteOpenHelper openHelper = new OpenHelper(this.context);
      db = openHelper.getWritableDatabase();
      Log.i(Constants.LOG_TAG, "Utworzono obiekt klasy DataManagerImpl. Stan bazy: " + db.isOpen());

      categoryDao = new CategoryDao(db);
      movieDao = new MovieDao(db);
      movieCategoryDao = new MovieCategoryDao(db);
   }

   public SQLiteDatabase getDb() {
      return db;
   }

   private void openDb() {
      if (!db.isOpen()) {
         db = SQLiteDatabase.openDatabase(DataConstants.DATABASE_PATH, null, SQLiteDatabase.OPEN_READWRITE);
         // Ponieważ przekazujemy bazę do obiektu DAO, trzeba odtworzyć obiekt DAO 
         // po ponownym otwarciu bazy.
         categoryDao = new CategoryDao(db);
         movieDao = new MovieDao(db);
         movieCategoryDao = new MovieCategoryDao(db);
      }
   }

   private void closeDb() {
      if (db.isOpen()) {
         db.close();
      }
   }

   private void resetDb() {
      Log.i(Constants.LOG_TAG, "Resetowanie połączenia z bazą (zamknięcie i ponowne otwarcie).");
      closeDb();
      SystemClock.sleep(500);
      openDb();
   }

   //
   // Nakładki na obiekty DAO (metody do zarządzania danymi).
   //
   // Pozwalają ukryć, że korzystamy z obiektów DAO. Udostępniane są tylko metody używane w 
   // aplikacji. Zastosowane rozwiązanie pozwala łączyć obiekty DAO, a logikę działania 
   // programu umieścić w innym miejscu.
   //  

   // Film.
   @Override
   public Movie getMovie(long movieId) {
      Movie movie = movieDao.get(movieId);
      if (movie != null) {
         movie.getCategories().addAll(movieCategoryDao.getCategories(movie.getId()));
      }
      return movie;
   }

   @Override
   public List<Movie> getMovieHeaders() {      
      return movieDao.getAll();
   }

   @Override
   public Movie findMovie(String name) {
      Movie movie = movieDao.find(name);
      if (movie != null) {
         movie.getCategories().addAll(movieCategoryDao.getCategories(movie.getId()));
      }
      return movie;
   }

   @Override
   public long saveMovie(Movie movie) {
	  // UWAGA: można też umieścić funkcje do manipulowania encjami w klasie DataManagerImpl i  
	  // utworzyć "menedżera" dla każdej encji. Tu jednak w celu uproszczenia rozwiązania
	  // bezpośrednio używamy obiektów DAO (także przy manipulowaniu encjami).
      long movieId = 0L;

      // Operacje wykonujemy w ramach transakcji (z uwagi na stosowanie kilku tabel).
      try {
         db.beginTransaction();

         // Najpierw zapisujemy film.                               
         movieId = movieDao.save(movie);

         // Następnie sprawdzamy, czy kategoria istnieje, i zapisujemy powiązanie 
         // filmu z kategorią. Wymaga to kilku zapytań, jednak kategorii zwykle jest mało.
         // Można też zapisać dane i przechwytywać wyjątki, jednak byłoby to nieeleganckie.
         if (movie.getCategories().size() > 0) {
            for (Category c : movie.getCategories()) {
               long catId = 0L;
               Category dbCat = categoryDao.find(c.getName());
               if (dbCat == null) {
                  catId = categoryDao.save(c);
               } else {
                  catId = dbCat.getId();
               }
               MovieCategoryKey mcKey = new MovieCategoryKey(movieId, catId);
               if (!movieCategoryDao.exists(mcKey)) {
                  movieCategoryDao.save(mcKey);
               }
            }
         }

         db.setTransactionSuccessful();
      } catch (SQLException e) {
         Log.e(Constants.LOG_TAG, "Błąd zapisu filmu (transakcję wycofano)", e);
         movieId = 0L;
      } finally {
         // "Nazwa zastępcza" dla polecenia commit
         db.endTransaction();
      }

      return movieId;
   }

   @Override
   public boolean deleteMovie(long movieId) {
      boolean result = false;
      // UWAGA: zmień kolejność instrukcji, aby zobaczyć naruszenie ograniczeń 
      // (z uwagi na klucze obce).
      try {
         db.beginTransaction();
         // Użyj metody getMovie zamiast bezpośrednio movieDao (ponieważ trzeba uwzględnić kategorie)
         Movie movie = getMovie(movieId);
         if (movie != null) {
            for (Category c : movie.getCategories()) {
               movieCategoryDao.delete(new MovieCategoryKey(movie.getId(), c.getId()));
            }
            movieDao.delete(movie);
         }
         db.setTransactionSuccessful();
         result = true;
      } catch (SQLException e) {
         Log.e(Constants.LOG_TAG, "Błąd przy usuwaniu filmu (transakcję wycofano)", e);
      } finally {
         db.endTransaction();
      }
      return result;
   }

   @Override
   public Cursor getMovieCursor() {
      // Zauważ, że w zapytaniu MUSI występować kolumna _id
      return db.rawQuery("select " + MovieColumns._ID + ", " + MovieColumns.NAME + ", " + MovieColumns.THUMB_URL
               + " from " + MovieTable.TABLE_NAME, null);
   }

   // Kategoria
   @Override
   public Category getCategory(long categoryId) {
      return categoryDao.get(categoryId);
   }

   @Override
   public List<Category> getAllCategories() {
      return categoryDao.getAll();
   }

   @Override
   public Category findCategory(String name) {
      return categoryDao.find(name);
   }

   @Override
   public long saveCategory(Category category) {
      return categoryDao.save(category);
   }

   @Override
   public void deleteCategory(Category category) {
      categoryDao.delete(category);
   }

}