/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * 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.google.samples.apps.iosched.io;

import android.content.ContentProviderOperation;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.BaseColumns;

import com.google.samples.apps.iosched.io.model.Speaker;
import com.google.samples.apps.iosched.provider.ScheduleContract;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.samples.apps.iosched.provider.ScheduleContractHelper;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

import static com.google.samples.apps.iosched.util.LogUtils.*;

public class SpeakersHandler extends JSONHandler {
    private static final String TAG = makeLogTag(SpeakersHandler.class);
    private HashMap<String, Speaker> mSpeakers = new HashMap<String, Speaker>();

    public SpeakersHandler(Context context) {
        super(context);
    }

    @Override
    public void process(JsonElement element) {
        for (Speaker speaker : new Gson().fromJson(element, Speaker[].class)) {
            mSpeakers.put(speaker.id, speaker);
        }
    }

    @Override
    public void makeContentProviderOperations(ArrayList<ContentProviderOperation> list) {
        Uri uri = ScheduleContractHelper.setUriAsCalledFromSyncAdapter(
                ScheduleContract.Speakers.CONTENT_URI);
        HashMap<String, String> speakerHashcodes = loadSpeakerHashcodes();
        HashSet<String> speakersToKeep = new HashSet<String>();
        boolean isIncrementalUpdate = speakerHashcodes != null && speakerHashcodes.size() > 0;

        if (isIncrementalUpdate) {
            LOGD(TAG, "Doing incremental update for speakers.");
        } else {
            LOGD(TAG, "Doing FULL (non incremental) update for speakers.");
            list.add(ContentProviderOperation.newDelete(uri).build());
        }

        int updatedSpeakers = 0;
        for (Speaker speaker : mSpeakers.values()) {
            String hashCode = speaker.getImportHashcode();
            speakersToKeep.add(speaker.id);

            // add speaker, if necessary
            if (!isIncrementalUpdate || !speakerHashcodes.containsKey(speaker.id) ||
                    !speakerHashcodes.get(speaker.id).equals(hashCode)) {
                ++updatedSpeakers;
                boolean isNew = !isIncrementalUpdate || !speakerHashcodes.containsKey(speaker.id);
                buildSpeaker(isNew, speaker, list);
            }
        }

        int deletedSpeakers = 0;
        if (isIncrementalUpdate) {
            for (String speakerId : speakerHashcodes.keySet()) {
                if (!speakersToKeep.contains(speakerId)) {
                    buildDeleteOperation(speakerId, list);
                    ++deletedSpeakers;
                }
            }
        }

        LOGD(TAG, "Speakers: " + (isIncrementalUpdate ? "INCREMENTAL" : "FULL") + " update. " +
                updatedSpeakers + " to update, " + deletedSpeakers + " to delete. New total: " +
                mSpeakers.size());
    }

    private void buildSpeaker(boolean isInsert, Speaker speaker,
                              ArrayList<ContentProviderOperation> list) {
        Uri allSpeakersUri = ScheduleContractHelper.setUriAsCalledFromSyncAdapter(
                ScheduleContract.Speakers.CONTENT_URI);
        Uri thisSpeakerUri = ScheduleContractHelper.setUriAsCalledFromSyncAdapter(
                ScheduleContract.Speakers.buildSpeakerUri(speaker.id));

        ContentProviderOperation.Builder builder;
        if (isInsert) {
            builder = ContentProviderOperation.newInsert(allSpeakersUri);
        } else {
            builder = ContentProviderOperation.newUpdate(thisSpeakerUri);
        }

        list.add(builder.withValue(ScheduleContract.SyncColumns.UPDATED, System.currentTimeMillis())
                .withValue(ScheduleContract.Speakers.SPEAKER_ID, speaker.id)
                .withValue(ScheduleContract.Speakers.SPEAKER_NAME, speaker.name)
                .withValue(ScheduleContract.Speakers.SPEAKER_ABSTRACT, speaker.bio)
                .withValue(ScheduleContract.Speakers.SPEAKER_COMPANY, speaker.company)
                .withValue(ScheduleContract.Speakers.SPEAKER_IMAGE_URL, speaker.thumbnailUrl)
                .withValue(ScheduleContract.Speakers.SPEAKER_PLUSONE_URL, speaker.plusoneUrl)
                .withValue(ScheduleContract.Speakers.SPEAKER_TWITTER_URL, speaker.twitterUrl)
                .withValue(ScheduleContract.Speakers.SPEAKER_IMPORT_HASHCODE,
                        speaker.getImportHashcode())
                .build());
    }

    private void buildDeleteOperation(String speakerId, ArrayList<ContentProviderOperation> list) {
        Uri speakerUri = ScheduleContractHelper.setUriAsCalledFromSyncAdapter(
                ScheduleContract.Speakers.buildSpeakerUri(speakerId));
        list.add(ContentProviderOperation.newDelete(speakerUri).build());
    }

    private HashMap<String, String> loadSpeakerHashcodes() {
        Uri uri = ScheduleContractHelper.setUriAsCalledFromSyncAdapter(
                ScheduleContract.Speakers.CONTENT_URI);
        Cursor cursor = null;
        try {
            cursor = mContext.getContentResolver().query(uri, SpeakerHashcodeQuery.PROJECTION,
                    null, null, null);
            if (cursor == null) {
                LOGE(TAG, "Error querying speaker hashcodes (got null cursor)");
                return null;
            }
            if (cursor.getCount() < 1) {
                LOGE(TAG, "Error querying speaker hashcodes (no records returned)");
                return null;
            }
            HashMap<String, String> result = new HashMap<String, String>();
            if (cursor.moveToFirst()) {
                do {
                    String speakerId = cursor.getString(SpeakerHashcodeQuery.SPEAKER_ID);
                    String hashcode = cursor.getString(SpeakerHashcodeQuery.SPEAKER_IMPORT_HASHCODE);
                    result.put(speakerId, hashcode == null ? "" : hashcode);
                } while (cursor.moveToNext());
            }
            return result;
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }

    public HashMap<String, Speaker> getSpeakerMap() {
        return mSpeakers;
    }

    private interface SpeakerHashcodeQuery {
        String[] PROJECTION = {
                BaseColumns._ID,
                ScheduleContract.Speakers.SPEAKER_ID,
                ScheduleContract.Speakers.SPEAKER_IMPORT_HASHCODE
        };
        final int _ID = 0;
        final int SPEAKER_ID = 1;
        final int SPEAKER_IMPORT_HASHCODE = 2;
    }
}
