/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Andoni del Olmo
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package org.ado.minesync.service;

import android.app.IntentService;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import com.dropbox.sync.android.DbxException;
import org.ado.minesync.ActivityTracker;
import org.ado.minesync.commons.ALog;
import org.ado.minesync.config.AppConfiguration;
import org.ado.minesync.db.SyncTypeEnum;
import org.ado.minesync.exception.DropboxAccountException;
import org.ado.minesync.exception.MineSyncException;
import org.ado.minesync.gui.ExceptionNotifier;
import org.ado.minesync.gui.notification.ConfigurationNotification;
import org.ado.minesync.gui.notification.MineSyncNotification;
import org.ado.minesync.minecraft.*;
import org.apache.commons.lang.StringUtils;

import java.io.File;

/**
 * Service to upload/download all worlds.
 *
 * @author andoni
 * @since 1.0.0
 */
public class UploadDownloadService extends IntentService {

    public static final String BROADCAST_ACTION = "org.ado.minesync";
    public static final String BROADCAST_PROGRESS = "progress";
    public static final String OPERATION_TYPE = "Operation_Type_Enum";
    public static final String OPERATION_TITLE = "Operation_Title";
    public static final String OPERATION_WORLD_NAME = "operation_world_name";
    public static final String OPERATION_WORLD_SYNC_TYPE = "pperation_world_sync_type";
    private static final String TAG = UploadDownloadService.class.getName();
    private final IBinder binder = new UploadDownloadBinder();
    private final Handler handler = new Handler();
    Intent intent;
    private MinecraftWorldManager minecraftWorldManager;
    private MinecraftData minecraftData;
    private ActivityTracker activityTracker;
    private ConfigurationNotification configurationNotification;
    private MineSyncNotification mineSyncNotification;
    private int progress;
    private Runnable sendUpdatesToUI = new Runnable() {
        public void run() {
            if (progress < 100) {
                displayLoggingInfo();
                handler.postDelayed(this, 1000);
            } else {
                try {
                    Thread.currentThread().interrupt();
                } catch (Exception e) {
                    // ignore
                }
            }
        }
    };
    private String operationTitle;

    public UploadDownloadService() {
        this(UploadDownloadService.class.getName());
    }

    public UploadDownloadService(String name) {
        super(name);
    }

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

    @Override
    public void onCreate() {
        super.onCreate();
        ALog.d(TAG, "onCreate.");
        minecraftWorldManager =
                new MinecraftWorldManager(AppConfiguration.getDropboxAccountManager(getApplicationContext()),
                        getApplicationContext());
        minecraftData = new MinecraftData();
        activityTracker = new ActivityTracker(getApplicationContext());
        configurationNotification = new ConfigurationNotification(getApplicationContext());
        mineSyncNotification = new MineSyncNotification(this);
        intent = new Intent(BROADCAST_ACTION);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        ALog.d(TAG, "onHandleIntent. intent[%s].", intent);
        removeCallbacks();
        handler.postDelayed(sendUpdatesToUI, 5000);

        operationTitle = intent.getStringExtra(OPERATION_TITLE);
        try {
            startProcess((OperationTypeEnum) intent.getSerializableExtra(OPERATION_TYPE),
                    intent.getStringExtra(OPERATION_WORLD_NAME),
                    (SyncTypeEnum) intent.getSerializableExtra(OPERATION_WORLD_SYNC_TYPE));
        } catch (MineSyncException e) {
            ExceptionNotifier.notifyException(getApplicationContext(), e);
        } catch (DropboxAccountException e) {
            ExceptionNotifier.notifyException(getApplicationContext(), e);
        }
    }

    private void displayLoggingInfo() {
        ALog.v(TAG, "entered DisplayLoggingInfo");

        intent.putExtra(BROADCAST_PROGRESS, progress);
        intent.putExtra(OPERATION_TITLE, operationTitle);
        sendBroadcast(intent);
    }

    @Override
    public void onDestroy() {
        ALog.d(TAG, "onDestroy.");
        super.onDestroy();
    }

    private void removeCallbacks() {
        handler.removeCallbacks(sendUpdatesToUI);
    }

    private void startProcess(OperationTypeEnum operationType, String worldName, SyncTypeEnum syncType) throws MineSyncException, DropboxAccountException {
        switch (operationType) {
            case DOWNLOAD:
                mineSyncNotification.buildNotification(false);
                mineSyncNotification.updateSyncState(MineSyncService.SyncStateEnum.UPLOADING_DOWNLOADING);
                minecraftWorldManager.downloadWorld(worldName, syncType, getMinecraftWorldListener(worldName));
                break;
            case DOWNLOAD_ALL:
                configurationNotification.show();
                minecraftWorldManager.downloadAll(getMinecraftWorldAllListener(operationType));
                break;
            case UPLOAD:
                mineSyncNotification.buildNotification(false);
                mineSyncNotification.updateSyncState(MineSyncService.SyncStateEnum.UPLOADING_DOWNLOADING);
                minecraftWorldManager.uploadWorld(worldName, syncType, getMinecraftWorldListener(worldName));
                break;
            case UPLOAD_ALL:
                configurationNotification.show();
                minecraftWorldManager.uploadAll(getMinecraftWorldAllListener(operationType));
                break;
        }
    }

    private MinecraftWorldListener getMinecraftWorldListener(final String worldName) {
        return new AbstractMinecraftWorldListener() {
            @Override
            public void operationFinished(MinecraftWorldActionEnum minecraftWorldAction, File file) {
                if (MinecraftWorldActionEnum.NETWORK == minecraftWorldAction
                        && StringUtils.equals(MinecraftUtils.getWorldName(file), worldName)) {
                    ALog.i(TAG, "File \"%s\" successfully transferred.", file.getName());
                    mineSyncNotification.updateSyncState(MineSyncService.SyncStateEnum.SYNC_ACTIVE);
                }
            }
        };
    }

    private MinecraftWorldListener getMinecraftWorldAllListener(final OperationTypeEnum operationType) {
        return new MinecraftWorldListener() {
            int item = 0;

            @Override
            public void operationFinished(MinecraftWorldActionEnum minecraftWorldAction, File file) {
                if (MinecraftWorldActionEnum.NETWORK.equals(minecraftWorldAction)
                        && OperationTypeEnum.DOWNLOAD_ALL.equals(operationType)) {
                    ALog.d(TAG, "file downloaded [%s]", file.getName());
                    int total = 0;
                    try {
                        total = getDropboxNumberOfFiles();
                    } catch (DropboxAccountException e) {
                        e.printStackTrace();
                    }
                    item++;
                    publishProgress((int) getPercentage(total));

                } else if (OperationTypeEnum.UPLOAD_ALL.equals(operationType)) {
                    int total = minecraftData.getWorlds().size() * 2;
                    item++;
                    publishProgress((int) getPercentage(total));

                } else {
                    throw new IllegalStateException("Unexpected state. operationType \"" + operationType
                            + "\" minecraftWorldAction \"" + minecraftWorldAction
                            + "\" file \"" + file.getName() + "\".");
                }
            }

            private float getPercentage(int total) {
                float percentage = (float) ((item * 100) / total);
                ALog.d(TAG, "progress: %s", percentage);
                return percentage;
            }

            @Override
            public void operationFinished() {
                ALog.d(TAG, "operationFinished");
                progress = 100;
                displayLoggingInfo();
                activityTracker.setConfigurationProcess(true);
                activityTracker.setNeedToShowConfigurationProcessFinishedDialog(true);
                configurationNotification.setFinished();
            }
        };
    }

    private void publishProgress(int percentage) {
        ALog.d(TAG, "publishProgress [%d]", percentage);
        this.progress = percentage;
        configurationNotification.setProgress(percentage);
    }

    private int getDropboxNumberOfFiles() throws DropboxAccountException {
        try {
            return minecraftWorldManager.getDropboxNumberOfFiles();
        } catch (DbxException e) {
            ALog.w(TAG, e, "Cannot access Dropbox");
            return 0;
        }
    }

    public class UploadDownloadBinder extends Binder {
        public UploadDownloadService getService() {
            return UploadDownloadService.this;
        }
    }
}