/*
 * Copyright 2015 Synced Synapse. 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 org.xbmc.kore.utils;

import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Base64;
import android.widget.Toast;

import org.xbmc.kore.R;
import org.xbmc.kore.Settings;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.jsonrpc.ApiCallback;
import org.xbmc.kore.host.HostConnection;
import org.xbmc.kore.jsonrpc.method.Files;
import org.xbmc.kore.jsonrpc.method.JSONRPC;
import org.xbmc.kore.jsonrpc.type.FilesType;

import java.io.File;
import java.util.List;
import java.util.Locale;

/**
 * Various methods to help with file downloading
 */
public class FileDownloadHelper {
    private static final String TAG = LogUtils.makeLogTag(FileDownloadHelper.class);

    // These chars cause problems with DownloadManager
    private static final String RESERVED_CHARS_REGEX = "[?]";

    public static final int OVERWRITE_FILES = 0,
            DOWNLOAD_WITH_NEW_NAME = 1;

    public static final String NO_ARTIST_DIR = "No artist",
            NO_MOVIE_TITLE_DIR = "No title",
            NO_TVSHOW_TITLE_DIR = "No title";

    public static abstract class MediaInfo {
        public String fileName;

        public MediaInfo(final String fileName) {
            this.fileName = fileName;
        }

        /**
         * Check whether the directory on which to load the file exists
         * @return Whether the directory exists
         */
        public boolean downloadDirectoryExists() {
            File file = new File(getAbsoluteDirectoryPath());
//            LogUtils.LOGD(TAG, "Checking directory: " + file.getPath());
//            LogUtils.LOGD(TAG, "Exists: " + file.exists());
            return file.exists();
        }

        /**
         * Check whether the file to download already exists
         * @return Whether the file exists
         */
        public boolean downloadFileExists() {
            if (!downloadDirectoryExists())
                return false;
            File file = new File(getAbsoluteFilePath());
            return file.exists();
        }

        public String getAbsoluteDirectoryPath() {
            File externalFilesDir = Environment.getExternalStoragePublicDirectory(getExternalPublicDirType());
            String filePath = externalFilesDir.getPath() + "/" + getRelativeDirectoryPath();
            return filePath.replaceAll(RESERVED_CHARS_REGEX, "_");
        }

        public String getAbsoluteFilePath() {
            String filePath = getAbsoluteDirectoryPath() + "/" + getDownloadFileName();
            return filePath.replaceAll(RESERVED_CHARS_REGEX, "_");
        }

        public String getRelativeFilePath() {
            String filePath = getRelativeDirectoryPath() + "/" + getDownloadFileName();
            return filePath.replaceAll(RESERVED_CHARS_REGEX, "_");
        }

        public abstract String getExternalPublicDirType();
        public abstract String getRelativeDirectoryPath();
        public abstract String getDownloadFileName();

        public abstract String getDownloadTitle(Context context);
        public String getDownloadDescrition(Context context) {
            return context.getString(R.string.download_file_description);
        }

        public String getMediaUrl(HostInfo hostInfo) {
            String pathforUrl = Uri.encode(fileName);
            String credentials = (hostInfo.getPassword() == null || hostInfo.getPassword().isEmpty()) ?
                                 "" :
                                 String.format("%s:%s@", hostInfo.getUsername(), hostInfo.getPassword());
            return String.format(Locale.US,
                                 "%s://%s%s:%d/vfs/%s",
                                 hostInfo.isHttps? "https" : "http",
                                 credentials, hostInfo.getAddress(), hostInfo.getHttpPort(), pathforUrl);
        }
    }

    /**
     * Info for downloading songs
     */
    public static class SongInfo extends MediaInfo {
        public String artist;
        public String album;
        public int songId;
        public int track;
        public String title;

        public SongInfo() {
            super(null);
            artist = null;
            album = null;
            songId = -1;
            track = -1;
            title = null;
        }

        public SongInfo(final String artist, final String album,
                        final int songId, final int track, final String title,
                        final String fileName) {
            super(fileName);
            this.artist = artist;
            this.album = album;
            this.songId = songId;
            this.track = track;
            this.title = title;
        }

        public String getRelativeDirectoryPath() {
            return (TextUtils.isEmpty(artist) ? NO_ARTIST_DIR :
                    TextUtils.isEmpty(album) ? artist : artist + "/" + album);
        }

        public String getDownloadFileName() {
            String ext = getFilenameExtension(fileName);
            return (ext != null) ?
                   track + " - " + title + ext :
                   null;
        }

        public String getExternalPublicDirType() {
            return Environment.DIRECTORY_MUSIC;
        }

        public String getDownloadTitle(Context context) {
            return title;
        }
    }

    /**
     * Info for downloading movies
     */
    public static class MovieInfo extends MediaInfo {
        public final String title;

        public MovieInfo(final String title, final String fileName) {
            super(fileName);
            this.title = title;
        }

        public String getRelativeDirectoryPath() {
            return (TextUtils.isEmpty(title)) ?
                   NO_MOVIE_TITLE_DIR : title;
        }

        public String getDownloadFileName() {
            String ext = getFilenameExtension(fileName);
            return (ext != null) ?
                   title + ext : null;
        }

        public String getExternalPublicDirType() {
            return Environment.DIRECTORY_MOVIES;
        }

        public String getDownloadTitle(Context context) {
            return title;
        }
    }

    /**
     * Info for downloading TVShows
     */
    public static class TVShowInfo extends MediaInfo {
        public final String tvshowTitle;
        public final int season;
        public final int episodeNumber;
        public final String title;

        public TVShowInfo(final String tvshowTitle, final int season,
                          final int episodeNumber, final String title,
                          final String fileName) {
            super(fileName);
            this.tvshowTitle = tvshowTitle;
            this.season = season;
            this.episodeNumber = episodeNumber;
            this.title = title;
        }

        public String getRelativeDirectoryPath() {
            if (season > 0) {
                return (TextUtils.isEmpty(tvshowTitle)) ? NO_TVSHOW_TITLE_DIR : tvshowTitle + "/Season" + season;
            } else {
                return (TextUtils.isEmpty(tvshowTitle)) ? NO_TVSHOW_TITLE_DIR : tvshowTitle;
            }
        }

        public String getDownloadFileName() {
            String ext = getFilenameExtension(fileName);
            return (ext != null) ? episodeNumber + " - " + title + ext : null;
        }

        public String getExternalPublicDirType() {
            return Environment.DIRECTORY_MOVIES;
        }

        public String getDownloadTitle(Context context) {
            return title;
        }
    }

    /**
     * Info for downloading music videos
     */
    public static class MusicVideoInfo extends MediaInfo {
        public final String title;

        private static final String SUBDIRECTORY = "Music Videos";

        public MusicVideoInfo(final String title, final String fileName) {
            super(fileName);
            this.title = title;
        }

        public String getRelativeDirectoryPath() {
            return (TextUtils.isEmpty(title)) ?
                   null : SUBDIRECTORY + "/" + title;
        }

        public String getDownloadFileName() {
            String ext = getFilenameExtension(fileName);
            return (ext != null) ?
                   title + ext : null;
        }

        public String getExternalPublicDirType() {
            return Environment.DIRECTORY_MUSIC;
        }

        public String getDownloadTitle(Context context) {
            return title;
        }
    }

    /**
     * Auxiliary method to get a filename extension, assuming the filename ends with .ext
     * @param filename File name
     * @return Extension if present, or null
     */
    public static String getFilenameExtension(String filename) {
        int idx = filename.lastIndexOf(".");
        return (idx > 0) ? filename.substring(idx) : null;
    }

    public static void downloadFiles(final Context context, final HostInfo hostInfo,
                                     final MediaInfo mediaInfo,
                                     final int fileHandlingMode,
                                     final Handler callbackHandler) {
        if (mediaInfo == null)
            return;

        if (!checkDownloadDir(context, mediaInfo.getAbsoluteDirectoryPath()))
            return;

        // Check if we are connected to the host
        final HostConnection httpHostConnection = new HostConnection(hostInfo);
        httpHostConnection.setProtocol(HostConnection.PROTOCOL_HTTP);

        JSONRPC.Ping action = new JSONRPC.Ping();
        action.execute(httpHostConnection, new ApiCallback<String>() {
            @Override
            public void onSuccess(String result) {
                // Ok, continue, iterate through the song list and launch a download for each
                final DownloadManager downloadManager = (DownloadManager)context.getSystemService(Context.DOWNLOAD_SERVICE);

                downloadSingleFile(context, httpHostConnection, hostInfo,
                        mediaInfo, fileHandlingMode, downloadManager, callbackHandler);
            }

            @Override
            public void onError(int errorCode, String description) {
                Toast.makeText(context, R.string.unable_to_connect_to_xbmc, Toast.LENGTH_SHORT)
                     .show();
            }
        }, callbackHandler);
    }

    public static void downloadFiles(final Context context, final HostInfo hostInfo,
                                    final List<? extends MediaInfo> mediaInfoList,
                                    final int fileHandlingMode,
                                    final Handler callbackHandler) {
        if ((mediaInfoList == null) || (mediaInfoList.isEmpty()))
            return;

        if (!checkDownloadDir(context, mediaInfoList.get(0).getAbsoluteDirectoryPath()))
            return;

        // Check if we are connected to the host
        final HostConnection httpHostConnection = new HostConnection(hostInfo);
        httpHostConnection.setProtocol(HostConnection.PROTOCOL_HTTP);

        JSONRPC.Ping action = new JSONRPC.Ping();
        action.execute(httpHostConnection, new ApiCallback<String>() {
            @Override
            public void onSuccess(String result) {
                // Ok, continue, iterate through the song list and launch a download for each
                final DownloadManager downloadManager = (DownloadManager)context.getSystemService(Context.DOWNLOAD_SERVICE);

                for (final MediaInfo mediaInfo : mediaInfoList) {
                    downloadSingleFile(context, httpHostConnection, hostInfo,
                            mediaInfo, fileHandlingMode, downloadManager, callbackHandler);
                }
            }

            @Override
            public void onError(int errorCode, String description) {
                Toast.makeText(context, R.string.unable_to_connect_to_xbmc, Toast.LENGTH_SHORT)
                        .show();
            }
        }, callbackHandler);
    }

    private static boolean checkDownloadDir(Context context, String downloadDirPath) {
        File downloadDir = new File(downloadDirPath);
        if ((downloadDir.exists() && !downloadDir.isDirectory())) {
            Toast.makeText(context,
                    "Download directory already exists and is not a directory.",
                    Toast.LENGTH_SHORT)
                 .show();
            return false;
        }
        if (!downloadDir.isDirectory() && !downloadDir.mkdirs()) {
            Toast.makeText(context,
                    "Couldn't create download directory: " + downloadDir.getPath(),
                    Toast.LENGTH_SHORT)
                 .show();
            return false;
        }
        return true;
    }

    private static void downloadSingleFile(final Context context,
                                           final HostConnection httpHostConnection,
                                           final HostInfo hostInfo,
                                           final MediaInfo mediaInfo,
                                           final int fileHandlingMode,
                                           final DownloadManager downloadManager,
                                           final Handler callbackHandler) {
        Files.PrepareDownload action = new Files.PrepareDownload(mediaInfo.fileName);
        action.execute(httpHostConnection, new ApiCallback<FilesType.PrepareDownloadReturnType>() {
            @Override
            public void onSuccess(FilesType.PrepareDownloadReturnType result) {
                // If the file exists and it's to be overwritten, delete it,
                // as the DownloadManager always creates a new name
                if (fileHandlingMode == OVERWRITE_FILES) {
                    File file = new File(mediaInfo.getAbsoluteFilePath());
                    if (file.exists()) {
                        file.delete();
                    }
                }

                // Ok, we got the path, invoke downloader
                Uri uri = Uri.parse(hostInfo.getHttpURL() + "/" + result.path);
                DownloadManager.Request request = new DownloadManager.Request(uri);
                // http basic authorization
                if ((hostInfo.getUsername() != null) && !hostInfo.getUsername().isEmpty() &&
                    (hostInfo.getPassword() != null) && !hostInfo.getPassword().isEmpty()) {
                    final String token = Base64.encodeToString((hostInfo.getUsername() + ":" +
                                                                hostInfo.getPassword()).getBytes(), Base64.DEFAULT);
                    request.addRequestHeader("Authorization", "Basic " + token);
                }
                request.allowScanningByMediaScanner();
                request.setAllowedNetworkTypes(Settings.allowedDownloadNetworkTypes(context));
                request.setTitle(mediaInfo.getDownloadTitle(context));
                request.setDescription(mediaInfo.getDownloadDescrition(context));

                request.setDestinationInExternalPublicDir(mediaInfo.getExternalPublicDirType(),
                        mediaInfo.getRelativeFilePath());
                downloadManager.enqueue(request);
            }

            @Override
            public void onError(int errorCode, String description) {
                Toast.makeText(context,
                        String.format(context.getString(R.string.error_getting_file_information),
                                mediaInfo.getDownloadTitle(context)),
                        Toast.LENGTH_SHORT)
                     .show();
            }
        }, callbackHandler);
    }
}
