/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package androidx.leanback.leanbackshowcase.app.room.db.repo;

import androidx.lifecycle.LiveData;
import androidx.room.Room;
import android.os.AsyncTask;
import androidx.annotation.WorkerThread;
import androidx.leanback.leanbackshowcase.R;
import androidx.leanback.leanbackshowcase.app.room.controller.app.SampleApplication;
import androidx.leanback.leanbackshowcase.app.room.api.VideoDownloadingService;
import androidx.leanback.leanbackshowcase.app.room.api.VideosWithGoogleTag;
import androidx.leanback.leanbackshowcase.app.room.config.AppConfiguration;
import androidx.leanback.leanbackshowcase.app.room.db.AppDatabase;
import androidx.leanback.leanbackshowcase.app.room.db.dao.CategoryDao;
import androidx.leanback.leanbackshowcase.app.room.db.dao.VideoDao;
import androidx.leanback.leanbackshowcase.app.room.db.entity.CategoryEntity;
import androidx.leanback.leanbackshowcase.app.room.db.entity.VideoEntity;
import androidx.leanback.leanbackshowcase.utils.Utils;
import android.util.Log;

import com.google.gson.Gson;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Singleton;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

@Singleton
public class VideosRepository {

    // For debugging purpose
    private static final boolean DEBUG = false;
    private static final String TAG = "VideosRepository";

    // meta data type constant
    private static final String RENTED = "rented";
    private static final String STATUS = "status";
    private static final String CARD = "card";
    private static final String BACKGROUND = "background";
    private static final String VIDEO = "video";

    private static VideosRepository sVideosRepository;

    private AppDatabase mDb;
    private VideoDao mVideoDao;
    private CategoryDao mCategoryDao;

    // maintain the local cache so the live data can be shared among different components
    private Map<String, LiveData<List<VideoEntity>>> mVideoEntitiesCache;
    private LiveData<List<CategoryEntity>> mCategories;

    public static VideosRepository getVideosRepositoryInstance() {
        if (sVideosRepository == null) {
            sVideosRepository = new VideosRepository();
        }
        return sVideosRepository;
    }

    /**
     * View Model talks to repository through this method to fetch the live data.
     *
     * @param category category
     * @return The list of categories which is wrapped in a live data.
     */
    public LiveData<List<VideoEntity>> getVideosInSameCategoryLiveData(String category) {

        // always try to retrive from local cache firstly
        if (mVideoEntitiesCache.containsKey(category)) {
            return mVideoEntitiesCache.get(category);
        }
        LiveData<List<VideoEntity>> videoEntities = mVideoDao.loadVideoInSameCateogry(category);
        mVideoEntitiesCache.put(category, videoEntities);
        return videoEntities;
    }

    public LiveData<List<CategoryEntity>> getAllCategories() {

        if (mCategories == null) {
            mCategories = mCategoryDao.loadAllCategories();
        }
        return mCategories;
    }

    public LiveData<List<VideoEntity>> getSearchResult(String query) {
        return mVideoDao.searchVideos(query);
    }

    public LiveData<VideoEntity> getVideoById(Long id) {
        return mVideoDao.loadVideoById(id);
    }



    /**
     * Helper function to access the database and update the video information in the database.
     *
     * @param video    video entity
     * @param category which fields to update
     * @param value    updated value
     */
    @WorkerThread
    public synchronized void updateDatabase(VideoEntity video, String category, String value) {
        try {
            mDb.beginTransaction();
            switch (category) {
                case VIDEO:
                    video.setVideoLocalStorageUrl(value);
                    break;
                case BACKGROUND:
                    video.setVideoBgImageLocalStorageUrl(value);
                    break;
                case CARD:
                    video.setVideoCardImageLocalStorageUrl(value);
                    break;
                case STATUS:
                    video.setStatus(value);
                    break;
                case RENTED:
                    video.setRented(true);
                    break;
            }
            mDb.videoDao().updateVideo(video);
            mDb.setTransactionSuccessful();
        } finally {
            mDb.endTransaction();
        }
    }

    @Inject
    public VideosRepository() {
        createAndPopulateDatabase();
        mVideoDao = mDb.videoDao();
        mCategoryDao = mDb.categoryDao();
        mVideoEntitiesCache = new HashMap<>();
    }

    private void createAndPopulateDatabase() {
        mDb = Room.databaseBuilder(SampleApplication.getInstance(),
                AppDatabase.class, AppDatabase.DATABASE_NAME).build();

        // insert contents into database
        try {
            String url =
                    "https://storage.googleapis.com/android-tv/";
            initializeDb(mDb, url);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void initializeDb(AppDatabase db, String url) throws IOException {

        // json data
        String json;

        if (AppConfiguration.IS_DEBUGGING_VERSION) {

            // when use debugging version, we won't fetch data from network but using local
            // json file (only contain 4 video entities in 2 categories.)
            json = Utils.inputStreamToString(SampleApplication
                    .getInstance()
                    .getApplicationContext()
                    .getResources()
                    .openRawResource(R.raw.live_movie_debug));
            Gson gson = new Gson();
            VideosWithGoogleTag videosWithGoogleTag = gson.fromJson(json,
                    VideosWithGoogleTag.class);
            populateDatabase(videosWithGoogleTag,db);
        } else {
            buildDatabase(db, url);
        }
    }

    /**
     * Takes the contents of a JSON object and populates the database
     *
     * @param db Room database.
     */
    private static void buildDatabase(final AppDatabase db, String url) throws IOException {
        Retrofit retrofit = new Retrofit
                .Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        VideoDownloadingService service = retrofit.create(VideoDownloadingService.class);

        Call<VideosWithGoogleTag> videosWithGoogleTagCall = service.getVideosList();
        videosWithGoogleTagCall.enqueue(new Callback<VideosWithGoogleTag>() {
            @Override
            public void onResponse(Call<VideosWithGoogleTag> call, Response<VideosWithGoogleTag> response) {
                VideosWithGoogleTag videosWithGoogleTag = response.body();
                if (videosWithGoogleTag == null) {
                    Log.d(TAG, "onResponse: result is null");
                    return;
                }
                populateDatabase(videosWithGoogleTag, db);
            }

            @Override
            public void onFailure(Call<VideosWithGoogleTag> call, Throwable t) {
                Log.d(TAG, "Fail to download the content");
            }
        });

    }

    private static void populateDatabase(VideosWithGoogleTag videosWithGoogleTag, final AppDatabase db) {
        for (final VideosWithGoogleTag.VideosGroupByCategory videosGroupByCategory :
                videosWithGoogleTag.getAllResources()) {

            // create category table
            final CategoryEntity categoryEntity = new CategoryEntity();
            categoryEntity.setCategoryName(videosGroupByCategory.getCategory());

            // create video table with customization
            postProcessing(videosGroupByCategory);

            new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(Void... voids) {
                    try {
                        db.beginTransaction();
                        db.categoryDao().insertCategory(categoryEntity);
                        db.videoDao().insertAllVideos(videosGroupByCategory.getVideos());
                        db.setTransactionSuccessful();
                    } finally {
                        db.endTransaction();
                    }
                    return null;
                }
            }.execute();

        }
    }

    /**
     * Helper function to make some customization on raw data
     */
    private static void postProcessing(VideosWithGoogleTag.VideosGroupByCategory videosGroupByCategory) {
        for (VideoEntity each : videosGroupByCategory.getVideos()) {
            each.setCategory(videosGroupByCategory.getCategory());
            each.setVideoLocalStorageUrl("");
            each.setVideoBgImageLocalStorageUrl("");
            each.setVideoCardImageLocalStorageUrl("");
            each.setVideoUrl(each.getVideoUrls().get(0));
            each.setRented(false);
            each.setStatus("");
            each.setTrailerVideoUrl("https://storage.googleapis.com/android-tv/Sample%20videos/Google%2B/Google%2B_%20Say%20more%20with%20Hangouts.mp4");
        }
    }
}