/*
 * 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.testing.data;

import android.content.Context;
import android.database.Cursor;
import android.media.tv.TvContentRating;
import android.media.tv.TvContract;
import com.android.tv.testing.R;
import com.android.tv.testing.utils.Utils;
import com.google.common.collect.ImmutableList;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

public final class ProgramInfo {
    /** If this is specify for title, it will be generated by adding index. */
    public static final String GEN_TITLE = "";

    /**
     * If this is specify for episode title, it will be generated by adding index. Also, season and
     * episode numbers would be generated, too. see: {@link #build} for detail.
     */
    public static final String GEN_EPISODE = "";

    private static final int SEASON_MAX = 10;
    private static final int EPISODE_MAX = 12;

    /**
     * If this is specify for poster art, it will be selected one of {@link #POSTER_ARTS_RES} in
     * order.
     */
    public static final String GEN_POSTER = "GEN";

    private static final int[] POSTER_ARTS_RES = {
        0,
        R.drawable.blue,
        R.drawable.red_large,
        R.drawable.green,
        R.drawable.red,
        R.drawable.green_large,
        R.drawable.blue_small
    };

    /**
     * If this is specified for duration, it will be selected one of {@link #DURATIONS_MS} in order.
     */
    public static final int GEN_DURATION = -1;

    private static final long[] DURATIONS_MS = {
        TimeUnit.MINUTES.toMillis(15),
        TimeUnit.MINUTES.toMillis(45),
        TimeUnit.MINUTES.toMillis(90),
        TimeUnit.MINUTES.toMillis(60),
        TimeUnit.MINUTES.toMillis(30),
        TimeUnit.MINUTES.toMillis(45),
        TimeUnit.MINUTES.toMillis(60),
        TimeUnit.MINUTES.toMillis(90),
        TimeUnit.HOURS.toMillis(5)
    };
    private static long durationsSumMs;

    static {
        durationsSumMs = 0;
        for (long duration : DURATIONS_MS) {
            durationsSumMs += duration;
        }
    }

    /** If this is specified for genre, it will be selected one of {@link #GENRES} in order. */
    public static final String GEN_GENRE = "GEN";

    private static final String[] GENRES = {
        "",
        TvContract.Programs.Genres.SPORTS,
        TvContract.Programs.Genres.NEWS,
        TvContract.Programs.Genres.SHOPPING,
        TvContract.Programs.Genres.DRAMA,
        TvContract.Programs.Genres.ENTERTAINMENT
    };

    public final String title;
    public final String episode;
    public final int seasonNumber;
    public final int episodeNumber;
    public final String posterArtUri;
    public final String description;
    public final long durationMs;
    public final String genre;
    public final ImmutableList<TvContentRating> contentRatings;
    public final String resourceUri;

    public static ProgramInfo fromCursor(Cursor c) {
        // TODO: Fill other fields.
        Builder builder = new Builder();
        int index = c.getColumnIndex(TvContract.Programs.COLUMN_TITLE);
        if (index >= 0) {
            builder.setTitle(c.getString(index));
        }
        index = c.getColumnIndex(TvContract.Programs.COLUMN_SHORT_DESCRIPTION);
        if (index >= 0) {
            builder.setDescription(c.getString(index));
        }
        index = c.getColumnIndex(TvContract.Programs.COLUMN_EPISODE_TITLE);
        if (index >= 0) {
            builder.setEpisode(c.getString(index));
        }
        return builder.build();
    }

    public ProgramInfo(
            String title,
            String episode,
            int seasonNumber,
            int episodeNumber,
            String posterArtUri,
            String description,
            long durationMs,
            ImmutableList<TvContentRating> contentRatings,
            String genre,
            String resourceUri) {
        this.title = title;
        this.episode = episode;
        this.seasonNumber = seasonNumber;
        this.episodeNumber = episodeNumber;
        this.posterArtUri = posterArtUri;
        this.description = description;
        this.durationMs = durationMs;
        this.contentRatings = contentRatings;
        this.genre = genre;
        this.resourceUri = resourceUri;
    }

    /**
     * Create a instance of {@link ProgramInfo} whose content will be generated as much as possible.
     */
    public static ProgramInfo create() {
        return new Builder().build();
    }

    /**
     * Get index of the program whose start time equals or less than {@code timeMs} and end time
     * more than {@code timeMs}.
     *
     * @param timeMs target time in millis to find a program.
     * @param channelId used to add complexity to the index between two consequence channels.
     */
    public int getIndex(long timeMs, long channelId) {
        if (durationMs != GEN_DURATION) {
            return Math.max((int) (timeMs / durationMs), 0);
        }
        long startTimeMs = channelId * DURATIONS_MS[((int) (channelId % DURATIONS_MS.length))];
        int index = (int) ((timeMs - startTimeMs) / durationsSumMs) * DURATIONS_MS.length;
        startTimeMs += (index / DURATIONS_MS.length) * durationsSumMs;
        while (startTimeMs + DURATIONS_MS[index % DURATIONS_MS.length] < timeMs) {
            startTimeMs += DURATIONS_MS[index % DURATIONS_MS.length];
            index++;
        }
        return index;
    }

    /**
     * Returns the start time for the program with the position.
     *
     * @param index index returned by {@link #getIndex}
     */
    public long getStartTimeMs(int index, long channelId) {
        if (durationMs != GEN_DURATION) {
            return index * durationMs;
        }
        long startTimeMs =
                channelId * DURATIONS_MS[((int) (channelId % DURATIONS_MS.length))]
                        + (index / DURATIONS_MS.length) * durationsSumMs;
        for (int i = 0; i < index % DURATIONS_MS.length; i++) {
            startTimeMs += DURATIONS_MS[i];
        }
        return startTimeMs;
    }

    /**
     * Return complete {@link ProgramInfo} with the generated value. See: {@link #GEN_TITLE}, {@link
     * #GEN_EPISODE}, {@link #GEN_POSTER}, {@link #GEN_DURATION}, {@link #GEN_GENRE}.
     *
     * @param index index returned by {@link #getIndex}
     */
    public ProgramInfo build(Context context, int index) {
        if (!GEN_TITLE.equals(title)
                && episode == null
                && !GEN_POSTER.equals(posterArtUri)
                && durationMs != GEN_DURATION
                && !GEN_GENRE.equals(genre)) {
            return this;
        }
        return new ProgramInfo(
                GEN_TITLE.equals(title) ? "Title(" + index + ")" : title,
                GEN_EPISODE.equals(episode) ? "Episode(" + index + ")" : episode,
                episode != null ? (index % SEASON_MAX + 1) : seasonNumber,
                episode != null ? (index % EPISODE_MAX + 1) : episodeNumber,
                GEN_POSTER.equals(posterArtUri)
                        ? Utils.getUriStringForResource(
                                context, POSTER_ARTS_RES[index % POSTER_ARTS_RES.length])
                        : posterArtUri,
                description,
                durationMs == GEN_DURATION ? DURATIONS_MS[index % DURATIONS_MS.length] : durationMs,
                contentRatings,
                GEN_GENRE.equals(genre) ? GENRES[index % GENRES.length] : genre,
                resourceUri);
    }

    @Override
    public String toString() {
        return "ProgramInfo{title="
                + title
                + ", episode="
                + episode
                + ", durationMs="
                + durationMs
                + "}";
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        ProgramInfo that = (ProgramInfo) o;
        return Objects.equals(seasonNumber, that.seasonNumber)
                && Objects.equals(episodeNumber, that.episodeNumber)
                && Objects.equals(durationMs, that.durationMs)
                && Objects.equals(title, that.title)
                && Objects.equals(episode, that.episode)
                && Objects.equals(posterArtUri, that.posterArtUri)
                && Objects.equals(description, that.description)
                && Objects.equals(genre, that.genre)
                && Objects.equals(contentRatings, that.contentRatings)
                && Objects.equals(resourceUri, that.resourceUri);
    }

    @Override
    public int hashCode() {
        return Objects.hash(title, episode, seasonNumber, episodeNumber);
    }

    public static class Builder {
        private String mTitle = GEN_TITLE;
        private String mEpisode = GEN_EPISODE;
        private int mSeasonNumber;
        private int mEpisodeNumber;
        private String mPosterArtUri = GEN_POSTER;
        private String mDescription;
        private long mDurationMs = GEN_DURATION;
        private ImmutableList<TvContentRating> mContentRatings;
        private String mGenre = GEN_GENRE;
        private String mResourceUri;

        public Builder setTitle(String title) {
            mTitle = title;
            return this;
        }

        public Builder setEpisode(String episode) {
            mEpisode = episode;
            return this;
        }

        public Builder setSeasonNumber(int seasonNumber) {
            mSeasonNumber = seasonNumber;
            return this;
        }

        public Builder setEpisodeNumber(int episodeNumber) {
            mEpisodeNumber = episodeNumber;
            return this;
        }

        public Builder setPosterArtUri(String posterArtUri) {
            mPosterArtUri = posterArtUri;
            return this;
        }

        public Builder setDescription(String description) {
            mDescription = description;
            return this;
        }

        public Builder setDurationMs(long durationMs) {
            mDurationMs = durationMs;
            return this;
        }

        public Builder setContentRatings(ImmutableList<TvContentRating> contentRatings) {
            mContentRatings = contentRatings;
            return this;
        }

        public Builder setGenre(String genre) {
            mGenre = genre;
            return this;
        }

        public Builder setResourceUri(String resourceUri) {
            mResourceUri = resourceUri;
            return this;
        }

        public ProgramInfo build() {
            return new ProgramInfo(
                    mTitle,
                    mEpisode,
                    mSeasonNumber,
                    mEpisodeNumber,
                    mPosterArtUri,
                    mDescription,
                    mDurationMs,
                    mContentRatings,
                    mGenre,
                    mResourceUri);
        }
    }
}
