content: // This file is part of Golly.
  // See docs/License.html for the copyright notice.
  
  package net.sf.golly;
  
  import java.io.File;
  import java.util.Arrays;
  
  import android.app.Activity;
  import android.app.AlertDialog;
  import android.content.DialogInterface;
  import android.content.Intent;
  import android.os.Bundle;
  import android.os.Handler;
  import android.util.DisplayMetrics;
  import android.util.Log;
  import android.view.KeyEvent;
  import android.view.Menu;
  import android.view.MenuItem;
  import android.view.View;
  import android.view.inputmethod.EditorInfo;
  import android.webkit.WebSettings;
  import android.webkit.WebView;
  import android.webkit.WebViewClient;
  import android.widget.Button;
  import android.widget.EditText;
  import android.widget.PopupMenu;
  import android.widget.TextView;
  import android.widget.TextView.OnEditorActionListener;
  import android.widget.Toast;
  
  public class RuleActivity extends Activity {
  
      // see jnicalls.cpp for these native routines:
      private native void nativeSaveCurrentSelection();
      private native int nativeGetAlgoIndex();
      private native String nativeGetAlgoName(int algoindex);
      private native String nativeGetRule();
      private native String nativeCheckRule(String rule, int algoindex);
      private native int nativeCheckAlgo(String rule, int algoindex);
      private native void nativeSetRule(String rule, int algoindex);
      
      // local fields:
      private static boolean firstcall = true;
      private static int algoindex = 0;           // index of currently displayed algorithm
      private static int numalgos;                // number of algorithms
      private static int[] scroll_pos;            // remember scroll position for each algo
      private Button algobutton;                  // for changing current algorithm
      private EditText ruletext;                  // for changing current rule
      private WebView gwebview;                   // for displaying html data
      
      private String bad_rule = "Given rule is not valid in any algorithm.";
      
      // -----------------------------------------------------------------------------
      
      // this class lets us intercept link taps and restore the scroll position
      private class MyWebViewClient extends WebViewClient {
          @Override
          public boolean shouldOverrideUrlLoading(WebView webview, String url) {
              if (url.startsWith("open:")) {
                  openFile(url.substring(5));
                  return true;
              }
              if (url.startsWith("delete:")) {
                  removeFile(url.substring(7));
                  return true;
              }
              if (url.startsWith("edit:")) {
                  editFile(url.substring(5));
                  return true;
              }
              if (url.startsWith("rule:")) {
                  // copy specified rule into ruletext
                  ruletext.setText(url.substring(5));
                  ruletext.setSelection(ruletext.getText().length());
                  checkAlgo();
                  return true;
              }
              return false;
          }
          
          @Override  
          public void onPageFinished(WebView webview, String url) {
              super.onPageFinished(webview, url);
              // webview.scrollTo doesn't always work here;
              // we need to delay until webview.getContentHeight() > 0
              final int scrollpos = restoreScrollPosition();
              if (scrollpos > 0) {
                  final Handler handler = new Handler();
                  Runnable runnable = new Runnable() {
                      public void run() {
                          if (gwebview.getContentHeight() > 0) {
                              gwebview.scrollTo(0, scrollpos);
                          } else {
                              // try again a bit later
                              handler.postDelayed(this, 100);
                          }
                      }
                  };
                  handler.postDelayed(runnable, 100);
              }
          }  
      }
  
      // -----------------------------------------------------------------------------
      
      private void saveScrollPosition() {
          scroll_pos[algoindex] = gwebview.getScrollY();
      }
      
      // -----------------------------------------------------------------------------
      
      private int restoreScrollPosition() {
          return scroll_pos[algoindex];
      }
  
      // -----------------------------------------------------------------------------
      
      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 removeFile(String filepath) {
          BaseApp baseapp = (BaseApp)getApplicationContext();
          final String fullpath = baseapp.userdir.getAbsolutePath() + "/" + filepath;
          final File file = new File(fullpath);
          
          // ask user if it's okay to delete given file
          AlertDialog.Builder alert = new AlertDialog.Builder(this);
          alert.setTitle("Delete file?");
          alert.setMessage("Do you really want to delete " + file.getName() + "?");
          alert.setPositiveButton("DELETE",
              new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog, int id) {
                      if (file.delete()) {
                          // file has been deleted so refresh gwebview
                          saveScrollPosition();
                          showAlgoHelp();
                      } else {
                          // should never happen
                          Log.e("removeFile", "Failed to delete file: " + fullpath);
                      }
                  }
              });
          alert.setNegativeButton("CANCEL", null);
          alert.show();
      }
      
      // -----------------------------------------------------------------------------
      
      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);
          }
      }
  
      // -----------------------------------------------------------------------------
  
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.rule_layout);
          algobutton = (Button) findViewById(R.id.algo);
          ruletext = (EditText) findViewById(R.id.rule);
  
          gwebview = (WebView) findViewById(R.id.webview);
          gwebview.setWebViewClient(new MyWebViewClient());
  
          WebSettings settings = gwebview.getSettings();
  
          // no need for JavaScript???
          // settings.setJavaScriptEnabled(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);
  
          getActionBar().hide();
  
          if (firstcall) {
              firstcall = false;
              initAlgoInfo();
          }
  
          algoindex = nativeGetAlgoIndex();
          algobutton.setText(nativeGetAlgoName(algoindex));
          // onResume will call showAlgoHelp
          
          ruletext.setText(nativeGetRule());
          ruletext.setSelection(ruletext.getText().length());     // put cursor at end of rule
  
          // following listener allows us to detect when user is closing the soft keyboard
          // (it assumes rule_layout.xml sets android:imeOptions="actionDone")
          ruletext.setOnEditorActionListener(new OnEditorActionListener() {
              @Override
              public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                  if (actionId == EditorInfo.IME_ACTION_DONE) {
                      checkAlgo();
                  }
                  return false;
              }
          });
          
          // selection might change if grid becomes smaller, so save current selection
          // in case RememberRuleChange/RememberAlgoChange is called later
          nativeSaveCurrentSelection();
      }
  
      // -----------------------------------------------------------------------------
  
      @Override
      protected void onPause() {
          super.onPause();
          saveScrollPosition();
      }
  
      // -----------------------------------------------------------------------------
  
      @Override
      protected void onResume() {
          super.onResume();
          restoreScrollPosition();
          showAlgoHelp();
      }
      
      // -----------------------------------------------------------------------------
  
      private void initAlgoInfo() {
          // initialize number of algorithms and scroll positions
          numalgos = 0;
          while (true) {
              String algoname = nativeGetAlgoName(numalgos);
              if (algoname.length() == 0) break;
              numalgos++;
          }
          scroll_pos = new int[numalgos];
          for (int i = 0; i < numalgos; i++) {
              scroll_pos[i] = 0;
          }
      }
      
      // -----------------------------------------------------------------------------
      
      private String createRuleLinks(File dir, String prefix, String title, boolean candelete) {
          String htmldata = "<p>" + title + "</p><p>";
          int rulecount = 0;
          File[] files = dir.listFiles();
          if (files != null) {
              Arrays.sort(files);         // sort into alphabetical order
              for (File file : files) {
                  if (file.isDirectory()) {
                      // ignore directory
                  } else {
                      String filename = file.getName();
                      if (filename.endsWith(".rule")) {
                          if (candelete) {
                              // allow user to delete .rule file
                              htmldata += "<a href=\"delete:";
                              htmldata += prefix;
                              htmldata += filename;
                              htmldata += "\"><font size=-2 color='red'>DELETE</font></a>&nbsp;&nbsp;&nbsp;";
                              // allow user to edit .rule file
                              htmldata += "<a href=\"edit:";
                              htmldata += prefix;
                              htmldata += filename;
                              htmldata += "\"><font size=-2 color='green'>EDIT</font></a>&nbsp;&nbsp;&nbsp;";
                          } else {
                              // allow user to read supplied .rule file
                              htmldata += "<a href=\"edit:";
                              htmldata += prefix;
                              htmldata += filename;
                              htmldata += "\"><font size=-2 color='green'>READ</font></a>&nbsp;&nbsp;&nbsp;";
                          }
                          // use "open:" link rather than "rule:" link
                          htmldata += "<a href=\"open:";
                          htmldata += prefix;
                          htmldata += filename;
                          htmldata += "\">";
                          htmldata += filename.substring(0, filename.length() - 5);    // strip off .rule
                          htmldata += "</a><br>";
                          rulecount++;
                      }
                  }
              }
          }
          if (rulecount == 0) htmldata += "</b>None.<b>";
          return htmldata;
      }
      
      // -----------------------------------------------------------------------------
      
      private void showAlgoHelp() {
          BaseApp baseapp = (BaseApp)getApplicationContext();
          String algoname = nativeGetAlgoName(algoindex);
          if (algoname.equals("RuleLoader")) {
              // create html data with links to the user's .rule files and the supplied .rule files
              File userrules = new File(baseapp.userdir, "Rules");
              File rulesdir = new File(baseapp.supplieddir, "Rules");
              String htmldata = "";
              htmldata += "<html><body bgcolor=\"#FFFFCE\"><a name=\"top\"></a>";
              htmldata += "<p>The RuleLoader algorithm allows CA rules to be specified in .rule files.";
              htmldata += " Given the rule string \"Foo\", RuleLoader will search for a file called Foo.rule";
              htmldata += " in your rules folder, then in the rules folder supplied with Golly.";
              htmldata += "</p><font color=\"black\"><b>";
              
              htmldata += createRuleLinks(userrules, "Rules/", "Your .rule files:", true);
              htmldata += createRuleLinks(rulesdir, "Supplied/Rules/", "Supplied .rule files:", false);
              // note that we use a prefix starting with "Supplied/" so editFile can distinguish
              // between a user rule and a supplied rule
              
              htmldata += "</b></font></body></html>";
              gwebview.loadDataWithBaseURL(null, htmldata, "text/html", "utf-8", null);
          } else {
              // replace any spaces with underscores
              algoname = algoname.replaceAll(" ", "_");
              // display html data in supplieddir/Help/Algorithms/algoname.html
              String fullpath = baseapp.supplieddir.getAbsolutePath() + "/Help/Algorithms/" + algoname + ".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 color=\"red\">Failed to find html file!</font></center></html>";
                  gwebview.loadDataWithBaseURL(null, htmldata, "text/html", "utf-8", null);
              }
          }
      }
      
      // -----------------------------------------------------------------------------
      
      // called when algobutton is tapped
      public void doAlgo(View view) {
          // display pop-up menu with algorithm names
          PopupMenu popup = new PopupMenu(this, view);
          for (int id = 0; id < numalgos; id++) {
              String algoname = nativeGetAlgoName(id);
              popup.getMenu().add(Menu.NONE, id, Menu.NONE, algoname);
          }
          
          popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
              @Override
              public boolean onMenuItemClick(MenuItem item) {
                  int id = item.getItemId();
                  String algoname = nativeGetAlgoName(id);
                  if (id != algoindex && algoname.length() > 0) {
                      // change algorithm
                      saveScrollPosition();
                      algoindex = id;
                      algobutton.setText(algoname);
                      showAlgoHelp();
                      // rule needs to change if it isn't valid in new algo
                      String currentrule = ruletext.getText().toString();
                      String newrule = nativeCheckRule(currentrule, algoindex);
                      if (!newrule.equals(currentrule)) {
                          ruletext.setText(newrule);
                          ruletext.setSelection(ruletext.getText().length());
                      }
                  }
                  return true;
              }
          });
          
          popup.show();
      }
      
      // -----------------------------------------------------------------------------
  
      private void checkAlgo() {
          // check displayed rule and show message if it's not valid in any algo,
          // or if the rule is valid then displayed algo might need to change
          String rule = ruletext.getText().toString();
          if (rule.length() == 0) {
              // change empty rule to Life
              rule = "B3/S23";
              ruletext.setText(rule);
              ruletext.setSelection(ruletext.getText().length());
          }
          int newalgo = nativeCheckAlgo(rule, algoindex);
          if (newalgo == -1) {
              Toast.makeText(this, bad_rule, Toast.LENGTH_LONG).show();
          } else if (newalgo != algoindex) {
              // change algorithm
              saveScrollPosition();
              algoindex = newalgo;
              algobutton.setText(nativeGetAlgoName(algoindex));
              showAlgoHelp();
          }
      }
      
      // -----------------------------------------------------------------------------
      
      // called when the Done button is tapped (not the soft keyboard's Done button)
      public void doDone(View view) {
          String rule = ruletext.getText().toString();
          // no need to check for empty rule here
          int newalgo = nativeCheckAlgo(rule, algoindex);
          if (newalgo == -1) {
              Toast.makeText(this, bad_rule, Toast.LENGTH_LONG).show();
          } else {
              // dismiss dialog first in case ChangeAlgorithm calls BeginProgress
              finish();
              nativeSetRule(rule, newalgo);
          }
      }
      
      // -----------------------------------------------------------------------------
      
      // called when the Cancel button is tapped
      public void doCancel(View view) {
          finish();
      }
  
  } // RuleActivity class
  