/*
 * Copyright (c) 2017 Google Inc.
 *
 *  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 @APP_PACKAGE@.channels.util;

import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable;
import android.media.tv.TvContract;
import android.net.Uri;
import android.os.PersistableBundle;
import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
import android.support.media.tv.Channel;
import android.support.media.tv.ChannelLogoUtils;
import android.support.media.tv.TvContractCompat;
import android.util.Log;

import @APP_PACKAGE@.Splash;
import @APP_PACKAGE@.channels.SyncChannelJobService;
import @APP_PACKAGE@.channels.SyncProgramsJobService;
import @APP_PACKAGE@.channels.model.Subscription;

/**
 * Manages interactions with the TV Provider.
 */
public class TvUtil
{

  private static final String TAG = "TvUtil";
  private static final int CHANNEL_JOB_ID = 500;
  private static final int CHANNEL_TRIGGERED_JOB_ID_OFFSET = 1000;
  private static final int CHANNEL_TIMED_JOB_ID_OFFSET = 2000;
  private static final int CHANNEL_IMMEDIATE_JOB_ID_OFFSET = 3000;

  private static final String[] CHANNELS_PROJECTION = {
          TvContractCompat.Channels._ID,
          TvContract.Channels.COLUMN_DISPLAY_NAME,
          TvContractCompat.Channels.COLUMN_BROWSABLE
  };

  /**
   * Converts a {@link Subscription} into a {@link Channel} and adds it to the tv provider.
   *
   * @param context      used for accessing a content resolver.
   * @param subscription to be converted to a channel and added to the tv provider.
   * @return the id of the channel that the tv provider returns.
   */
  @WorkerThread
  public static long createChannel(Context context, Subscription subscription)
  {
    // Checks if our subscription has been added to the channels before.
    Cursor cursor =
            context.getContentResolver()
                    .query(
                            TvContractCompat.Channels.CONTENT_URI,
                            CHANNELS_PROJECTION,
                            null,
                            null,
                            null);
    if (cursor != null && cursor.moveToFirst())
    {
      do
      {
        Channel channel = Channel.fromCursor(cursor);
        if (subscription.getName().equals(channel.getDisplayName()))
        {
          Log.d(
                  TAG,
                  "Channel already exists. Returning channel "
                          + channel.getId()
                          + " from TV Provider.");
          return channel.getId();
        }
      } while (cursor.moveToNext());
    }

    Intent playlistIntent = new Intent(context, Splash.class);
    if (subscription.getUri().isEmpty())
    {
      playlistIntent.setAction(Intent.ACTION_VIEW);
    }
    else
    {
      playlistIntent.setAction(Intent.ACTION_GET_CONTENT);
      playlistIntent.setData(Uri.parse(Uri.parse(subscription.getUri()).getFragment()));
    }

    Channel.Builder builder = new Channel.Builder();
    builder.setType(TvContractCompat.Channels.TYPE_PREVIEW)
            .setDisplayName(subscription.getName())
            .setAppLinkIntent(playlistIntent);

    Log.d(TAG, "Creating channel: " + subscription.getName());
    Uri channelUrl =
            context.getContentResolver()
                    .insert(
                            TvContractCompat.Channels.CONTENT_URI,
                            builder.build().toContentValues());

    Log.d(TAG, "channel insert at " + channelUrl);
    long channelId = ContentUris.parseId(channelUrl);
    Log.d(TAG, "channel id " + channelId);

    Bitmap bitmap = convertToBitmap(context, subscription.getChannelLogo());
    ChannelLogoUtils.storeChannelLogo(context, channelId, bitmap);

    return channelId;
  }

  public static int getNumberOfChannels(Context context)
  {
    Cursor cursor =
            context.getContentResolver()
                    .query(
                            TvContractCompat.Channels.CONTENT_URI,
                            CHANNELS_PROJECTION,
                            null,
                            null,
                            null);
    return cursor != null ? cursor.getCount() : 0;
  }

  /**
   * Converts a resource into a {@link Bitmap}. If the resource is a vector drawable, it will be
   * drawn into a new Bitmap. Otherwise the {@link BitmapFactory} will decode the resource.
   *
   * @param context    used for getting the drawable from resources.
   * @param resourceId of the drawable.
   * @return a bitmap of the resource.
   */
  @NonNull
  public static Bitmap convertToBitmap(Context context, int resourceId)
  {
    Drawable drawable = context.getDrawable(resourceId);
    if (drawable instanceof VectorDrawable)
    {
      Bitmap bitmap =
              Bitmap.createBitmap(
                      drawable.getIntrinsicWidth(),
                      drawable.getIntrinsicHeight(),
                      Bitmap.Config.ARGB_8888);
      Canvas canvas = new Canvas(bitmap);
      drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
      drawable.draw(canvas);
      return bitmap;
    }

    return BitmapFactory.decodeResource(context.getResources(), resourceId);
  }

  /**
   * Schedules syncing channels via a {@link JobScheduler}.
   *
   * @param context for accessing the {@link JobScheduler}.
   */
  public static void scheduleSyncingChannel(Context context)
  {
    JobScheduler scheduler =
            (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
    if (scheduler.getPendingJob(CHANNEL_JOB_ID) != null)
      return;

    ComponentName componentName = new ComponentName(context, SyncChannelJobService.class);
    JobInfo.Builder builder = new JobInfo.Builder(CHANNEL_JOB_ID, componentName);
    builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
    builder.setMinimumLatency(10000);

    Log.d(TAG, "Scheduled channel creation.");
    scheduler.schedule(builder.build());
  }

  /**
   * Schedulers syncing programs for a channel. The scheduler will listen to a {@link Uri} for a
   * particular channel.
   *
   * @param context   for accessing the {@link JobScheduler}.
   * @param channelId for the channel to listen for changes.
   */
  public static void scheduleTriggeredSyncingProgramsForChannel(Context context, long channelId)
  {
    JobScheduler scheduler =
            (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
    ComponentName componentName = new ComponentName(context, SyncProgramsJobService.class);

    JobInfo.Builder builder =
            new JobInfo.Builder(getTriggeredJobIdForChannelId(channelId), componentName);

    JobInfo.TriggerContentUri triggerContentUri =
            new JobInfo.TriggerContentUri(
                    TvContractCompat.buildChannelUri(channelId),
                    JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS);
    builder.addTriggerContentUri(triggerContentUri);
    builder.setTriggerContentMaxDelay(0L);
    builder.setTriggerContentUpdateDelay(0L);

    PersistableBundle bundle = new PersistableBundle();
    bundle.putLong(TvContractCompat.EXTRA_CHANNEL_ID, channelId);
    builder.setExtras(bundle);

    scheduler.cancel(getTriggeredJobIdForChannelId(channelId));
    scheduler.schedule(builder.build());
  }

  /**
   * Schedulers syncing programs for a channel on a time base. The scheduler will listen to a {@link Uri} for a
   * particular channel.
   *
   * @param context   for accessing the {@link JobScheduler}.
   * @param channelId for the channel to listen for changes.
   */
  public static void scheduleTimedSyncingProgramsForChannel(Context context, long channelId)
  {
    JobScheduler scheduler =
            (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
    if (scheduler.getPendingJob(getTimedJobIdForChannelId(channelId)) != null)
      return;

    ComponentName componentName = new ComponentName(context, SyncProgramsJobService.class);

    JobInfo.Builder builder =
            new JobInfo.Builder(getTimedJobIdForChannelId(channelId), componentName);
    builder.setPeriodic(1800000);

    PersistableBundle bundle = new PersistableBundle();
    bundle.putLong(TvContractCompat.EXTRA_CHANNEL_ID, channelId);
    builder.setExtras(bundle);

    JobInfo job = builder.build();
    Log.d(TAG, "scheduleTimedSyncingProgramsForChannel: minperiod=" + job.getMinPeriodMillis());

    scheduler.schedule(job);
  }

  /**
   * Schedulers syncing programs for a channel on a time base. The scheduler will listen to a {@link Uri} for a
   * particular channel.
   *
   * @param context   for accessing the {@link JobScheduler}.
   * @param channelId for the channel to listen for changes.
   */
  public static void scheduleSyncingProgramsForChannel(Context context, long channelId)
  {
    JobScheduler scheduler =
            (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
    if (scheduler.getPendingJob(getImmediateJobIdForChannelId(channelId)) != null)
      return;

    ComponentName componentName = new ComponentName(context, SyncProgramsJobService.class);

    JobInfo.Builder builder =
            new JobInfo.Builder(getImmediateJobIdForChannelId(channelId), componentName);
    builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);

    PersistableBundle bundle = new PersistableBundle();
    bundle.putLong(TvContractCompat.EXTRA_CHANNEL_ID, channelId);
    builder.setExtras(bundle);

    scheduler.schedule(builder.build());
  }

  public static int getTriggeredJobIdForChannelId(long channelId)
  {
    return (int) (CHANNEL_TRIGGERED_JOB_ID_OFFSET + channelId);
  }
  public static int getTimedJobIdForChannelId(long channelId)
  {
    return (int) (CHANNEL_TIMED_JOB_ID_OFFSET + channelId);
  }
  public static int getImmediateJobIdForChannelId(long channelId)
  {
    return (int) (CHANNEL_IMMEDIATE_JOB_ID_OFFSET + channelId);
  }
}
