/**
 * $URL$
 * DAAND
 * Copyright (C) 2004 - 2013, INSIDE Secure BV.  All rights reserved.
 */
package com.insidesecure.drm.android.reference;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import android.annotation.TargetApi;
import android.app.*;
import android.content.*;
import android.util.Log;
import com.insidesecure.drmagent.DRMContent;

@TargetApi(11)
public class Tools
{
    public static final String TAG = "Tools";
    private static final String defaultSubfileName = Constants.CONFIGURATION_CDESC_PATH + "subtitles";
    private static final String dot = ".";

    //Log constants
    private static final String LOG_OUTPUT_FORMAT = "threadtime";
    private static final String SET_LOG_OUTPUT_FORMAT = "-v";
    private static final String PRIORITY = "*:V";
    private static final String LOGCAT_CMD = "logcat";
    private static final String WRITE_LOGS_TO_FILE_CMD = "-f";
    private static final String FILE_FORMAT = ".txt";

    public static Process sLogSavingProcess = null;
    
    /**
     * Creates a new <code>HttpUrlConnection</code> instance.
     *
     * @return The newly created HTTP URL connection
     * @throws IOException If an error occurs while getting instance of <code>HttpUrlConnection</code> class
     */
    public static HttpURLConnection createHttpURLConnection(URL url) throws IOException
    {
        return (HttpURLConnection) url.openConnection();
    }

    /**
     * Copies the specified input stream onto the given output stream.
     *
     * @param inputStream  Input stream to read from
     * @param outputStream Output stream to write to
     * @return The number of bytes copied
     * @throws IOException If an error occurs while copying the data
     */
    public static long copyStream(final InputStream inputStream, final OutputStream outputStream) throws IOException
    {
        final int size = 16192;
        final byte[] buffer = new byte[size];

        int len;

        long totalCount = 0;
        while ((len = inputStream.read(buffer, 0, size)) != -1)
        {
            outputStream.write(buffer, 0, len);
            totalCount += len;

        }
        outputStream.flush();

        return totalCount;

    }

    /**
     * Method will check if the specified object is null and if so, generate and throw a <code>NullPointerException</code>.
     *
     * @param name The display name to use in the exception
     * @param obj  The object to check for null.
     */
    public static void DEFENSE(String name, Object obj)
    {
        if (obj == null)
        {
            throw new NullPointerException(name);
        }
    }

    public static ProgressDialog createSimpleProgressDialog(final Context owner, final CharSequence title, final CharSequence text)
    {
        ProgressDialog dialog = new ProgressDialog(owner);
        dialog.setTitle(title);
        dialog.setMessage(text);
        dialog.setCancelable(false);
        return dialog;
    }


    public static Dialog createAlertDialog(final Context owner, final CharSequence title, final CharSequence text, final boolean cancelable,
                                           final CharSequence positiveText, final DialogInterface.OnClickListener positiveListener,
                                           final CharSequence negativeText, final DialogInterface.OnClickListener negativeListener)
    {
        final AlertDialog.Builder builder = new AlertDialog.Builder(owner);
        builder.setTitle(title);
        builder.setMessage(text);
        builder.setCancelable(cancelable);
        if (positiveText != null && positiveListener != null)
        {
            builder.setPositiveButton(positiveText, positiveListener);
        }
        if (negativeText != null && negativeListener != null)
        {
            builder.setNegativeButton(negativeText, negativeListener);
        }
        return builder.create();
    }

    /** Converts the specified time (in seconds) to a string like 01:02
     *
     * @param time Seconds to convert
     * @return
     */
    public static String convertSecToNiceTime(final long seconds)
    {
        long minutes = seconds / 60;
        long remainingSeconds = (seconds - (minutes * 60));
        return zeroPad(minutes) + ":" + zeroPad(remainingSeconds);
    }

    public static String zeroPad(final long number)
    {
        return (number <= 9 ? "0" + number : "" + number);
    }

    public static String toHex(String txt)
    {
        return toHex(txt.getBytes());
    }

    public static String fromHex(String hex)
    {
        return new String(toByte(hex));
    }

    public static byte[] toByte(String hexString)
    {
        int len = hexString.length() / 2;
        byte[] result = new byte[len];
        for (int i = 0; i < len; i++)
        {
            result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue();
        }
        return result;
    }

    public static String toHex(byte[] buf)
    {
        if (buf == null)
        {
            return "";
        }
        StringBuffer result = new StringBuffer(2 * buf.length);
        for (int i = 0; i < buf.length; i++)
        {
            appendHex(result, buf[i]);
        }
        return result.toString();
    }

    private final static String HEX = "0123456789ABCDEF";

    private static void appendHex(StringBuffer sb, byte b)
    {
        sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
    }


    public static String toGUID(final byte[] raw)
    {
        byte[] first = new byte[4];
        byte[] second = new byte[2];
        byte[] third = new byte[2];
        byte[] fourth = new byte[2];
        byte[] fifth = new byte[6];

        System.arraycopy(raw, 0, first, 0, 4);
        System.arraycopy(raw, 4, second, 0, 2);
        System.arraycopy(raw, 6, third, 0, 2);
        System.arraycopy(raw, 8, fourth, 0, 2);
        System.arraycopy(raw, 10, fifth, 0, 6);

        final StringBuffer sb = new StringBuffer();

        sb.append(Tools.toHex(reverse(first)));
        sb.append('-');
        sb.append(Tools.toHex(reverse(second)));
        sb.append('-');
        sb.append(Tools.toHex(reverse(third)));
        sb.append('-');
        sb.append(Tools.toHex(fourth));
        sb.append('-');
        sb.append(Tools.toHex(fifth));

        return sb.toString().toUpperCase();

    }

    private static byte[] reverse(byte[] input)
    {
        byte[] output = new byte[input.length];

        int out = 0;
        for (int i = input.length-1; i >=0; i--)
        {
            output[out++] = input[i];
        }
        return output;
    }
    
    public static String exportHDS(String source)
    {
        Log.d(TAG, "Source: " + source);
        
        File sourceFile = new File(source);
        if(!sourceFile.exists())
        {
            Log.d(TAG, "Source file DOES not exit (" + source + ")");
            return "";
        }

        String destination = "sdcard/insidesecure/playready.hds";
        File destFile = new File(destination);

        if(destFile.exists())
        {
            Log.d(TAG, "Destination file ("+ destFile.getName() + ") exist; delete it before proceeding.");
            destFile.delete();
        }
        try
        {
            Log.d(TAG, "Creating destination file: " + destFile.getAbsolutePath() + ".");

            destFile.createNewFile();
        }
        catch (IOException iOException)
        {
            Log.d(TAG,"Exception: " + iOException.getMessage());
        }

        try
        {
            boolean copyRes = copyFile(sourceFile,destFile);
            Log.d(TAG,"Copy completed " + (copyRes ? "successfully" : "NOT successfully"));
        }
        catch(Exception ex)
        {
            Log.d(TAG, "Copy failed with msg=" + ex.getMessage());
        }
        return destination;
    }

    private static boolean copyFile(File source, File destination)
    {
        boolean retVal = false;

        FileInputStream inputStream = null;
        FileOutputStream outputStream = null;
        try
        {
            inputStream = new FileInputStream(source);
            outputStream = new FileOutputStream(destination);
            Tools.copyStream(inputStream, outputStream);
            retVal = true;

        }
        catch(FileNotFoundException ex)
        {
            Log.d(TAG,"FileNotFoundException=" + ex.getMessage());
        }
        catch(IOException ex2)
        {
            Log.d(TAG,"IOException=" + ex2.getMessage());
        }
        finally
        {
            try
            {
                if (inputStream != null)
                    inputStream.close();
                if (outputStream != null)
                {
                    outputStream.flush();
                    outputStream.close();
                }
            }
            catch (IOException e)
            {
                Log.e(TAG, e.getMessage());
            }
        }

        return retVal;
    }

    // Returns the extension of the found file or null
    // path is the full filename _without_ the extension, e.g. /sdcard/insidesecure/subtitle
    private static String subFileExists(String path, String[] supportedExtensions)
    {
        for (String extension: supportedExtensions)
            if (new File(path + dot + extension).exists())
                return extension;
        return null;
    }

    private static List<String> addSubtitleFiles(String path, String[] supportedExtensions)
    {
        List<String> subtitleFiles = new ArrayList<String>();

        for (String extension: supportedExtensions)
        {
            final String subtitlePath = path + dot + extension;
            final File subtitleFile = new File(subtitlePath);
            if (subtitleFile.exists())
                subtitleFiles.add(subtitlePath);
        }
        Log.i(TAG, "Found " + subtitleFiles.size() + " subtitle files for " + path + " and " + Arrays.toString(supportedExtensions));
        return subtitleFiles;

    }
    // Returns an array containing the full path of the found subtitle files (if any is found)
    public static String[] getSubtitlePaths(String title, String[] supportedExtensions)
    {
        List<String> subtitleFiles = new ArrayList<String>();
        if  (title == null
                || supportedExtensions == null || supportedExtensions.length == 0)
            return new String[0];

        //search first for 'title' for given extensions
        subtitleFiles = addSubtitleFiles(Constants.CONFIGURATION_CDESC_PATH + title, supportedExtensions);
        if(subtitleFiles.size()>0)
        {
            return subtitleFiles.toArray(new String[subtitleFiles.size()]);
        }
        else
        {
            //then for defaultSubfileName
            subtitleFiles = addSubtitleFiles(defaultSubfileName, supportedExtensions);
            return subtitleFiles.toArray(new String[subtitleFiles.size()]);
        }
    }


    public static String readableFileSize(long size)
    {
        if (size <= 0) return "0 bytes";
        final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"};
        int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
        return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
    }

    public static boolean isHTTPScheme(final URI uri)
    {
        return "http".equals(uri.getScheme()) || "https".equals(uri.getScheme());
    }

    public static DRMContent.FourCC getFourCC(String extension)
    {
        if (extension.equals("dfxp"))
        {
            return DRMContent.FourCC.TTML;
        }
        if (extension.equals("srt") || extension.equals("vtt"))  // we use the srt parser for vtt too
        {
            return DRMContent.FourCC.SRT;
        }
        if (extension.equals("sub"))
        {
            return DRMContent.FourCC.SUB;
        }
        return null;
    }

    public static void saveLogs(final Context context) {
        if (sLogSavingProcess == null || !isLogSavingProcessRunning()) {
            try {
                final File logFile = generateLogFile(context);
                if (null != logFile) {
                    sLogSavingProcess = Runtime.getRuntime().exec(new String[]{LOGCAT_CMD, WRITE_LOGS_TO_FILE_CMD, logFile.getPath(),
                            SET_LOG_OUTPUT_FORMAT, LOG_OUTPUT_FORMAT, PRIORITY});
                }
            } catch (IOException e) {
                Log.e(TAG, e.getMessage());
            }
        }
    }

    public static void stopLogSavingProcess() {
        if (sLogSavingProcess !=null && isLogSavingProcessRunning()) {
            sLogSavingProcess.destroy();
        }
    }

    private static boolean isLogSavingProcessRunning() {
        try {
            sLogSavingProcess.exitValue();
            return false;
        } catch (Exception e) {
            return true;
        }
    }

    private static File generateLogFile(final Context context) throws IOException {
        final String appName = context.getApplicationInfo().loadLabel(context.getPackageManager())
                .toString().replaceAll(" ", "_");
        final String currentTime = new SimpleDateFormat("dd-M-yyyy hh:mm:ss").format(new Date(System.currentTimeMillis()))
                .replaceAll(" ", "_").replaceAll(":", "-");
        final File logFileName = new File(Constants.CONFIGURATION_CDESC_PATH + appName + "_" + currentTime + FILE_FORMAT);
        return logFileName.createNewFile() ? logFileName : null;
    }


    public static int convertToPercentage(final long duration, final long playingTime)
    {
        long durationSeconds = duration / 1000;
        long playingTimeSeconds = playingTime / 1000;
        return Math.round(((((float)durationSeconds)-(durationSeconds-playingTimeSeconds)) / durationSeconds) * 100);
    }


    public static List<String> getAudioTrackStrings(final List<DRMContent.AudioTrack> audioTracks) {
        if (audioTracks == null || audioTracks.isEmpty()) {
            return null;
        }
        List<String> audioTrackStrings = new ArrayList<>(audioTracks.size());
        for (DRMContent.AudioTrack audioTrack : audioTracks) {
            audioTrackStrings.add(audioTrack.mName + " (" + audioTrack.mLanguage +
                    (audioTrack.getMetaData().get("GROUP-ID") != null ? "/" + audioTrack.getMetaData().get("GROUP-ID") : "") + ")");
        }
        return audioTrackStrings;
    }

    public static List<String> getSubtitleTracksString(final List<DRMContent.SubtitleTrack> subtitleTracks) {
        if (subtitleTracks == null || subtitleTracks.isEmpty()) {
            return null;
        }
        List<String> subtitleTrackStrings = new ArrayList<>(subtitleTracks.size());
        for (DRMContent.SubtitleTrack subtitleTrack : subtitleTracks) {
            subtitleTrackStrings.add(subtitleTrack.mName + " (" + subtitleTrack.mLanguage + ")" + (subtitleTrack.mType == DRMContent.SubtitleTrackType.CLOSED_CAPTIONS ? " - CC" : ""));
        }
        return subtitleTrackStrings;
    }
}
