/*
 * Copyright 2018 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 com.android.pump.db;

import android.content.ContentResolver;
import android.content.ContentUris;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;

import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;

import com.android.pump.provider.Query;
import com.android.pump.util.Clog;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;

@WorkerThread
class VideoStore extends ContentObserver {
    private static final String TAG = Clog.tag(VideoStore.class);

    // TODO Replace the following with MediaStore.Video.Media.RELATIVE_PATH throughout the code.
    private static final String RELATIVE_PATH = "relative_path";

    // TODO Replace with Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q throughout the code.
    private static boolean isAtLeastRunningQ() {
        return Build.VERSION.SDK_INT > Build.VERSION_CODES.P
                || (Build.VERSION.SDK_INT == Build.VERSION_CODES.P
                && Build.VERSION.PREVIEW_SDK_INT > 0);
    }

    private final ContentResolver mContentResolver;
    private final ChangeListener mChangeListener;
    private final MediaProvider mMediaProvider;

    interface ChangeListener {
        void onMoviesAdded(@NonNull Collection<Movie> movies);
        void onSeriesAdded(@NonNull Collection<Series> series);
        void onEpisodesAdded(@NonNull Collection<Episode> episodes);
        void onOthersAdded(@NonNull Collection<Other> others);
    }

    @AnyThread
    VideoStore(@NonNull ContentResolver contentResolver, @NonNull ChangeListener changeListener,
            @NonNull MediaProvider mediaProvider) {
        super(null);

        Clog.i(TAG, "VideoStore(" + contentResolver + ", " + changeListener
                + ", " + mediaProvider + ")");
        mContentResolver = contentResolver;
        mChangeListener = changeListener;
        mMediaProvider = mediaProvider;

        // TODO(b/123706961) Do we need content observer for other content uris? (E.g. thumbnail)
        mContentResolver.registerContentObserver(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                true, this);

        // TODO(b/123706961) When to call unregisterContentObserver?
        // mContentResolver.unregisterContentObserver(this);
    }

    void load() {
        Clog.i(TAG, "load()");
        Collection<Movie> movies = new ArrayList<>();
        Collection<Series> series = new ArrayList<>();
        Collection<Episode> episodes = new ArrayList<>();
        Collection<Other> others = new ArrayList<>();

        /* TODO get via count instead?
                Cursor countCursor = mContentResolver.query(CONTENT_URI,
                new String[] { "count(*) AS count" },
                null,
                null,
                null);
        countCursor.moveToFirst();
        int count = countCursor.getInt(0);
        Clog.i(TAG, "count = " + count);
        countCursor.close();
        */

        {
            Uri contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            String[] projection;
            if (isAtLeastRunningQ()) {
                projection = new String[] {
                    MediaStore.Video.Media._ID,
                    MediaStore.Video.Media.MIME_TYPE,
                    RELATIVE_PATH,
                    MediaStore.Video.Media.DISPLAY_NAME
                };
            } else {
                projection = new String[] {
                    MediaStore.Video.Media._ID,
                    MediaStore.Video.Media.MIME_TYPE,
                    MediaStore.Video.Media.DATA
                };
            }
            String sortOrder = MediaStore.Video.Media._ID;
            Cursor cursor = mContentResolver.query(contentUri, projection, null, null, sortOrder);
            if (cursor != null) {
                try {
                    int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
                    int dataColumn;
                    int relativePathColumn;
                    int displayNameColumn;
                    int mimeTypeColumn = cursor.getColumnIndexOrThrow(
                            MediaStore.Video.Media.MIME_TYPE);

                    if (isAtLeastRunningQ()) {
                        dataColumn = -1;
                        relativePathColumn = cursor.getColumnIndexOrThrow(RELATIVE_PATH);
                        displayNameColumn = cursor.getColumnIndexOrThrow(
                                MediaStore.Video.Media.DISPLAY_NAME);
                    } else {
                        dataColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA);
                        relativePathColumn = -1;
                        displayNameColumn = -1;
                    }

                    for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
                        long id = cursor.getLong(idColumn);
                        String mimeType = cursor.getString(mimeTypeColumn);

                        File file;
                        if (isAtLeastRunningQ()) {
                            String relativePath = cursor.getString(relativePathColumn);
                            String displayName = cursor.getString(displayNameColumn);
                            file = new File(relativePath, displayName);
                        } else {
                            String data = cursor.getString(dataColumn);
                            file = new File(data);
                        }
                        Query query = Query.parse(Uri.fromFile(file));
                        if (query.isMovie()) {
                            Movie movie;
                            if (query.hasYear()) {
                                movie = new Movie(id, mimeType, query.getName(), query.getYear());
                            } else {
                                movie = new Movie(id, mimeType, query.getName());
                            }
                            movies.add(movie);
                        } else if (query.isEpisode()) {
                            Series serie = null;
                            for (Series s : series) {
                                if (s.getTitle().equals(query.getName())
                                        && s.hasYear() == query.hasYear()
                                        && (!s.hasYear() || s.getYear() == query.getYear())) {
                                    serie = s;
                                    break;
                                }
                            }
                            if (serie == null) {
                                if (query.hasYear()) {
                                    serie = new Series(query.getName(), query.getYear());
                                } else {
                                    serie = new Series(query.getName());
                                }
                                series.add(serie);
                            }

                            Episode episode = new Episode(id, mimeType, serie,
                                    query.getSeason(), query.getEpisode());
                            episodes.add(episode);

                            serie.addEpisode(episode);
                        } else {
                            Other other = new Other(id, mimeType, query.getName());
                            others.add(other);
                        }
                    }
                } finally {
                    cursor.close();
                }
            }
        }

        mChangeListener.onMoviesAdded(movies);
        mChangeListener.onSeriesAdded(series);
        mChangeListener.onEpisodesAdded(episodes);
        mChangeListener.onOthersAdded(others);
    }

    boolean loadData(@NonNull Movie movie) {
        Uri thumbnailUri = getThumbnailUri(movie.getId());
        if (thumbnailUri != null) {
            return movie.setThumbnailUri(thumbnailUri);
        }
        return false;
    }

    boolean loadData(@NonNull Series series) {
        return false;
    }

    boolean loadData(@NonNull Episode episode) {
        Uri thumbnailUri = getThumbnailUri(episode.getId());
        if (thumbnailUri != null) {
            return episode.setThumbnailUri(thumbnailUri);
        }
        return false;
    }

    boolean loadData(@NonNull Other other) {
        boolean updated = false;

        Uri contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
        String[] projection = {
            MediaStore.Video.Media.TITLE,
            MediaStore.Video.Media.DURATION,
            MediaStore.Video.Media.DATE_TAKEN,
            MediaStore.Video.Media.LATITUDE,
            MediaStore.Video.Media.LONGITUDE
        };
        String selection = MediaStore.Video.Media._ID + " = ?";
        String[] selectionArgs = { Long.toString(other.getId()) };
        Cursor cursor = mContentResolver.query(
                contentUri, projection, selection, selectionArgs, null);
        if (cursor != null) {
            try {
                int titleColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.TITLE);
                int durationColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION);
                int dateTakenColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATE_TAKEN);
                int latitudeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.LATITUDE);
                int longitudeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.LONGITUDE);

                if (cursor.moveToFirst()) {
                    if (!cursor.isNull(titleColumn)) {
                        String title = cursor.getString(titleColumn);
                        updated |= other.setTitle(title);
                    }
                    if (!cursor.isNull(durationColumn)) {
                        long duration = cursor.getLong(durationColumn);
                        updated |= other.setDuration(duration);
                    }
                    if (!cursor.isNull(dateTakenColumn)) {
                        long dateTaken = cursor.getLong(dateTakenColumn);
                        updated |= other.setDateTaken(dateTaken);
                    }
                    if (!cursor.isNull(latitudeColumn) && !cursor.isNull(longitudeColumn)) {
                        double latitude = cursor.getDouble(latitudeColumn);
                        double longitude = cursor.getDouble(longitudeColumn);
                        updated |= other.setLatLong(latitude, longitude);
                    }
                }
            } finally {
                cursor.close();
            }
        }

        Uri thumbnailUri = getThumbnailUri(other.getId());
        if (thumbnailUri != null) {
            updated |= other.setThumbnailUri(thumbnailUri);
        }

        return updated;
    }

    private @Nullable Uri getThumbnailUri(long id) {
        // TODO(b/130363861) No need to store the URI -- generate when requested instead
        return ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id)
                .buildUpon().appendPath("thumbnail").build();
    }

    @Override
    public void onChange(boolean selfChange) {
        Clog.i(TAG, "onChange(" + selfChange + ")");
        onChange(selfChange, null);
    }

    @Override
    public void onChange(boolean selfChange, @Nullable Uri uri) {
        Clog.i(TAG, "onChange(" + selfChange + ", " + uri + ")");
        // TODO(b/123706961) Figure out what changed
        // onChange(false, content://media)
        // onChange(false, content://media/external)
        // onChange(false, content://media/external/audio/media/444)
        // onChange(false, content://media/external/video/media/328?blocking=1&orig_id=328&group_id=0)

        // TODO(b/123706961) Notify listener about changes
        // mChangeListener.xxx();
    }
}