package org.settingsdeployer;

import android.content.Context;
import android.os.AsyncTask;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;


/**
 * The downloader file. Takes a String as URL, updates with integer (0-100) and the result is the path of the downloaded file
 */
public class SettingsDownloader extends AsyncTask<String, Integer, Boolean>
{
    /// our progress types, passed in the progress update functions
    public final static int PROGRESS_DOWNLOAD = 1;
    public final static int PROGRESS_EXTRACT = 2;

    // maximum wakelock time in ms
    public final static int FOUR_MINUTES = 4 * 60 * 1000;

    /// our context
    Context m_ctx;

    /// log writer
    FileWriter m_logWriter;

    public SettingsDownloader(Context ctx, FileWriter logWriter)
    {
        m_ctx = ctx;
        m_logWriter = logWriter;
    }

    @Override
    protected void onPreExecute()
    {
    }

    @Override
    protected Boolean doInBackground(String... strParams)
    {
        Boolean result = Boolean.FALSE;

        // get a wakelock to hold for the duration of the background work. downloading
        // may be slow. extraction usually isn't too slow but also takes a bit of time. limit the wakelock's time!
        PowerManager powerManager = (PowerManager)m_ctx.getSystemService(Context.POWER_SERVICE);
        WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SettingsDownloaderWakeLock");
        wakeLock.acquire(FOUR_MINUTES);

        // download the file
        String filename = downloadFile(strParams[0]);
        if (filename != null)
        {
            File fSettingsZip = new File(filename);
            if (fSettingsZip.exists() && !isCancelled())
            {
                try
                {
                    m_logWriter.write("Successfully downloaded to: "+filename+"\n");

                    // extract to wanted directory
                    String destDir = strParams[1];
                    boolean bSuccess = extractSettingsZip(fSettingsZip, destDir);
                    result = (bSuccess ? Boolean.TRUE : Boolean.FALSE);

                    // delete settings zip
                    if (fSettingsZip.exists()) {
                        fSettingsZip.delete();
                    }
                }
                catch(Exception e)
                {
                    Log.e("SettingsDownloader", "Error: "+e.toString());
                }
            }
        }

        // if our max time hasn't passed but work is done or an error occurred we bail out and release
        if (wakeLock.isHeld())
        {
            wakeLock.release();
        }

        return result;
    }

    /**
     * Updating the UI
     * @param progress
     */
    protected void onProgressUpdate(Integer... progress)
    {
        // lovely cast we don't like. update the activity
        MainActivity activity = (MainActivity)m_ctx;
        activity.onProgressUpdate(progress);
    }

    /**
     * Called in the UI thread after the task finished
     * @param result
     */
    @Override
    protected void onPostExecute(Boolean result)
    {
        // @todo: make an interface instead of a cast
        MainActivity activity = (MainActivity)m_ctx;
        activity.DeploymentFinished(result);
    }

    /**
     * called on the UI thread after cancellation and background work has stopped
     */
    protected void onCancelled()
    {
        MainActivity activity = (MainActivity)m_ctx;
        activity.DeploymentFinished(false);
    }

    /**
     * Download the file and save it to our files dir
     * @param strURL URL of file to DL
     * @return Full path tyo the settings ZIP file
     */
    private String downloadFile(String strURL)
    {
        publishProgress(PROGRESS_DOWNLOAD, 0);
        int npos = strURL.lastIndexOf('/');
        if (npos == strURL.length()-1)
        {
            return null;
        }
        String filename = strURL.substring(npos+1);
        try
        {
            m_logWriter.write("Trying URL: "+strURL+", filename: "+filename+"\n");
            m_logWriter.flush();
            URL url = new URL(strURL);
            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            conn.connect();

            // setup streams to download and write to file
            String outputFilePath = m_ctx.getFilesDir().getAbsolutePath()+"/"+filename;
            File fSettingsZip = new File(outputFilePath);
            if (fSettingsZip.exists()) {    // todo: do we want this configurable?
                m_logWriter.write("Deleting old file: "+outputFilePath+"\n");
                fSettingsZip.delete();
            }

            int contentLength = conn.getContentLength();

            InputStream is = new BufferedInputStream(url.openStream(), 8192);
            OutputStream os = new BufferedOutputStream(new FileOutputStream(outputFilePath));
            byte data[] = new byte[1024];
            long totalRead = 0;
            int read=0;
            while ((read = is.read(data)) != -1)
            {
                if (isCancelled())
                {
                    outputFilePath = null;
                    break;
                }
                totalRead += read;

                // progress if we know the content length
                if (contentLength > 0) {
                    publishProgress(PROGRESS_DOWNLOAD, (int)(totalRead*100/contentLength));
                }

                // write to file
                os.write(data, 0, read);
            }

            // close and get ready to bail
            os.flush();
            os.close();
            is.close();
            filename = outputFilePath;
        }
        catch(Exception e)
        {
            Log.e("SettingsDownloader", "Error: "+e.toString());
            return null;
        }

        return filename;
    }

    /**
     * Extract the settings zip to the wanted location
     * @param fSettingsZip The zip file
     * @return True if extraction was successful
     */
    private boolean extractSettingsZip(File fSettingsZip, String destDir)
    {
        publishProgress(PROGRESS_EXTRACT, 0);
        boolean result = false;
        try
        {
            m_logWriter.write("Unzipping to destination: "+destDir+"\n");
            m_logWriter.flush();

            // open the zip
            ZipFile zip = new ZipFile(fSettingsZip);
            int count=0;
            int zipSize = zip.size();
            Enumeration<? extends ZipEntry> entries = zip.entries();

            while(entries.hasMoreElements())
            {
                if (isCancelled()) {
                    break;
                }

                // todo: update progress
                ZipEntry ze = (ZipEntry)entries.nextElement();
                count++;
                String entryName = ze.getName();
                String destFullpath = destDir+"/"+entryName;
                m_logWriter.write("Extracting: "+destFullpath+"\n");
                File fDestPath = new File(destFullpath);
                if (ze.isDirectory())
                {
                    fDestPath.mkdirs();
                    publishProgress(PROGRESS_EXTRACT, (count*100/zipSize));
                    continue;
                }
                fDestPath.getParentFile().mkdirs();

                // write file
                try {
                    InputStream is = zip.getInputStream(ze);
                    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFullpath));
                    int n=0;
                    byte buf[] = new byte[4096];
                    while((n = is.read(buf, 0, 4096)) > -1)
                    {
                        bos.write(buf, 0, n);
                    }
                    // close
                    is.close();
                    bos.close();
                } catch(IOException ioe) {
                    m_logWriter.write("Could not write, error: "+ioe.toString());
                }

                // update progress
                publishProgress(PROGRESS_EXTRACT, (count*100/zipSize));
            }

            // close zip and bail
            zip.close();
            m_logWriter.write("Successfully extracted: "+fSettingsZip.getName()+"\n");
            m_logWriter.flush();
            result = !isCancelled();
        }
        catch(Exception e)
        {
            Log.e("SettingsDownloader", "Error: "+e.toString());
            result = false;
        }

        return result;
    }

}