/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 */

/*
* Core Application class for debugger (used both when app or applet)
*/

// when     who     what
// 06/27/97 jband   added this header to my code
//

package com.netscape.jsdebugging.ifcui;

import java.io.*;
import java.awt.Dimension;
import netscape.application.*;
import netscape.util.*;
import com.netscape.jsdebugging.api.*;
import netscape.javascript.*;
import netscape.security.PrivilegeManager;
import com.netscape.jsdebugging.ifcui.palomar.widget.layout.*;
import com.netscape.jsdebugging.ifcui.palomar.widget.toolbar.*;
import com.netscape.jsdebugging.ifcui.palomar.widget.toolTip.*;
import com.netscape.jsdebugging.ifcui.palomar.widget.PopupButton;
import com.netscape.jsdebugging.ifcui.palomar.util.*;
import com.netscape.jsdebugging.ifcui.palomar.util.AssertFailureHandler;


public class JSDebuggerApp
    extends Application
    implements Target, AssertFailureHandler, EmperorOwner
{
    private static final boolean WILL_EXPIRE = false;
    private static final boolean IS_INTERNAL_RELEASE = true;
    private static final boolean ALWAYS_SHOW_BUILD_DATE = true;

    // "15 Sep 1997" -> 874306800000L   // PR1 date
    // "31 Oct 1997" -> 878284800000L   // PR2 date
    // "31 Mar 1998" -> 891331200000L   // 1.1 PR1 Date (proposed)
    private static final long _deathTime = 891331200000L;

    public static final  int    MAJOR_VERSION   = 1;
    public static final  int    MINOR_VERSION   = 2;
    public static final  int    PREVIEW_VERSION = 1;

    private static final String _mainTitleLocal  = "Netscape JavaScript Debugger";
//    private static final String _mainTitleLocal  = "(CLIENT) Netscape JavaScript Debugger";
    private static final String _mainTitleServer = "(SERVER) Netscape JavaScript Debugger";
    private static final String _mainTitleRhino = "Netscape JavaScript for Java Debugger";

    // this is usually the string literal version of Emperor 'modes'
    // -- parsed below --
    public void setMode(String s)
    {
        if(AS.S)ER.T(null==_emperor, "attempt to setMode while running", this);
        _modeString = s;
    }
    public void setHost(String s)
    {
        if(AS.S)ER.T(null==_emperor, "attempt to setHost while running", this);
        _host = s;
    }

    // override Application.init()
    public void init()
    {
        super.init();
        initForReal();
    }

    private void initForReal()
    {
//        System.out.println("++++ initForReal() start" );

        // Let's ask for everything upfront to get this all out of the
        // way (from the user's point of view)
        try
        {
            PrivilegeManager.enablePrivilege("Debugger");
            PrivilegeManager.enablePrivilege("UniversalFileRead");

            // It doesn't really matter that much if the user says no to
            // these (at this point)
            try
            {
                PrivilegeManager.enablePrivilege("UniversalFileWrite");
                PrivilegeManager.enablePrivilege("UniversalTopLevelWindow");
            }
            catch(Exception e)
            {
                // just eat these
            }

        }
        catch(Exception e)
        {
            performCommandLater( this, LOAD_FAILED_CMD, null );
            return;
        }

        if(AS.DEBUG)
        {
            Thread t = Thread.currentThread();
            _uiThreadForAssertCheck = t;
            ER.setFailureHandler(t,this);
        }

        // set some static globals...
        Env.Init();

        // we don't want to spit this info to the console if not an applet
        if( isApplet() )
        {
            int platform = Env.getCoursePlatformType();

            System.out.println("JSD platform is " +
                               (platform == Env.ENV_WIN ?
                                 "Windows" :
                                platform == Env.ENV_MAC ?
                                 "Mac" :
                                 "Unix" ));
            System.out.println("JSD install directory: " + Env.getCodebaseDir());
            System.out.println("JSD prefs directory: " + Env.getPrefsDir());
        }

        // set mode
        if(null == _modeString)
            _modeString = parameterNamed( "DebugHostMode" );

        if(null != _modeString)
        {
            if( _modeString.equals("LOCAL") )
                _mode = Emperor.LOCAL;
            else if( _modeString.equals("REMOTE_SERVER") )
                _mode = Emperor.REMOTE_SERVER;
            else if( _modeString.equals("RHINO") )
                _mode = Emperor.RHINO;
            else
                _mode = Emperor.LOCAL;
        }
        else
            _mode = Emperor.LOCAL;

        // set host (this may be further massaged below...

        if(null == _host)
            _host = parameterNamed( "DebugHost" );

        // init the system that will allow us access to the startup prefs file
        // this needs to be done after our mode is established
        _initStartupPrefs();

        if(AS.DEBUG)
        {
            if( Emperor.LOCAL == _mode )
                Log.setFilename("jsdclog.log", true);
            else
                Log.setFilename("jsdslog.log", true);
            Log.setEnabled(true);
            Log.setEnabledType(Log.ERROR, true);
            Log.setEnabledType(Log.WARN, true);
            Log.setEnabledType(Log.TRACE, false);
            Log.setShowDate(true);
            Log.setShowThreadName(true);
            Log.setLogToFile(true);
            Log.setLogToConsole(false);
            Log.log(null, "+++++++++++++++ startup begin +++++++++++++++" );
            // we don't want to spit this info to the console if not an applet
            if( isApplet() )
                System.out.println("JSD is using log file: " + Log.getFilename());
        }
        else
        {
            Log.setEnabled(false);
        }

        _mainWindow = new ToolTipExternalWindow();

        Dimension res = AWTCompatibility.awtToolkit().getScreenSize();
        _mainWindow.setBounds(0, 0, 525, res.height - 50);

        if( Emperor.REMOTE_SERVER == _mode )
            _mainWindow.setTitle(_mainTitleServer);
        else if( Emperor.RHINO == _mode )
            _mainWindow.setTitle(_mainTitleRhino);
        else
            _mainWindow.setTitle(_mainTitleLocal);

        setMainRootView(_mainWindow.rootView());

        _mainWindow.show();
        setWaitCursor(true);

        StatusWindow statusWindow = new StatusWindow();
        statusWindow.setText("Loading...");
        statusWindow.show();

        if( WILL_EXPIRE && daysTillDeath() <= 0 )
        {
            setWaitCursor(false);
            Alert.runAlertInternally( Alert.warningImage(),
                "Error",
                  "This Preview copy of Netscape JavaScript Debugger has expired\n"
                + "Please go to the Netscape home site to get a new version\n"
                + "\n"
                + "        The debugger will exit now...",
                "OK", null, null );
            performCommandLater( this, LOAD_FAILED_CMD, null );
            return;
        }

        if( staleTwisterClassesFound() )
        {
            setWaitCursor(false);
            Alert.runAlertInternally( Alert.warningImage(),
                "Error",
                  "Conflict with obsolete Java classes detected.\n"
                + "\n"
                + "Netscape Visual JavaScript Preview Release 1 must be uninstalled\n"
                + "before the Netscape JavaScript Debugger can be run.\n"
                + "\n"
                + "Please see the Debugger release notes for additional information\n"
                + "\n"
                + "        The debugger will exit now...",
                "OK", null, null );
            performCommandLater( this, LOAD_FAILED_CMD, null );
            return;
        }

//        statusWindow.setText("Checking License...");
//        if( ! userHasAgreedToLicense() )
//        {
//            performCommandLater( this, LOAD_FAILED_CMD, null );
//            return;
//        }
//        statusWindow.setText("Loading...");

        if( Emperor.REMOTE_SERVER == _mode && null == _host )
        {
            String hostname = _readHostName();
            HostNameDialog dlg = new
                    HostNameDialog("Choose Server",
                                   "Enter Hostname or ip address of Server",
                                   hostname);
            while(true)
            {
                statusWindow.hide();
                setWaitCursor(false);
                dlg.showModally();
                setWaitCursor(true);

                if( HostNameDialog.OK == dlg.getKeyPressed() )
                {
                    statusWindow.show();
                    statusWindow.setText("Looking for Server...");
                    hostname = dlg.getHostName();
                    _host = "http://" + hostname;
                    if( isDebuggingSupported() )
                        break;

                    setWaitCursor(false);
                    if( Alert.DEFAULT_OPTION ==
                        Alert.runAlertInternally( Alert.warningImage(),
                            "Error",
                            "JavaScript Debugger support was not found in "+ _host+"\n"
                            + "\n"
                            + "Try a different Server?",
                            "Yes", "No", null ) )
                    {
                        setWaitCursor(true);
                        continue;
                    }
                    setWaitCursor(true);
                    _host = null;
                    break;
                }
                else if( HostNameDialog.LOCALHOST == dlg.getKeyPressed() )
                {
                    statusWindow.show();
                    hostname = "127.0.0.1";
                    _host = "http://" + hostname;
                    break;
                }
                else // if( HostNameDialog.CANCEL == dlg.getKeyPressed() )
                {
                    statusWindow.show();
                    _host = null;
                    break;
                }
            }

            if( null == _host )
            {
                performCommandLater( this, LOAD_FAILED_CMD, null );
                return;
            }
            statusWindow.setText("Looking for Server...");
            _writeHostName(hostname);
        }

        if( ! isDebuggingSupported() )
        {
            String core_msg;
            String suggestion;

            if( Emperor.LOCAL == _mode )
            {
                core_msg = "JavaScript Debugger support was not found in this copy of Navigator";
                suggestion = "";
            }

            else
            {
                core_msg = "JavaScript Debugger support was not found in "+ _host;
                suggestion = "If the server has been restarted, then it may be necessary\n" +
                             "to restart Navigator in order to re-establish the connection\n\n";
            }

            setWaitCursor(false);
            Alert.runAlertInternally( Alert.warningImage(),
                "Error",
                  core_msg
                + "\n\n"
                + suggestion
                + "Please see the Debugger release notes for troubleshooting information\n"
                + "\n"
                + "        The debugger will exit now...",
                "OK", null, null );
            performCommandLater( this, LOAD_FAILED_CMD, null );
            return;
        }

        if( Emperor.REMOTE_SERVER == _mode )
            statusWindow.setText("Found Server...");

        Menu mainMenu = new Menu(true);
        MenuView menuView = new MenuView(mainMenu);
        MenuItem fileMenu = mainMenu.addItemWithSubmenu("File");
        MenuItem item;

        _emperor = new Emperor( this, _mainWindow, mainMenu, fileMenu,
                                menuView, _mode, _host, statusWindow );

        addSeparator(fileMenu);
        item = fileMenu.submenu().addItem("Exit", EXIT_APP_CMD, this);

        MenuItem helpMenu = mainMenu.addItemWithSubmenu("Help");
        item = helpMenu.submenu().addItem("About", SHOW_ABOUT_CMD, this);

        menuView.sizeToMinSize();
        _mainWindow.setMenuView(menuView);
        _mainWindow.menuView().setDirty(true);

        statusWindow.hide();
        setWaitCursor(false);

        if(AS.DEBUG)Log.log(null, "startup success" );
        _signalInitSuccessToHTMLPage();
//        System.out.println("++++ initForReal() end" );
    }


    /*
    * Temporary method to add menu item separators until IFC gets a native one....
    */
    private static void addSeparator(MenuItem item)
    {
        item.submenu().addSeparator();
//        AWTCompatibility.awtMenuForMenu(item.submenu()).addSeparator();
    }

    // implement Target interface
    public void performCommand( String cmd, Object theObject )
    {
        if( cmd.equals(EXIT_APP_CMD) )
        {
            if( null != _mainWindow )
                _mainWindow.hide();
        }
        else if( cmd.equals(LOAD_FAILED_CMD) )
        {
            if( null != _mainWindow )
                _mainWindow.hide();
            // close the applet that owns us
            try
            {
                JSObject jso = JSObject.getWindow( AWTCompatibility.awtApplet() );
                if( null != jso )
                    jso.eval( "window.close()" );
            }
            catch(Throwable t){} // eat failure
        }
        else if( cmd.equals(SHOW_ABOUT_CMD) )
        {
            showAboutDialog();
        }
    }

    // assumes that wait cursor is showing!!!
    private boolean userHasAgreedToLicense()
    {
        if(AS.S)ER.T(_waitCount == 1,"hasUserAgreedToLicense() called when _waitCount != 1");

        String baseDir = Env.getCodebaseDir();
        if( null == baseDir )
        {
            System.err.println( "failed to get CodebaseDir, can't show license");
            return false;
        }

        String acceptFilename = baseDir
                                + "accept_"+MAJOR_VERSION+""+MINOR_VERSION
                                + (PREVIEW_VERSION != 0 ? ("_pr"+PREVIEW_VERSION):"")
                                + ".txt";

        if( new java.io.File(acceptFilename).exists() )
            return true;

        String licenseHtmlFilename = baseDir+"license.html";

        LicenseViewer v =
            new LicenseViewer("Netscape JavaScript Debugger License", licenseHtmlFilename);

        if( ! v.HtmlLoadedSuccesfully() )
        {
            setWaitCursor(false);
            int answer = Alert.runAlertInternally( Alert.warningImage(),
                "Error",
                "Unable to load license text from\n"
                + licenseHtmlFilename
                + "\n"
                + "\n"
                + "license.html should be in the same directory as the debugger start page.\n"
                + "\n"
                + "  Do you accept the terms of the license?",
                "I Accept", "Cancel", null );
            setWaitCursor(true);
            return Alert.DEFAULT_OPTION == answer;
        }

        setWaitCursor(false);
        v.showModally();
        setWaitCursor(true);

        if(v.userPressedAccept())
        {
            try
            {
                // write a zero length file
                new FileOutputStream(acceptFilename).close();
//                DataOutputStream dos =
//                    new DataOutputStream(new FileOutputStream(acceptFilename));
//                dos.writeBytes("accepted");
//                if( null != dos )
//                    dos.close();
            }
            catch(Exception e)
            {
                // eat it...
            }
            return true;
        }
        return false;
    }

    private void showAboutDialog()
    {
        String title = "About";
        String msg;

        msg  = "Netscape JavaScript Debugger\n";
        msg += "Version "+MAJOR_VERSION+"."+MINOR_VERSION;
        if( PREVIEW_VERSION != 0 )
            msg += " Preview Release "+PREVIEW_VERSION;
        msg += "\n\n";
        if(AS.DEBUG || IS_INTERNAL_RELEASE || ALWAYS_SHOW_BUILD_DATE)
            msg += "Built: "+BuildDate.display()+"\n\n";
        if( WILL_EXPIRE )
            msg += "This software will expire in "+ daysTillDeath() +" days\n\n";
        msg += "This software is subject to the terms\n";
        msg += "of the accompanying license agreement.\n";

        Alert.runAlertInternally( title, msg, "OK", null, null );

    }

    public boolean isDebuggingSupported()
    {
        return Emperor.isDebuggingSupported(_mode, _host);
    }

    private void _initStartupPrefs()
    {
        String prefsDir = Env.getPrefsDir();
        if( null == prefsDir )
            return;

        String startupPrefsFullFilename;
        if( Emperor.LOCAL == _mode )
            startupPrefsFullFilename = prefsDir + _startupPrefsFilenameClient;
        else
            startupPrefsFullFilename = prefsDir + _startupPrefsFilenameServer;

        _startupPrefsFile = new java.io.File(startupPrefsFullFilename);
    }


    // NOTE: this is destructive write of just this one key=value pair
    // This system will have to be improved if further pairs are needed.
    private void _writeHostName(String hostname)
    {
        if( null == _startupPrefsFile )
            return;

        try
        {
            DataOutputStream dos =
                new DataOutputStream(new FileOutputStream(_startupPrefsFile));
            dos.writeBytes(_LastServerHostNameKey+"="+hostname);
            if( null != dos )
                dos.close();
        }
        catch(Exception e)
        {
            // eat it...
        }
    }

    private String _readHostName()
    {
        String retval = "127.0.0.1";

        if( null == _startupPrefsFile )
            return retval;

        // any existing prefs?
        if( ! _startupPrefsFile.exists() )
            return retval;

        try
        {
            DataInputStream dis =
                new DataInputStream(new FileInputStream(_startupPrefsFile));

            String line;
            String key = _LastServerHostNameKey+"=";
            while( null != (line = dis.readLine()) )
            {
                if( line.startsWith(key) )
                {
                    retval = line.substring(key.length()).trim();
                    break;
                }
            }
            if( null != dis )
                dis.close();
        }
        catch(Exception e)
        {
            // eat it...
        }
        return retval;
    }

    /*
    * The scheme is that if the applet tag has a PARAM called "AppletInitProp",
    * then we will use it to get the name of the window property to be set.
    * We then set that window property to "1".
    */

    private void    _signalInitSuccessToHTMLPage()
    {
        String initProp = parameterNamed( "AppletInitProp" );
        if( null == initProp )
            return;

        String expression = "window."+initProp+"=1";
        try
        {
            JSObject jso = JSObject.getWindow( AWTCompatibility.awtApplet() );
            if( null != jso )
            {
//                System.out.println( "writing: " + expression );
                jso.eval( expression );
            }
        }
        catch(Throwable t){} // eat failure
    }

    private int daysTillDeath()
    {
        long millisecondsTillDeath = _deathTime - new java.util.Date().getTime();
        return 1 + (int) (millisecondsTillDeath / (1000*60*60*24));
    }

    // this tries to run a method which was not present in Twister PR1 but
    // is present in our jar. If this fails, then Twister PR1 is in the
    // classpath (which is very bad for us).
    private boolean staleTwisterClassesFound()
    {
        try
        {
            new com.netscape.jsdebugging.ifcui.palomar.widget.PopupButton(null,null,null,null,null).showLabel(false);
        }
        catch(Throwable t)
        {
            return true;
        }
        return false;
    }

    // override Application.cleanup
    public void cleanup()
    {
        if( null != _emperor )
            _emperor.IFC_cleanupCalled();
        super.cleanup();
    }

    // implement AssertFailureHandler
    public int assertFailed( String msgRaw, String msgCooked, Object ob )
    {
        if(AS.DEBUG)
        {
            // All these single use vars are here because the code
            // used to allow for internal or external dlgs (and may again)
            Image  img = Alert.questionImage();
            String title = "Assertion failed";
            String msg = msgCooked + "\n\n" + "(set debugger to catch \"com.netscape.jsdebugging.ifcui.palomar.util.DebuggerCaughtException\")";
            String opt1 = "Continue";
            String opt2 = "Abort";
            String opt3 = "Debug";

            Log.error("Assertion failed", msgCooked );

            int choice = Alert.runAlertExternally(img,title,msg,opt1,opt2,opt3);

            switch(choice)
            {
                case Alert.DEFAULT_OPTION: return AssertFailureHandler.CONTINUE;
                case Alert.SECOND_OPTION:  return AssertFailureHandler.ABORT;
                case Alert.THIRD_OPTION:   return AssertFailureHandler.DEBUG;
            }
        }
        return AssertFailureHandler.DEBUG;
    }

    // implement EmperorOwner
    public void setWaitCursor(boolean set)
    {
        _waitCount += set ? 1 : -1;

        if(AS.S)ER.T(_waitCount >= 0,"_waitCount went negative", this );
        if(AS.S)ER.T(Thread.currentThread()==_uiThreadForAssertCheck,"setWaitCursor() called on non-UI thread",this);

        if( (1 == _waitCount && set) || (0 == _waitCount && !set) )
        {
            Vector windows = externalWindows();
            int size = windows.size();
            for (int i= 0; i < size; i++)
            {
                ExternalWindow window = (ExternalWindow)windows.elementAt(i);
                FoundationPanel panel = window.panel();

                if (set)
                    panel.setCursor(View.WAIT_CURSOR);
                else
                    panel.setCursor(View.ARROW_CURSOR);
            }
        }
    }


    /****************************************************/

    // data...

    private ToolTipExternalWindow   _mainWindow;
    private InternalWindow          _toolBarWindow;
    private Emperor                 _emperor;
    private String                  _modeString;
    private int                     _mode;
    private String                  _host;
    private Thread                  _uiThreadForAssertCheck = null;
    private int                     _waitCount = 0;

    private static final String _startupPrefsFilenameServer = "jsdsstrt.txt";
    private static final String _startupPrefsFilenameClient = "jsdcstrt.txt";
    private java.io.File        _startupPrefsFile;

    private static final String _LastServerHostNameKey = "LastHost0";

    private static final String EXIT_APP_CMD = "EXIT_APP_CMD";
    private static final String LOAD_FAILED_CMD = "LOAD_FAILED_CMD";
    private static final String SHOW_ABOUT_CMD = "SHOW_ABOUT_CMD";
}
