// This file is part of Golly.
// See docs/License.html for the copyright notice.

package net.sf.golly;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.NavUtils;
import android.util.DisplayMetrics;
// import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.SslErrorHandler;
import android.net.http.SslError;
import android.net.Uri;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.Toast;

public class HelpActivity extends Activity {

    // see jnicalls.cpp for these native routines:
    private static native void nativeClassInit();   // this MUST be static
    private native void nativeCreate();             // the rest must NOT be static
    private native void nativeDestroy();
    private native void nativeGetURL(String url, String pageurl);
    private native void nativeProcessDownload(String filepath);
    private native void nativeUnzipFile(String zippath);
    private native boolean nativeDownloadedFile(String url);

    // local fields:
    private static boolean firstcall = true;
    private static Bundle webbundle = null;         // for saving/restoring scroll position and page history
    private boolean restarted = false;              // onRestart was called before OnResume?
    private boolean clearhistory = false;           // tell onPageFinished to clear page history?
    private WebView gwebview;                       // for displaying html pages
    private Button backbutton;                      // go to previous page
    private Button nextbutton;                      // go to next page
    private static String pageurl;                  // URL for last displayed page
    private ProgressBar progbar;                    // progress bar for downloads
    private LinearLayout proglayout;                // view containing progress bar
    private boolean cancelled;                      // download was cancelled?

    public final static String SHOWHELP_MESSAGE = "net.sf.golly.SHOWHELP";
        
    // -----------------------------------------------------------------------------
    
    static {
    	nativeClassInit();      // caches Java method IDs
    }
    
    // -----------------------------------------------------------------------------
    
    // this class lets us intercept link taps
    private class MyWebViewClient extends WebViewClient {
        @Override
        public boolean shouldOverrideUrlLoading(WebView webview, String url) {
            // look for special prefixes used by Golly and return true if found
            if (url.startsWith("open:")) {
                openFile(url.substring(5));
                return true;
            }
            if (url.startsWith("edit:")) {
                editFile(url.substring(5));
                return true;
            }
            if (url.startsWith("rule:")) {
                changeRule(url.substring(5));
                return true;
            }
            if (url.startsWith("lexpatt:")) {
                // user tapped on pattern in Life Lexicon
                loadLexiconPattern(url.substring(8));
                return true;
            }
            if (url.startsWith("get:")) {
                // download file specifed in link (possibly relative to a previous full url)
                nativeGetURL(url.substring(4), pageurl);
                return true;
            }
            if (url.startsWith("unzip:")) {
                nativeUnzipFile(url.substring(6));
                return true;
            }
            // no special prefix, so look for file with .zip/rle/lif/mc extension and download it
            if (nativeDownloadedFile(url)) {
                return true;
            }
            /* better to open http[s] links in external browser???!!!
            if (url.startsWith("http")) {
                Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                startActivity(i);
                return true;
            }
            */
            return false;
        }
        
        @Override
        public void onReceivedError(WebView webview, int errorCode, String description, String failingUrl) {
            Toast.makeText(HelpActivity.this, "Web error! " + description, Toast.LENGTH_LONG).show();
        }
        
        @Override
        public void onPageFinished(WebView webview, String url) {
            super.onPageFinished(webview, url);
            
            if (clearhistory) {
                // this only works after page is loaded
                gwebview.clearHistory();
                clearhistory = false;
            }
            backbutton.setEnabled(gwebview.canGoBack());
            nextbutton.setEnabled(gwebview.canGoForward());

            // need URL of this page for relative "get:" links
            pageurl = gwebview.getUrl();
            // Log.i("onPageFinished URL", pageurl);
        }  
        
        // this allows links in Help > References to open
        @Override
        public void onReceivedSslError(WebView webview, SslErrorHandler handler, SslError err) {
            // this will ignore all SSL certificate errors but probably prevent app from being accepted by Google
            // handler.proceed();
            
            // ask user if ok to continue
            final AlertDialog.Builder builder = new AlertDialog.Builder(HelpActivity.this);
            builder.setTitle("SSL Certificate Error");
            builder.setMessage(err.toString());
            builder.setPositiveButton("Continue", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    handler.proceed();
                }
            });
            builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    handler.cancel();
                }
            });
            final AlertDialog dialog = builder.create();
            dialog.show();
        }
    }

    // -----------------------------------------------------------------------------
    
    private void openFile(String filepath) {
        // switch to main screen and open given file
        Intent intent = new Intent(this, MainActivity.class);
        intent.putExtra(MainActivity.OPENFILE_MESSAGE, filepath);
        startActivity(intent);
    }
    
    // -----------------------------------------------------------------------------
    
    private void editFile(String filepath) {
        BaseApp baseapp = (BaseApp)getApplicationContext();
        // let user read/edit given file
        if (filepath.startsWith("Supplied/")) {
            // read contents of supplied .rule file into a string
            String prefix = baseapp.supplieddir.getAbsolutePath();
            prefix = prefix.substring(0, prefix.length() - 8);  // strip off "Supplied"
            String fullpath = prefix + filepath;
            // display filecontents
            Intent intent = new Intent(this, InfoActivity.class);
            intent.putExtra(InfoActivity.INFO_MESSAGE, fullpath);
            startActivity(intent);
        } else {
            // let user read or edit user's .rule file
            String fullpath = baseapp.userdir.getAbsolutePath() + "/" + filepath;
            Intent intent = new Intent(this, EditActivity.class);
            intent.putExtra(EditActivity.EDITFILE_MESSAGE, fullpath);
            startActivity(intent);
        }
    }

    // -----------------------------------------------------------------------------
    
    private void changeRule(String rule) {
        // switch to main screen and change rule
        Intent intent = new Intent(this, MainActivity.class);
        intent.putExtra(MainActivity.RULE_MESSAGE, rule);
        startActivity(intent);
    }
    
    // -----------------------------------------------------------------------------
    
    private void loadLexiconPattern(String pattern) {
        // switch to main screen and load given lexicon pattern
        Intent intent = new Intent(this, MainActivity.class);
        intent.putExtra(MainActivity.LEXICON_MESSAGE, pattern);
        startActivity(intent);
    }
    
    // -----------------------------------------------------------------------------
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        setContentView(R.layout.help_layout);
        backbutton = (Button) findViewById(R.id.back);
        nextbutton = (Button) findViewById(R.id.forwards);
        progbar = (ProgressBar) findViewById(R.id.progress_bar);
        proglayout = (LinearLayout) findViewById(R.id.progress_layout);
        
        proglayout.setVisibility(LinearLayout.INVISIBLE);
        
        // show the Up button in the action bar
        getActionBar().setDisplayHomeAsUpEnabled(true);
        
        nativeCreate();     // cache this instance
        
        gwebview = (WebView) findViewById(R.id.webview);
        gwebview.setWebViewClient(new MyWebViewClient());

        WebSettings settings = gwebview.getSettings();

        // JavaScript is used to detect device type
        settings.setJavaScriptEnabled(true);

        // no need???
        // settings.setDomStorageEnabled(true);

        settings.setAllowFileAccess(true); // for file URLs

        DisplayMetrics metrics = getResources().getDisplayMetrics();
        // my Nexus 7 has a density of 320
        if (metrics.densityDpi > 300) {
            // use bigger font size for high density screens (default size is 16)
            settings.setDefaultFontSize(24);
        }
        
        // allow zooming
        settings.setBuiltInZoomControls(true);

        if (firstcall) {
            firstcall = false;
            backbutton.setEnabled(false);
            nextbutton.setEnabled(false);
            showContentsPage();
            // pageurl has now been initialized
        }
    }
    
    // -----------------------------------------------------------------------------
    
    @Override
    protected void onNewIntent(Intent intent) {
    	setIntent(intent);
    	restarted = true;
    }
    
    // -----------------------------------------------------------------------------
    
    @Override
    protected void onPause() {
        super.onPause();
        gwebview.onPause();
        
        // save scroll position and back/forward history
        if (webbundle == null) webbundle = new Bundle();
        gwebview.saveState(webbundle);
    }

    // -----------------------------------------------------------------------------
    
    @Override
    protected void onResume() {
        super.onResume();
        gwebview.onResume();

        // restore scroll position and back/forward history
        if (webbundle != null && !restarted) {
            gwebview.restoreState(webbundle);
        }
        restarted = false;

        // check for messages sent by other activities
        Intent intent = getIntent();
        String filepath = intent.getStringExtra(SHOWHELP_MESSAGE);
        if (filepath != null) {
            // replace any spaces with %20
            filepath = filepath.replaceAll(" ", "%20");
            // Log.i("onResume filepath", filepath);
            gwebview.loadUrl("file://" + filepath);
        } else {
            // no need: gwebview.reload();
        }
    }

    // -----------------------------------------------------------------------------

    @Override
    protected void onRestart() {
        super.onRestart();
        // set flag to prevent onResume calling gwebview.restoreState
        // causing app to crash for some unknown reason
        restarted = true;
    }

    // -----------------------------------------------------------------------------

    @Override
    protected void onDestroy() {
        nativeDestroy();
        super.onDestroy();
    }

    // -----------------------------------------------------------------------------

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // add main.xml items to the action bar
        getMenuInflater().inflate(R.menu.main, menu);
        
        // disable the item for this activity
        MenuItem item = menu.findItem(R.id.help);
        item.setEnabled(false);
        
        return true;
    }

    // -----------------------------------------------------------------------------

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // action bar item has been tapped
        Intent intent;
        int id = item.getItemId();
        if (id == android.R.id.home) {
            // the Home or Up button will go back to MainActivity
            NavUtils.navigateUpFromSameTask(this);
            return true;
        } else if (id == R.id.open) {
            finish();
            intent = new Intent(this, OpenActivity.class);
            startActivity(intent);
            return true;
        } else if (id == R.id.settings) {
            finish();
            intent = new Intent(this, SettingsActivity.class);
            startActivity(intent);
            return true;
        } else if (id == R.id.help) {
            // do nothing
            ;
        }
        return super.onOptionsItemSelected(item);
    }
    
    // -----------------------------------------------------------------------------
    
    private void showContentsPage() {
        BaseApp baseapp = (BaseApp)getApplicationContext();
        // display html data in supplieddir/Help/index.html
        String fullpath = baseapp.supplieddir.getAbsolutePath() + "/Help/index.html";
        File htmlfile = new File(fullpath);
        if (htmlfile.exists()) {
            gwebview.loadUrl("file://" + fullpath);     // fullpath starts with "/"
        } else {
            // should never happen
            String htmldata = "<html><center><font size=+1 color=\"red\">Failed to find index.html!</font></center></html>";
            gwebview.loadDataWithBaseURL(null, htmldata, "text/html", "utf-8", null);
        }
    }
    
    // -----------------------------------------------------------------------------
    
    // called when the Contents button is tapped
    public void doContents(View view) {
        clearhistory = true;    // tell onPageFinished to clear page history
        showContentsPage();
        if (webbundle != null) {
            webbundle.clear();
            webbundle = null;
        }
    }
    
    // -----------------------------------------------------------------------------
    
    // called when backbutton is tapped
    public void doBack(View view) {
        gwebview.goBack();
    }
    
    // -----------------------------------------------------------------------------
    
    // called when nextbutton is tapped
    public void doForwards(View view) {
        gwebview.goForward();
    }
    
    // -----------------------------------------------------------------------------
    
    // called when Cancel is tapped
    public void doCancel(View view) {
        cancelled = true;
    }
    
    // -----------------------------------------------------------------------------

    private String downloadURL(String urlstring, String filepath) {
        // download given url and save data in given file
        try {
            File outfile = new File(filepath);
            final int BUFFSIZE = 8192;
            FileOutputStream outstream = null;
            try {
                outstream = new FileOutputStream(outfile);
            } catch (FileNotFoundException e) {
                return "File not found: " + filepath;
            }
            
            long starttime = System.nanoTime();
            // Log.i("downloadURL", urlstring);
            URL url = new URL(urlstring);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setAllowUserInteraction(false);
            connection.setInstanceFollowRedirects(true);
            connection.setRequestMethod("GET");
            connection.connect();
            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                outstream.close();
                return "Bad HTTP response: " + connection.getResponseCode();
            }
            
            // init info for progress bar
            int filesize = connection.getContentLength();
            int downloaded = 0;
            int percent;
            int lastpercent = 0;
            
            // stream the data to given file
            InputStream instream = connection.getInputStream();
            byte[] buffer = new byte[BUFFSIZE];
            int bufflen = 0;
            while ((bufflen = instream.read(buffer, 0, BUFFSIZE)) > 0) {
                outstream.write(buffer, 0, bufflen);
                downloaded += bufflen;
                percent = (int) ((downloaded / (float)filesize) * 100);
                if (percent > lastpercent) {
                    progbar.setProgress(percent);
                    lastpercent = percent;
                }
                // show proglayout only if download takes more than 1 second
                if (System.nanoTime() - starttime > 1000000000L) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            proglayout.setVisibility(LinearLayout.VISIBLE);
                        }
                    });
                    starttime = Long.MAX_VALUE; // only show it once
                }

                if (cancelled) break;
            }
            outstream.close();
            connection.disconnect();
            if (cancelled) return "Cancelled.";
            
        } catch (MalformedURLException e) {
            return "Bad URL string:\n" + urlstring;
        } catch (IOException e) {
            return "Error connecting to URL:\n" + e.toString();
        }
        return "";  // success
    }
    
    // -----------------------------------------------------------------------------

    private String dresult;

    // this method is called from C++ code (see jnicalls.cpp)
    private void DownloadFile(String urlstring, String filepath) {
        cancelled = false;
        progbar.setProgress(0);
        // don't show proglayout immediately
        // proglayout.setVisibility(LinearLayout.VISIBLE);

        // we cannot do network connections on main thread
        final Handler handler = new Handler();
        final String durl = urlstring;
        final String dfile = filepath;
        Thread download_thread = new Thread(new Runnable() {
            @Override
            public void run() {
                dresult = downloadURL(durl, dfile);
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        proglayout.setVisibility(LinearLayout.INVISIBLE);
                        if (dresult.length() == 0) {
                            // download succeeded
                            nativeProcessDownload(dfile);
                        } else if (!cancelled) {
                            Toast.makeText(HelpActivity.this, "Download failed! " + dresult, Toast.LENGTH_LONG).show();
                        }
                    }
                });
            }
        });
        
        download_thread.setPriority(Thread.MAX_PRIORITY);
        download_thread.start();
    }

} // HelpActivity class
