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

package net.sf.golly;

import java.io.File;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Typeface;
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
import android.text.InputType;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class MainActivity 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 nativeStartGenerating();
    private native void nativeStopGenerating();
    private native void nativeStopBeforeNew();
    private native void nativePauseGenerating();
    private native void nativeResumeGenerating();
    private native void nativeResetPattern();
    private native void nativeUndo();
    private native void nativeRedo();
    private native boolean nativeCanReset();
    private native boolean nativeAllowUndo();
    private native boolean nativeCanUndo();
    private native boolean nativeCanRedo();
    private native boolean nativeInfoAvailable();
    private native boolean nativeIsGenerating();
    private native int nativeGetStatusColor();
    private native String nativeGetStatusLine(int line);
    private native String nativeGetPasteMode();
    private native String nativeGetRandomFill();
    private native void nativeNewPattern();
    private native void nativeFitPattern();
    private native void nativeGenerate();
    private native void nativeStep();
    private native void nativeStep1();
    private native void nativeFaster();
    private native void nativeSlower();
    private native void nativeScale1to1();
    private native void nativeBigger();
    private native void nativeSmaller();
    private native void nativeMiddle();
    private native void nativeSetMode(int mode);
    private native int nativeGetMode();
    private native int nativeCalculateSpeed();
    private native int nativeNumLayers();
    private native boolean nativePasteExists();
    private native boolean nativeSelectionExists();
    private native void nativePaste();
    private native void nativeSelectAll();
    private native void nativeRemoveSelection();
    private native void nativeCutSelection();
    private native void nativeCopySelection();
    private native void nativeClearSelection(int inside);
    private native void nativeShrinkSelection();
    private native void nativeFitSelection();
    private native void nativeRandomFill();
    private native void nativeFlipSelection(int y);
    private native void nativeRotateSelection(int clockwise);
    private native void nativeAdvanceSelection(int inside);
    private native void nativeAbortPaste();
    private native void nativeDoPaste(int toselection);
    private native void nativeFlipPaste(int y);
    private native void nativeRotatePaste(int clockwise);
    private native void nativeClearMessage();
    private native String nativeGetValidExtensions();
    private native boolean nativeValidExtension(String filename);
    private native boolean nativeFileExists(String filename);
    private native void nativeSavePattern(String filename);
    private native void nativeOpenFile(String filepath);
    private native void nativeSetFullScreen(boolean fullscreen);
    private native void nativeChangeRule(String rule);
    private native void nativeLexiconPattern(String pattern);
    private native int nativeDrawingState();

    // local fields:
    private static boolean fullscreen = false;      // in full screen mode?
    private boolean widescreen;                     // use different layout for wide screens?
    private Button ssbutton;                        // Start/Stop button
    private Button resetbutton;                     // Reset button (used if wide screen)
    private Button undobutton;                      // Undo button
    private Button redobutton;                      // Redo button
    private Button editbutton;                      // Edit/Paste button
    private Button statebutton;                     // button to change drawing state
    private Button modebutton;                      // Draw/Pick/Select/Move button
    private Button drawbutton;                      // Draw button (used if wide screen)
    private Button pickbutton;                      // Pick button (used if wide screen)
    private Button selectbutton;                    // Select button (used if wide screen)
    private Button movebutton;                      // Move button (used if wide screen)
    private Button infobutton;                      // Info button
    private Button restorebutton;                   // Restore button
    private TextView status1, status2, status3;     // status bar has 3 lines
    private int statuscolor = 0xFF000000;           // background color of status bar
    private PatternGLSurfaceView pattView;          // OpenGL ES is used to draw patterns
    private Handler genhandler;                     // for generating patterns
    private Runnable generate;                      // code started/stopped by genhandler
    private int geninterval;                        // interval between nativeGenerate calls (in millisecs)
    private Handler callhandler;                    // for calling a method again
    private Runnable callagain;                     // code that calls method
    private String methodname;                      // name of method to call
    private View currview;                          // current view parameter
    private MenuItem curritem;                      // current menu item parameter
    private PopupMenu popup;                        // used for all pop-up menus
    private boolean stopped = true;                 // generating is stopped?

    // -----------------------------------------------------------------------------
    
    // this stuff is used to display a progress bar:
    
    private static boolean cancelProgress = false;  // cancel progress dialog?
    private static long progstart, prognext;        // for progress timing
    private static int progresscount = 0;           // if > 0 then BeginProgress has been called
    private TextView progtitle;                     // title above progress bar
    private ProgressBar progbar;                    // progress bar
    private LinearLayout proglayout;                // view containing progress bar

    // -----------------------------------------------------------------------------
    
    // this stuff is used in other activities:
    
    public final static String OPENFILE_MESSAGE = "net.sf.golly.OPENFILE";
    public final static String RULE_MESSAGE = "net.sf.golly.RULE";
    public final static String LEXICON_MESSAGE = "net.sf.golly.LEXICON";
    
    // -----------------------------------------------------------------------------

    // the following stuff allows time consuming code (like nativeGenerate) to periodically
    // check if any user events need to be processed, but without blocking the UI thread
    // (thanks to http://stackoverflow.com/questions/4994263/how-can-i-do-non-blocking-events-processing-on-android)
    
    private boolean processingevents = false;
    private Handler evthandler = null;

    private Runnable doevents = new Runnable() {
        public void run() {
            Looper looper = Looper.myLooper();
            looper.quit();
            evthandler.removeCallbacks(this);
            evthandler = null;
        }
    };

    private class IdleHandler implements MessageQueue.IdleHandler {
        private Looper idlelooper;
        protected IdleHandler(Looper looper) {
            idlelooper = looper;
        }
        public boolean queueIdle() {
            evthandler = new Handler(idlelooper);
            evthandler.post(doevents);
            return(false);
        }
    };

    // this method is called from C++ code (see jnicalls.cpp)
    private void CheckMessageQueue() {
        // process any pending UI events in message queue
        if (!processingevents) {
            Looper looper = Looper.myLooper();
            looper.myQueue().addIdleHandler(new IdleHandler(looper));
            processingevents = true;
            try {
                looper.loop();
            } catch (RuntimeException re) {
                // looper.quit() in doevents causes an exception
            }
            processingevents = false;
        }
    }

    // -----------------------------------------------------------------------------
    
    static {
        nativeClassInit();      // caches Java method IDs
    }

    // -----------------------------------------------------------------------------
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // Log.i("Golly","screen density in dpi = " + Integer.toString(metrics.densityDpi));
        // eg. densityDpi = 320 on Nexus 7
        
        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
        float dpWidth = displayMetrics.widthPixels / displayMetrics.density;

        // Log.i("Golly","screen width in dp = " + Integer.toString(config.screenWidthDp));
        // eg. on Nexus 7 screenWidthDp = 600 in portrait, 960 in landscape
        widescreen = dpWidth >= 600;
        
        if (widescreen) {
            // use a layout that has more buttons
            setContentView(R.layout.main_layout_wide);
            resetbutton = (Button) findViewById(R.id.reset);
            drawbutton = (Button) findViewById(R.id.drawmode);
            pickbutton = (Button) findViewById(R.id.pickmode);
            selectbutton = (Button) findViewById(R.id.selectmode);
            movebutton = (Button) findViewById(R.id.movemode);
            if (dpWidth >= 800) {
                // show nicer text in some buttons
                ((Button) findViewById(R.id.slower)).setText("Slower");
                ((Button) findViewById(R.id.faster)).setText("Faster");
                ((Button) findViewById(R.id.smaller)).setText("Smaller");
                ((Button) findViewById(R.id.scale1to1)).setText("Scale=1:1");
                ((Button) findViewById(R.id.bigger)).setText("Bigger");
                ((Button) findViewById(R.id.middle)).setText("Middle");
            }
        } else {
            // screen isn't wide enough to have all the buttons we'd like
            setContentView(R.layout.main_layout);
            modebutton = (Button) findViewById(R.id.touchmode);
        }
        
        // following stuff is used in both layouts
        ssbutton = (Button) findViewById(R.id.startstop);
        undobutton = (Button) findViewById(R.id.undo);
        redobutton = (Button) findViewById(R.id.redo);
        editbutton = (Button) findViewById(R.id.edit);
        statebutton = (Button) findViewById(R.id.state);
        infobutton = (Button) findViewById(R.id.info);
        restorebutton = (Button) findViewById(R.id.restore);
        status1 = (TextView) findViewById(R.id.status1);
        status2 = (TextView) findViewById(R.id.status2);
        status3 = (TextView) findViewById(R.id.status3);
        progbar = (ProgressBar) findViewById(R.id.progress_bar);
        proglayout = (LinearLayout) findViewById(R.id.progress_layout);
        progtitle = (TextView) findViewById(R.id.progress_title);
        
        nativeCreate();         // must be called every time (to cache this instance)
        
        // this will call the PatternGLSurfaceView constructor
        pattView = (PatternGLSurfaceView) findViewById(R.id.patternview);
        
        restorebutton.setVisibility(View.INVISIBLE);
        proglayout.setVisibility(LinearLayout.INVISIBLE);

        // create handler and runnable for generating patterns
        geninterval = nativeCalculateSpeed();
        genhandler = new Handler();
        generate = new Runnable() {
            public void run() {
                if (!stopped) {
                    nativeGenerate();
                    genhandler.postDelayed(this, geninterval);
                    // nativeGenerate will be called again after given interval
                }
            }
        };
        
        // create handler and runnable for calling a method again when the user
        // invokes certain events while the next generation is being calculated
        callhandler = new Handler();
        callagain = new Runnable() {
            public void run() {
                if (methodname.equals("doStartStop")) doStartStop(currview); else
                if (methodname.equals("doStep"))      doStep(currview); else
                if (methodname.equals("doNew"))       doNewPattern(currview); else
                if (methodname.equals("doUndo"))      doUndo(currview); else
                if (methodname.equals("doRedo"))      doRedo(currview); else
                if (methodname.equals("doRule"))      doRule(currview); else
                if (methodname.equals("doInfo"))      doInfo(currview); else
                if (methodname.equals("doSave"))      doSave(currview); else
                if (methodname.equals("doReset"))     doReset(currview); else
                if (methodname.equals("doPaste"))     doPaste(currview); else
                if (methodname.equals("doSelItem"))   doSelectionItem(curritem); else
                // string mismatch
                Log.e("Fix callagain", methodname);
            }
        };
    }
    
    // -----------------------------------------------------------------------------
    
    @Override
    protected void onNewIntent(Intent intent) {
    	// check for messages sent by other activities
    	String filepath = intent.getStringExtra(OPENFILE_MESSAGE);
    	if (filepath != null) {
    	    nativeOpenFile(filepath);
    	}
    	String rule = intent.getStringExtra(RULE_MESSAGE);
    	if (rule != null) {
    	    nativeChangeRule(rule);
    	}
    	String pattern = intent.getStringExtra(LEXICON_MESSAGE);
    	if (pattern != null) {
    	    nativeLexiconPattern(pattern);
    	}
    }
    
    // -----------------------------------------------------------------------------

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // add main.xml items to the action bar
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

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

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // action bar item has been tapped
        nativeClearMessage();
        Intent intent;
        int id = item.getItemId();
        if (id == R.id.open) {
            intent = new Intent(this, OpenActivity.class);
            startActivity(intent);
            return true;
        } else if (id == R.id.settings) {
            intent = new Intent(this, SettingsActivity.class);
            startActivity(intent);
            return true;
        } else if (id == R.id.help) {
            intent = new Intent(this, HelpActivity.class);
            startActivity(intent);
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
    
    // -----------------------------------------------------------------------------
    
    @Override
    protected void onPause() {
        super.onPause();
        pattView.onPause();
        stopped = true;
    }

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

    @Override
    protected void onResume() {
        super.onResume();
        pattView.onResume();
        
        if (fullscreen) {
            fullscreen = false;         // following will set it true
            toggleFullScreen(null);
        } else {
            updateButtons();
            UpdateEditBar();
        }
        
        if (nativeIsGenerating()) {
            stopped = false;
            genhandler.post(generate);
        }
    }

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

    @Override
    protected void onDestroy() {
        stopped = true;             // should have been done in OnPause, but play safe
        nativeDestroy();
        super.onDestroy();
    }

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

    private void deleteTempFiles() {
        File dir = getCacheDir();
        File[] files = dir.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.getName().startsWith("GET-") || file.getName().endsWith(".html")) {
                    // don't delete files created via "get:" links in HelpActivity
                } else {
                    file.delete();
                }
            }
        }
    }

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

    private void updateButtons() {
        if (fullscreen) return;
        
        if (nativeIsGenerating()) {
            ssbutton.setText("Stop");
            ssbutton.setTextColor(Color.rgb(255,0,0));
            if (widescreen) resetbutton.setEnabled(true);
            undobutton.setEnabled(nativeAllowUndo());
            redobutton.setEnabled(false);
        } else {
            ssbutton.setText("Start");
            ssbutton.setTextColor(Color.rgb(0,255,0));
            if (widescreen) resetbutton.setEnabled(nativeCanReset());
            undobutton.setEnabled(nativeAllowUndo() && (nativeCanReset() || nativeCanUndo()));
            redobutton.setEnabled(nativeCanRedo());
        }
        infobutton.setEnabled(nativeInfoAvailable());
    }

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

    private void updateGeneratingSpeed() {
        geninterval = nativeCalculateSpeed();
        genhandler.removeCallbacks(generate);
        stopped = false;
        if (nativeIsGenerating()) genhandler.post(generate);
    }

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

    private boolean callAgainAfterDelay(String callname, View view, MenuItem item) {
        if (processingevents) {
            // CheckMessageQueue has been called inside a (possibly) lengthy task
            // so call the given method again after a short delay
            methodname = callname;
            if (view != null) currview = view;
            if (item != null) curritem = item;
            callhandler.postDelayed(callagain, 5);    // call after 5 millisecs
            return true;
        } else {
            return false;
        }
    }

    // -----------------------------------------------------------------------------
    
    // called when the Start/Stop button is tapped
    public void doStartStop(View view) {
        nativeClearMessage();
        if (callAgainAfterDelay("doStartStop", view, null)) return;
        if (nativeIsGenerating()) {
            // stop calling nativeGenerate
            stopped = true;
            nativeStopGenerating();
        } else {
            nativeStartGenerating();
            // nativeIsGenerating() might still be false (eg. if pattern is empty)
            if (nativeIsGenerating()) {
                // start calling nativeGenerate
                geninterval = nativeCalculateSpeed();
                genhandler.removeCallbacks(generate);   // probably unnecessary here but play safe
                stopped = false;
                genhandler.post(generate);
            }
        }
        updateButtons();
    }

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

    private void stopIfGenerating() {
        if (nativeIsGenerating()) {
            // note that genhandler.removeCallbacks(generate) doesn't always work if
            // processingevents is true, so we use a global flag to start/stop
            // making calls to nativeGenerate
            stopped = true;
            nativeStopGenerating();
        }
    }

    // -----------------------------------------------------------------------------
    
    // called when the Step button is tapped
    public void doStep(View view) {
        nativeClearMessage();
        stopIfGenerating();
        if (callAgainAfterDelay("doStep", view, null)) return;
        nativeStep();
        updateButtons();
    }

    // -----------------------------------------------------------------------------
    
    // called when the Slower button is tapped
    public void doSlower(View view) {
        nativeClearMessage();
        nativeSlower();
        updateGeneratingSpeed();
    }

    // -----------------------------------------------------------------------------
    
    // called when the Step=1 button is tapped
    public void doStep1(View view) {
        nativeClearMessage();
        nativeStep1();
        updateGeneratingSpeed();
    }

    // -----------------------------------------------------------------------------
    
    // called when the Faster button is tapped
    public void doFaster(View view) {
        nativeClearMessage();
        nativeFaster();
        updateGeneratingSpeed();
    }

    // -----------------------------------------------------------------------------
    
    // called when the Reset button is tapped
    public void doReset(View view) {
        nativeClearMessage();
        stopIfGenerating();
        if (callAgainAfterDelay("doReset", view, null)) return;
        nativeResetPattern();
        updateButtons();
    }

    // -----------------------------------------------------------------------------
    
    // called when the Control button is tapped
    public void doControl(View view) {
        nativeClearMessage();
        // display pop-up menu with these items: Step=1, Faster, Slower, Reset
        popup = new PopupMenu(this, view);
        MenuInflater inflater = popup.getMenuInflater();
        inflater.inflate(R.menu.control_menu, popup.getMenu());
        popup.show();
    }

    // -----------------------------------------------------------------------------
    
    // called when item from control_menu is selected
    public void doControlItem(MenuItem item) {
        if (item.getItemId() == R.id.reset) {
            stopIfGenerating();
            if (callAgainAfterDelay("doReset", null, item)) return;
        }
        popup.dismiss();
        int id = item.getItemId();
        if (id == R.id.step1) {
            nativeStep1();
        } else if (id == R.id.faster) {
            nativeFaster();
        } else if (id == R.id.slower) {
            nativeSlower();
        } else if (id == R.id.reset) {
            nativeResetPattern();
        } else {
            Log.e("Golly","Fix bug in doControlItem!");
        }
        if (item.getItemId() == R.id.reset) {
            updateButtons();
        } else {
            updateGeneratingSpeed();
        }
    }

    // -----------------------------------------------------------------------------
    
    // called when the Fit button is tapped
    public void doFitPattern(View view) {
        nativeClearMessage();
        nativeFitPattern();
    }

    // -----------------------------------------------------------------------------
    
    // called when the Smaller button is tapped
    public void doSmaller(View view) {
        nativeClearMessage();
        nativeSmaller();
    }

    // -----------------------------------------------------------------------------
    
    // called when the Scale=1:1 button is tapped
    public void doScale1to1(View view) {
        nativeClearMessage();
        nativeScale1to1();
    }

    // -----------------------------------------------------------------------------
    
    // called when the Bigger button is tapped
    public void doBigger(View view) {
        nativeClearMessage();
        nativeBigger();
    }

    // -----------------------------------------------------------------------------
    
    // called when the Middle button is tapped
    public void doMiddle(View view) {
        nativeClearMessage();
        nativeMiddle();
    }

    // -----------------------------------------------------------------------------
    
    // called when the View button is tapped
    public void doView(View view) {
        nativeClearMessage();
        // display pop-up menu with these items: Scale=1:1, Bigger, Smaller, Middle
        popup = new PopupMenu(this, view);
        MenuInflater inflater = popup.getMenuInflater();
        inflater.inflate(R.menu.view_menu, popup.getMenu());
        popup.show();
    }

    // -----------------------------------------------------------------------------
    
    // called when item from view_menu is selected
    public void doViewItem(MenuItem item) {
        popup.dismiss();
        int id = item.getItemId();
        if (id == R.id.scale1to1) {
            nativeScale1to1();
        } else if (id == R.id.bigger) {
            nativeBigger();
        } else if (id == R.id.smaller) {
            nativeSmaller();
        } else if (id == R.id.middle) {
            nativeMiddle();
        } else {
            Log.e("Golly","Fix bug in doViewItem!");
        }
    }

    // -----------------------------------------------------------------------------
    
    // called when the Undo button is tapped
    public void doUndo(View view) {
        nativeClearMessage();
        stopIfGenerating();
        if (callAgainAfterDelay("doUndo", view, null)) return;
        nativeUndo();
        updateButtons();
        
    }

    // -----------------------------------------------------------------------------
    
    // called when the Redo button is tapped
    public void doRedo(View view) {
        nativeClearMessage();
        // nativeIsGenerating() should never be true here
        if (callAgainAfterDelay("doRedo", view, null)) return;
        nativeRedo();
        updateButtons();
    }

    // -----------------------------------------------------------------------------
    
    // called when the All button is tapped
    public void doSelectAll(View view) {
        nativeClearMessage();
        nativeSelectAll();
        UpdateEditBar();
    }

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

    private void createSelectionMenu(MenuInflater inflater) {
        Menu menu = popup.getMenu();
        inflater.inflate(R.menu.selection_menu, menu);
        MenuItem item = menu.findItem(R.id.random);
        item.setTitle("Random Fill (" + nativeGetRandomFill() + "%)");
        if (widescreen) {
            // delete the top 2 items
            menu.removeItem(R.id.paste);
            menu.removeItem(R.id.all);
        }
    }

    // -----------------------------------------------------------------------------
    
    // called when the Edit button is tapped (widescreen is true)
    public void doEdit(View view) {
        nativeClearMessage();
        if (nativeSelectionExists()) {
            popup = new PopupMenu(this, view);
            MenuInflater inflater = popup.getMenuInflater();
            createSelectionMenu(inflater);
            popup.show();
        } else {
            // editbutton should be disabled if there's no selection
            Log.e("Golly","Bug detected in doEdit!");
        }
    }

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

    private void createPasteMenu(MenuInflater inflater) {
        Menu menu = popup.getMenu();
        inflater.inflate(R.menu.paste_menu, menu);
        MenuItem item = menu.findItem(R.id.pastesel);
        item.setEnabled(nativeSelectionExists());
        item = menu.findItem(R.id.pastemode);
        item.setTitle("Paste (" + nativeGetPasteMode() + ")");
        if (nativeIsGenerating()) {
            // probably best to stop generating when Paste button is tapped
            // (consistent with iOS Golly)
            stopped = true;
            nativeStopGenerating();
            updateButtons();
        }
    }

    // -----------------------------------------------------------------------------
    
    // called when the Paste button is tapped (widescreen is true)
    public void doPaste(View view) {
        nativeClearMessage();
        if (nativePasteExists()) {
            // show pop-up menu with various paste options
            popup = new PopupMenu(this, view);
            MenuInflater inflater = popup.getMenuInflater();
            createPasteMenu(inflater);
            popup.show();
        } else {
            // create the paste image
            stopIfGenerating();
            if (callAgainAfterDelay("doPaste", view, null)) return;
            nativePaste();
            UpdateEditBar();
        }
    }

    // -----------------------------------------------------------------------------
    
    // called when the Edit/Paste button is tapped (widescreen is false)
    public void doEditPaste(View view) {
        nativeClearMessage();
        // display pop-up menu with items that depend on whether a selection or paste image exists
        popup = new PopupMenu(this, view);
        MenuInflater inflater = popup.getMenuInflater();
        if (nativePasteExists()) {
            createPasteMenu(inflater);
        } else if (nativeSelectionExists()) {
            createSelectionMenu(inflater);
        } else {
            inflater.inflate(R.menu.edit_menu, popup.getMenu());
        }
        popup.show();
    }

    // -----------------------------------------------------------------------------
    
    // called when item from edit_menu is selected
    public void doEditItem(MenuItem item) {
        if (item.getItemId() == R.id.paste) {
            stopIfGenerating();
            if (callAgainAfterDelay("doPaste", null, item)) return;
        }
        popup.dismiss();
        int id = item.getItemId();
        if (id == R.id.paste) {
            nativePaste();
        } else if (id == R.id.all) {
            nativeSelectAll();
        } else {
            Log.e("Golly","Fix bug in doEditItem!");
        }
        UpdateEditBar();
    }

    // -----------------------------------------------------------------------------
    
    // called when item from selection_menu is selected
    public void doSelectionItem(MenuItem item) {
        if (item.getItemId() != R.id.remove &&
            item.getItemId() != R.id.copy &&
            item.getItemId() != R.id.shrink &&
            item.getItemId() != R.id.fitsel) {
            // item can modify the current pattern so we must stop generating,
            // but nicer if we only stop temporarily and resume when done
            if (nativeIsGenerating()) {
                // no need to set stopped = true
                nativePauseGenerating();
            }
            if (callAgainAfterDelay("doSelItem", null, item)) return;
        }
        popup.dismiss();
        int id = item.getItemId();
        // doEditItem handles the top 2 items (if widescreen is false)
//        if (id == R.id.paste) {
//            nativePaste();
//        } else if (id == R.id.all) {
//            nativeSelectAll();
//        } else
        if (id == R.id.remove) {
            nativeRemoveSelection();
        } else if (id == R.id.cut) {
            nativeCutSelection();
        } else if (id == R.id.copy) {
            nativeCopySelection();
        } else if (id == R.id.clear) {
            nativeClearSelection(1);
        } else if (id == R.id.clearo) {
            nativeClearSelection(0);
        } else if (id == R.id.shrink) {
            nativeShrinkSelection();
        } else if (id == R.id.fitsel) {
            nativeFitSelection();
        } else if (id == R.id.random) {
            nativeRandomFill();
        } else if (id == R.id.flipy) {
            nativeFlipSelection(1);
        } else if (id == R.id.flipx) {
            nativeFlipSelection(0);
        } else if (id == R.id.rotatec) {
            nativeRotateSelection(1);
        } else if (id == R.id.rotatea) {
            nativeRotateSelection(0);
        } else if (id == R.id.advance) {
            nativeAdvanceSelection(1);
        } else if (id == R.id.advanceo) {
            nativeAdvanceSelection(0);
        } else {
            Log.e("Golly","Fix bug in doSelectionItem!");
        }
        // resume generating (only if nativePauseGenerating was called)
        nativeResumeGenerating();
    }

    // -----------------------------------------------------------------------------
    
    // called when item from paste_menu is selected
    public void doPasteItem(MenuItem item) {
        popup.dismiss();
        int id = item.getItemId();
        if (id == R.id.abort) {
            nativeAbortPaste();
        } else if (id == R.id.pastemode) {
            nativeDoPaste(0);
        } else if (id == R.id.pastesel) {
            nativeDoPaste(1);
        } else if (id == R.id.pflipy) {
            nativeFlipPaste(1);
        } else if (id == R.id.pflipx) {
            nativeFlipPaste(0);
        } else if (id == R.id.protatec) {
            nativeRotatePaste(1);
        } else if (id == R.id.protatea) {
            nativeRotatePaste(0);
        } else {
            Log.e("Golly","Fix bug in doPasteItem!");
        }
        UpdateEditBar();
    }
    
    // -----------------------------------------------------------------------------
    
    // called when the Draw/Pick/Select/Move button is tapped
    public void doSetTouchMode(View view) {
        nativeClearMessage();
        // display pop-up menu with these items: Draw, Pick, Select, Move
        popup = new PopupMenu(this, view);
        MenuInflater inflater = popup.getMenuInflater();
        inflater.inflate(R.menu.mode_menu, popup.getMenu());
        popup.show();
    }

    // -----------------------------------------------------------------------------
    
    // called when item from mode_menu is selected
    public void doModeItem(MenuItem item) {
        popup.dismiss();
        int id = item.getItemId();
        if (id == R.id.draw) {
            nativeSetMode(0);
        } else if (id == R.id.pick) {
            nativeSetMode(1);
        } else if (id == R.id.select) {
            nativeSetMode(2);
        } else if (id == R.id.move) {
            nativeSetMode(3);
        } else {
            Log.e("Golly","Fix bug in doModeItem!");
        }
        UpdateEditBar();      // update modebutton text
    }

    // -----------------------------------------------------------------------------
    
    // called when the Draw button is tapped
    public void doDrawMode(View view) {
        nativeClearMessage();
        if (nativeGetMode() != 0) {
            nativeSetMode(0);
            UpdateEditBar();
        }
    }

    // -----------------------------------------------------------------------------
    
    // called when the Pick button is tapped
    public void doPickMode(View view) {
        nativeClearMessage();
        if (nativeGetMode() != 1) {
            nativeSetMode(1);
            UpdateEditBar();
        }
    }

    // -----------------------------------------------------------------------------
    
    // called when the Select button is tapped
    public void doSelectMode(View view) {
        nativeClearMessage();
        if (nativeGetMode() != 2) {
            nativeSetMode(2);
            UpdateEditBar();
        }
    }

    // -----------------------------------------------------------------------------
    
    // called when the Move button is tapped
    public void doMoveMode(View view) {
        nativeClearMessage();
        if (nativeGetMode() != 3) {
            nativeSetMode(3);
            UpdateEditBar();
        }
    }

    // -----------------------------------------------------------------------------
    
    // called when the state button is tapped
    public void doChangeState(View view) {
        nativeClearMessage();
        // let user change the current drawing state
        Intent intent = new Intent(this, StateActivity.class);
        startActivity(intent);
    }

    // -----------------------------------------------------------------------------
    
    // called when the New button is tapped
    public void doNewPattern(View view) {
        nativeClearMessage();
        // stopIfGenerating() would work here but we use smarter code that
        // avoids trying to save the current pattern (potentially very large)
        if (nativeIsGenerating()) {
            stopped = true;
            nativeStopBeforeNew();
        }
        if (callAgainAfterDelay("doNew", view, null)) return;
        
        nativeNewPattern();
        updateButtons();
        UpdateEditBar();
        
        if (nativeNumLayers() == 1) {
            // delete all files in tempdir
            deleteTempFiles();
        }
    }

    // -----------------------------------------------------------------------------
    
    // called when the Rule button is tapped
    public void doRule(View view) {
        nativeClearMessage();
        stopIfGenerating();
        if (callAgainAfterDelay("doRule", view, null)) return;
        
        // let user change the current rule and/or algorithm
        Intent intent = new Intent(this, RuleActivity.class);
        startActivity(intent);
    }

    // -----------------------------------------------------------------------------
        
    // called when the Info button is tapped
    public void doInfo(View view) {
        nativeClearMessage();
        stopIfGenerating();
        if (callAgainAfterDelay("doInfo", view, null)) return;
        
        // display any comments in current pattern file
        Intent intent = new Intent(this, InfoActivity.class);
        intent.putExtra(InfoActivity.INFO_MESSAGE, "native");
        startActivity(intent);
    }

    // -----------------------------------------------------------------------------
    
    // called when the Save button is tapped
    public void doSave(View view) {
        nativeClearMessage();
        stopIfGenerating();
        if (callAgainAfterDelay("doSave", view, null)) return;
        
        AlertDialog.Builder alert = new AlertDialog.Builder(this);
        alert.setTitle("Save current pattern");
        alert.setMessage("Valid file name extensions are\n" + nativeGetValidExtensions());
        
        // or use radio buttons as in iPad Golly???
        // might be better to create a new SaveActivity and make it appear in a dialog
        // by setting its theme to Theme.Holo.Dialog in the <activity> manifest element

        // use an EditText view to get filename
        final EditText input = new EditText(this);
        input.setSingleLine(true);
        input.setHint("enter a file name");
        input.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
        alert.setView(input);

        alert.setPositiveButton("SAVE",
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                    // do nothing here (see below)
                }
            });
        alert.setNegativeButton("CANCEL",
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                    dialog.cancel();
                }
            });

        // don't call alert.show() here -- instead we use the following stuff
        // which allows us to prevent the alert dialog from closing if the
        // given file name is invalid
        
        final AlertDialog dialog = alert.create();
        dialog.show();
        dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(
            new View.OnClickListener() {
                public void onClick(View v) {
                    String filename = input.getText().toString();
                    if (filename.length() == 0) {
                        PlayBeepSound();
                        return;
                    }
                    BaseApp baseapp = (BaseApp)getApplicationContext();
                    // check for valid extension
                    if (!nativeValidExtension(filename)) {
                        baseapp.Warning("Invalid file extension.");
                        return;
                    }
                    // check if given file already exists
                    if (nativeFileExists(filename)) {
                        String answer = baseapp.YesNo("A file with that name already exists.  Do you want to replace that file?");
                        if (answer.equals("no")) return;
                    }
                     // dismiss dialog first in case SavePattern calls BeginProgress
                    dialog.dismiss();
                    nativeSavePattern(filename);
                }
            });
    }

    // -----------------------------------------------------------------------------
    
    // called when the Full/Restore button is tapped
    public void toggleFullScreen(View view) {
        // no need to call nativeClearMessage()
        LinearLayout topbar = (LinearLayout) findViewById(R.id.top_bar);
        LinearLayout editbar = (LinearLayout) findViewById(R.id.edit_bar);
        RelativeLayout bottombar = (RelativeLayout) findViewById(R.id.bottom_bar);
        
        if (fullscreen) {
            fullscreen = false;
            restorebutton.setVisibility(View.INVISIBLE);
            
            status1.setVisibility(View.VISIBLE);
            status2.setVisibility(View.VISIBLE);
            status3.setVisibility(View.VISIBLE);

            topbar.setVisibility(LinearLayout.VISIBLE);
            editbar.setVisibility(LinearLayout.VISIBLE);
            bottombar.setVisibility(RelativeLayout.VISIBLE);
            
            getActionBar().show();
        } else {
            fullscreen = true;
            restorebutton.setVisibility(View.VISIBLE);
            
            status1.setVisibility(View.GONE);
            status2.setVisibility(View.GONE);
            status3.setVisibility(View.GONE);
            
            topbar.setVisibility(LinearLayout.GONE);
            editbar.setVisibility(LinearLayout.GONE);
            bottombar.setVisibility(RelativeLayout.GONE);
            
            getActionBar().hide();
        }
        
        // need to let native code know (this will update status bar if not fullscreen)
        nativeSetFullScreen(fullscreen);
        
        if (!fullscreen) {
            updateButtons();
            UpdateEditBar();
        }
    }

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

    // this method is called from C++ code (see jnicalls.cpp)
    private void StartMainActivity() {
        Intent intent = new Intent(this, MainActivity.class);
        startActivity(intent);
    }

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

    // this method is called from C++ code (see jnicalls.cpp)
    private void RefreshPattern() {
        // this can be called from any thread
        pattView.requestRender();
    }

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

    // this method is called from C++ code (see jnicalls.cpp)
    private void ShowStatusLines() {
        // no need to check fullscreen flag here (caller checks it)
        
        // this might be called from a non-UI thread
        runOnUiThread(new Runnable() {
            public void run() {
                int bgcolor = nativeGetStatusColor();
                if (statuscolor != bgcolor) {
                    // change background color of status lines
                    statuscolor = bgcolor;
                    status1.setBackgroundColor(statuscolor);
                    status2.setBackgroundColor(statuscolor);
                    status3.setBackgroundColor(statuscolor);
                }
                status1.setText(nativeGetStatusLine(1));
                status2.setText(nativeGetStatusLine(2));
                status3.setText(nativeGetStatusLine(3));
            }
        });
    }

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

    // this method is called from C++ code (see jnicalls.cpp)
    private void UpdateEditBar() {
        if (fullscreen) return;
        
        // this might be called from a non-UI thread
        runOnUiThread(new Runnable() {
            public void run() {
                undobutton.setEnabled(nativeCanUndo());
                redobutton.setEnabled(nativeCanRedo());
                
                // update Edit button
                if (widescreen) {
                    editbutton.setEnabled(nativeSelectionExists());
                } else {
                    if (nativePasteExists()) {
                        editbutton.setText("Paste");
                    } else {
                        editbutton.setText("Edit");
                    }
                }
                
                // show current drawing state
                statebutton.setText("State=" + Integer.toString(nativeDrawingState()));
                
                // show current touch mode
                if (widescreen) {
                    drawbutton.setTypeface(Typeface.DEFAULT);
                    pickbutton.setTypeface(Typeface.DEFAULT);
                    selectbutton.setTypeface(Typeface.DEFAULT);
                    movebutton.setTypeface(Typeface.DEFAULT);
                    drawbutton.setTextColor(Color.rgb(0,0,0));
                    pickbutton.setTextColor(Color.rgb(0,0,0));
                    selectbutton.setTextColor(Color.rgb(0,0,0));
                    movebutton.setTextColor(Color.rgb(0,0,0));
                    switch (nativeGetMode()) {
                        case 0:  drawbutton.setTypeface(Typeface.DEFAULT_BOLD);
                                 drawbutton.setTextColor(Color.rgb(0,0,255));
                                 break;
                        case 1:  pickbutton.setTypeface(Typeface.DEFAULT_BOLD);
                                 pickbutton.setTextColor(Color.rgb(0,0,255));
                                 break;
                        case 2:  selectbutton.setTypeface(Typeface.DEFAULT_BOLD);
                                 selectbutton.setTextColor(Color.rgb(0,0,255));
                                 break;
                        case 3:  movebutton.setTypeface(Typeface.DEFAULT_BOLD);
                                 movebutton.setTextColor(Color.rgb(0,0,255));
                                 break;
                        default: Log.e("Golly","Fix bug in UpdateEditBar!");
                    }
                } else {
                    switch (nativeGetMode()) {
                        case 0:  modebutton.setText("Draw"); break;
                        case 1:  modebutton.setText("Pick"); break;
                        case 2:  modebutton.setText("Select"); break;
                        case 3:  modebutton.setText("Move"); break;
                        default: Log.e("Golly","Fix bug in UpdateEditBar!");
                    }
                }
            }
        });
    }

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

    // this method is called from C++ code (see jnicalls.cpp)
    private void PlayBeepSound() {
        try {
            ToneGenerator tg = new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100);
            tg.startTone(ToneGenerator.TONE_PROP_BEEP);
            tg.release();
        } catch (RuntimeException e) {
            //
            // new ToneGenerator() may throw java.lang.RuntimeException: Init failed
            //
        }
    }
    
    // -----------------------------------------------------------------------------

    // this method is called from C++ code (see jnicalls.cpp)
    private void RemoveFile(String filepath) {
        File file = new File(filepath);
        if (!file.delete()) {
            Log.e("RemoveFile failed", filepath);
        }
    }
    
    // -----------------------------------------------------------------------------

    // this method is called from C++ code (see jnicalls.cpp)
    private String MoveFile(String oldpath, String newpath) {
        String result = "";
        File oldfile = new File(oldpath);
        File newfile = new File(newpath);
        if (!oldfile.renameTo(newfile)) {
            Log.e("MoveFile failed: old", oldpath);
            Log.e("MoveFile failed: new", newpath);
            result = "MoveFile failed";
        }
        return result;
    }
    
    // -----------------------------------------------------------------------------

    // this method is called from C++ code (see jnicalls.cpp)
    private void CopyTextToClipboard(String text) {
        ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
        ClipData clip = ClipData.newPlainText("RLE data", text);
        clipboard.setPrimaryClip(clip);
        // Log.i("CopyTextToClipboard", text);
    }

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

    // this method is called from C++ code (see jnicalls.cpp)
    private String GetTextFromClipboard() {
        BaseApp baseapp = (BaseApp)getApplicationContext();
        ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
        String text = "";
        if (clipboard.hasPrimaryClip()) {
            ClipData clipData = clipboard.getPrimaryClip();
            text = clipData.getItemAt(0).coerceToText(this).toString();
            if (text.length() == 0) {
            	baseapp.Warning("Failed to get text from clipboard!");
            }
        }
        // Log.i("GetTextFromClipboard", text);
        return text;
    }
    
    // -----------------------------------------------------------------------------

    // this method is called from C++ code (see jnicalls.cpp)
    private void ShowTextFile(String filepath) {
        BaseApp baseapp = (BaseApp)getApplicationContext();
        // display file contents
        Intent intent = new Intent(baseapp.getCurrentActivity(), InfoActivity.class);
        intent.putExtra(InfoActivity.INFO_MESSAGE, filepath);
        startActivity(intent);
    }
    
    // -----------------------------------------------------------------------------

    // this method is called from C++ code (see jnicalls.cpp)
    private void ShowHelp(String filepath) {
        Intent intent = new Intent(this, HelpActivity.class);
        intent.putExtra(HelpActivity.SHOWHELP_MESSAGE, filepath);
        startActivity(intent);
    }
    
    // -----------------------------------------------------------------------------

    // this method is called from C++ code (see jnicalls.cpp)
    private void BeginProgress(String title) {
        if (progresscount == 0) {
            // can we disable interaction with all views outside proglayout???
            progtitle.setText(title);
            progbar.setProgress(0);
            // proglayout.setVisibility(LinearLayout.VISIBLE);
            cancelProgress = false;
            progstart = System.nanoTime();
        }
        progresscount++;    // handles nested calls
    }
    
    // -----------------------------------------------------------------------------

    // this method is called from C++ code (see jnicalls.cpp)
    private boolean AbortProgress(int percentage, String message) {
        if (progresscount <= 0) {
            Log.e("Golly","Bug detected in AbortProgress!");
            return true;
        }
        long nanosecs = System.nanoTime() - progstart;
        if (proglayout.getVisibility() == LinearLayout.VISIBLE) {
            if (nanosecs < prognext) return false;
            prognext = nanosecs + 100000000L;     // update progress bar about 10 times per sec
            updateProgressBar(percentage);
            return cancelProgress;
        } else {
            // note that percentage is not always an accurate estimator for how long
            // the task will take, especially when we use nextcell for cut/copy
            if ( (nanosecs > 1000000000L && percentage < 30) || nanosecs > 2000000000L ) {
                // task is probably going to take a while so show progress bar
                proglayout.setVisibility(LinearLayout.VISIBLE);
                updateProgressBar(percentage);
            }
            prognext = nanosecs + 10000000L;     // 0.01 sec delay until 1st progress update
        }
        return false;
    }
    
    // -----------------------------------------------------------------------------

    private void updateProgressBar(int percentage) {
        if (percentage < 0 || percentage > 100) return;
        progbar.setProgress(percentage);
        CheckMessageQueue();
    }
    
    // -----------------------------------------------------------------------------

    // this method is called from C++ code (see jnicalls.cpp)
    private void EndProgress() {
        if (progresscount <= 0) {
            Log.e("Golly","Bug detected in EndProgress!");
            return;
        }
        progresscount--;
        if (progresscount == 0) {
            proglayout.setVisibility(LinearLayout.INVISIBLE);
        }
    }

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

    // called when the Cancel button is tapped
    public void doCancel(View view) {
        cancelProgress = true;
    }

} // MainActivity class
