/*
 * Copyright (C) 2015 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.tv.dvr.recorder;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.MainThread;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.android.tv.InputSessionManager;
import com.android.tv.InputSessionManager.OnRecordingSessionChangeListener;
import com.android.tv.R;
import com.android.tv.Starter;
import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.common.util.Clock;
import com.android.tv.dvr.WritableDvrDataManager;
import com.android.tv.util.RecurringRunner;

/**
 * DVR recording service. This service should be a foreground service and send a notification to
 * users to do long-running recording task.
 *
 * <p>This service is waken up when there's a scheduled recording coming soon and at boot completed
 * since schedules have to be loaded from databases in order to set new recording alarms, which
 * might take a long time.
 */
@RequiresApi(Build.VERSION_CODES.N)
public class DvrRecordingService extends Service {
    private static final String TAG = "DvrRecordingService";
    private static final boolean DEBUG = false;

    private static final String DVR_NOTIFICATION_CHANNEL_ID = "dvr_notification_channel";
    private static final int ONGOING_NOTIFICATION_ID = 1;
    @VisibleForTesting static final String EXTRA_START_FOR_RECORDING = "start_for_recording";

    private static DvrRecordingService sInstance;
    private NotificationChannel mNotificationChannel;
    private String mContentTitle;
    private String mContentTextRecording;
    private String mContentTextLoading;

    /**
     * Starts the service in foreground.
     *
     * @param startForRecording {@code true} if there are upcoming recordings in {@link
     *     RecordingScheduler#SOON_DURATION_IN_MS} and the service is started in foreground for
     *     those recordings.
     */
    @MainThread
    static void startForegroundService(Context context, boolean startForRecording) {
        if (sInstance == null) {
            Intent intent = new Intent(context, DvrRecordingService.class);
            intent.putExtra(EXTRA_START_FOR_RECORDING, startForRecording);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                context.startForegroundService(intent);
            } else {
                context.startService(intent);
            }
        } else {
            sInstance.startForeground(startForRecording);
        }
    }

    @MainThread
    static void stopForegroundIfNotRecording() {
        if (sInstance != null) {
            sInstance.stopForegroundIfNotRecordingInternal();
        }
    }

    private RecurringRunner mReaperRunner;
    private InputSessionManager mSessionManager;

    @VisibleForTesting boolean mIsRecording;
    private boolean mForeground;

    @VisibleForTesting
    final OnRecordingSessionChangeListener mOnRecordingSessionChangeListener =
            new OnRecordingSessionChangeListener() {
                @Override
                public void onRecordingSessionChange(final boolean create, final int count) {
                    mIsRecording = count > 0;
                    if (create) {
                        startForeground(true);
                    } else {
                        stopForegroundIfNotRecordingInternal();
                    }
                }
            };

    @Override
    public void onCreate() {
        Starter.start(this);
        if (DEBUG) Log.d(TAG, "onCreate");
        super.onCreate();
        SoftPreconditions.checkFeatureEnabled(this, CommonFeatures.DVR, TAG);
        sInstance = this;
        TvSingletons singletons = TvSingletons.getSingletons(this);
        WritableDvrDataManager dataManager =
                (WritableDvrDataManager) singletons.getDvrDataManager();
        mSessionManager = singletons.getInputSessionManager();
        mSessionManager.addOnRecordingSessionChangeListener(mOnRecordingSessionChangeListener);
        mReaperRunner =
                new RecurringRunner(
                        this,
                        java.util.concurrent.TimeUnit.DAYS.toMillis(1),
                        new ScheduledProgramReaper(dataManager, Clock.SYSTEM),
                        null);
        mReaperRunner.start();
        mContentTitle = getString(R.string.dvr_notification_content_title);
        mContentTextRecording = getString(R.string.dvr_notification_content_text_recording);
        mContentTextLoading = getString(R.string.dvr_notification_content_text_loading);
        createNotificationChannel();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (DEBUG) Log.d(TAG, "onStartCommand (" + intent + "," + flags + "," + startId + ")");
        if (intent != null) {
            startForeground(intent.getBooleanExtra(EXTRA_START_FOR_RECORDING, false));
        }
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        if (DEBUG) Log.d(TAG, "onDestroy");
        mReaperRunner.stop();
        mSessionManager.removeRecordingSessionChangeListener(mOnRecordingSessionChangeListener);
        sInstance = null;
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @VisibleForTesting
    protected void stopForegroundIfNotRecordingInternal() {
        if (mForeground && !mIsRecording) {
            stopForeground();
        }
    }

    private void startForeground(boolean hasUpcomingRecording) {
        if (!mForeground || hasUpcomingRecording) {
            // We may need to update notification for upcoming recordings.
            mForeground = true;
            startForegroundInternal(hasUpcomingRecording);
        }
    }

    private void stopForeground() {
        stopForegroundInternal();
        mForeground = false;
    }

    @VisibleForTesting
    protected void startForegroundInternal(boolean hasUpcomingRecording) {
        Notification.Builder builder =
                new Notification.Builder(this)
                        .setContentTitle(mContentTitle)
                        .setContentText(
                                hasUpcomingRecording ? mContentTextRecording : mContentTextLoading)
                        .setSmallIcon(R.drawable.ic_dvr);
        Notification notification =
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
                        ? builder.setChannelId(DVR_NOTIFICATION_CHANNEL_ID).build()
                        : builder.build();
        startForeground(ONGOING_NOTIFICATION_ID, notification);
    }

    @VisibleForTesting
    protected void stopForegroundInternal() {
        stopForeground(true);
    }

    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            mNotificationChannel =
                    new NotificationChannel(
                            DVR_NOTIFICATION_CHANNEL_ID,
                            getString(R.string.dvr_notification_channel_name),
                            NotificationManager.IMPORTANCE_LOW);
            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE))
                    .createNotificationChannel(mNotificationChannel);
        }
    }
}
