/*
 * 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.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.media.tv.TvContract;
import android.media.tv.TvContract.Channels;
import android.net.Uri;
import android.os.AsyncTask;
import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/** Static helper methods for working with {@link android.media.tv.TvContract}. */
public class ChannelUtils {
    private static final String TAG = "ChannelUtils";
    private static final boolean DEBUG = false;

    /**
     * Query and return the map of (channel_id, ChannelInfo). See: {@link
     * com.android.tv.testing.data.ChannelInfo#fromCursor(Cursor)}.
     */
    @WorkerThread
    public static Map<Long, ChannelInfo> queryChannelInfoMapForTvInput(
            Context context, String inputId) {
        Uri uri = TvContract.buildChannelsUriForInput(inputId);
        Map<Long, ChannelInfo> map = new HashMap<>();

        String[] projections = new String[ChannelInfo.PROJECTION.length + 1];
        projections[0] = Channels._ID;
        System.arraycopy(ChannelInfo.PROJECTION, 0, projections, 1, ChannelInfo.PROJECTION.length);
        try (Cursor cursor =
                context.getContentResolver().query(uri, projections, null, null, null)) {
            if (cursor != null) {
                while (cursor.moveToNext()) {
                    map.put(cursor.getLong(0), ChannelInfo.fromCursor(cursor));
                }
            }
            return map;
        }
    }

    @WorkerThread
    public static void updateChannels(Context context, String inputId, List<ChannelInfo> channels) {
        // Create a map from original network ID to channel row ID for existing channels.
        SparseArray<Long> existingChannelsMap = new SparseArray<>();
        Uri channelsUri = TvContract.buildChannelsUriForInput(inputId);
        String[] projection = {Channels._ID, Channels.COLUMN_ORIGINAL_NETWORK_ID};
        ContentResolver resolver = context.getContentResolver();
        try (Cursor cursor = resolver.query(channelsUri, projection, null, null, null)) {
            while (cursor != null && cursor.moveToNext()) {
                long rowId = cursor.getLong(0);
                int originalNetworkId = cursor.getInt(1);
                existingChannelsMap.put(originalNetworkId, rowId);
            }
        }

        Map<Uri, String> logos = new HashMap<>();
        for (ChannelInfo channel : channels) {
            // If a channel exists, update it. If not, insert a new one.
            ContentValues values = new ContentValues();
            values.put(Channels.COLUMN_INPUT_ID, inputId);
            values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.number);
            values.put(Channels.COLUMN_DISPLAY_NAME, channel.name);
            values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId);
            String videoFormat = channel.getVideoFormat();
            if (videoFormat != null) {
                values.put(Channels.COLUMN_VIDEO_FORMAT, videoFormat);
            } else {
                values.putNull(Channels.COLUMN_VIDEO_FORMAT);
            }
            if (!TextUtils.isEmpty(channel.appLinkText)) {
                values.put(Channels.COLUMN_APP_LINK_TEXT, channel.appLinkText);
            }
            if (channel.appLinkColor != 0) {
                values.put(Channels.COLUMN_APP_LINK_COLOR, channel.appLinkColor);
            }
            if (!TextUtils.isEmpty(channel.appLinkPosterArtUri)) {
                values.put(Channels.COLUMN_APP_LINK_POSTER_ART_URI, channel.appLinkPosterArtUri);
            }
            if (!TextUtils.isEmpty(channel.appLinkIconUri)) {
                values.put(Channels.COLUMN_APP_LINK_ICON_URI, channel.appLinkIconUri);
            }
            if (!TextUtils.isEmpty(channel.appLinkIntentUri)) {
                values.put(Channels.COLUMN_APP_LINK_INTENT_URI, channel.appLinkIntentUri);
            }
            Long rowId = existingChannelsMap.get(channel.originalNetworkId);
            Uri uri;
            if (rowId == null) {
                if (DEBUG) {
                    Log.d(TAG, "Inserting " + channel);
                }
                uri = resolver.insert(TvContract.Channels.CONTENT_URI, values);
            } else {
                if (DEBUG) {
                    Log.d(TAG, "Updating " + channel);
                }
                uri = TvContract.buildChannelUri(rowId);
                resolver.update(uri, values, null, null);
                existingChannelsMap.remove(channel.originalNetworkId);
            }
            if (!TextUtils.isEmpty(channel.logoUrl)) {
                logos.put(TvContract.buildChannelLogoUri(uri), channel.logoUrl);
            }
        }
        if (!logos.isEmpty()) {
            new InsertLogosTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, logos);
        }

        // Deletes channels which don't exist in the new feed.
        int size = existingChannelsMap.size();
        for (int i = 0; i < size; ++i) {
            Long rowId = existingChannelsMap.valueAt(i);
            resolver.delete(TvContract.buildChannelUri(rowId), null, null);
        }
    }

    public static void copy(InputStream is, OutputStream os) throws IOException {
        byte[] buffer = new byte[1024];
        int len;
        while ((len = is.read(buffer)) != -1) {
            os.write(buffer, 0, len);
        }
    }

    private ChannelUtils() {
        // Prevent instantiation.
    }

    public static List<ChannelInfo> createChannelInfos(Context context, int channelCount) {
        List<ChannelInfo> channels = new ArrayList<>();
        for (int i = 1; i <= channelCount; i++) {
            channels.add(ChannelInfo.create(context, i));
        }
        return channels;
    }

    public static class InsertLogosTask extends AsyncTask<Map<Uri, String>, Void, Void> {
        private final Context mContext;

        InsertLogosTask(Context context) {
            mContext = context;
        }

        @SafeVarargs
        @Override
        public final Void doInBackground(Map<Uri, String>... logosList) {
            for (Map<Uri, String> logos : logosList) {
                for (Uri uri : logos.keySet()) {
                    if (uri == null) {
                        continue;
                    }
                    Uri logoUri = Uri.parse(logos.get(uri));
                    try (InputStream is = mContext.getContentResolver().openInputStream(logoUri);
                            OutputStream os = mContext.getContentResolver().openOutputStream(uri)) {
                        copy(is, os);
                    } catch (IOException ioe) {
                        Log.e(TAG, "Failed to write " + logoUri + "  to " + uri, ioe);
                    }
                }
            }
            return null;
        }
    }
}
