// This file is part of the "IBController".
// Copyright (C) 2004 Steven M. Kearns ([email protected] )
// Copyright (C) 2004 - 2011 Richard L King ([email protected])
// For conditions of distribution and use, see copyright notice in COPYING.txt

// IBController is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// IBController is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with IBController.  If not, see <http://www.gnu.org/licenses/>.

package ibcontroller;

import java.awt.Container;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import javax.swing.JDialog;
import javax.swing.JMenuItem;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.TreePath;

class Utils {
    
    static final SimpleDateFormat _DateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
    
    // set these to the defaults, so that we can continue to use them 
    // even when TWS redirects System.out and System.err to its own logfile
    private static final PrintStream out = System.out;
    private static final PrintStream err = System.err;
    
    private static boolean sendConsoleOutputToTwsLog = false;
    
    /**
     * Performs a click on the menu item at the specified path, waiting if necessary for the
     * menu item to become enabled.
     * 
     * If there is more than one menu bar within the specified container, they are searched
     * (in hierarchical containment order) until one is found that contains the specified menu item.
     * 
     * Note that this method may block the calling thread if the required menu item is currently disabled.
     * @param container
     * the Container to search in
     * @param path
     * the path of the required menu item
     * @return
     * true if the menu item was successfully clicked; false if the menu item could not be found
     * @throws  IllegalStateException 
     * the method has been called on the Swing event dispatch thread
     */
    static boolean invokeMenuItem(final Container container, final String[] path) throws IllegalStateException {
        if (SwingUtilities.isEventDispatchThread()) throw new IllegalStateException("Function must not be called on the event dispatch thread, as it may block the thread");
        while (true) {
            FutureTask<Boolean> task = new FutureTask<>(new Callable<Boolean>() {
                @Override
                public Boolean call() throws IBControllerException {
                    String s = path[0];
                    for (int i = 1; i < path.length; i++) s = s + " > " + path[i];

                    JMenuItem menuItem = SwingUtils.findMenuItemInAnyMenuBar(container, path);
                    if (menuItem == null) throw new IBControllerException("menu item: " + s);
                    if (!menuItem.isEnabled()) return false;
                    menuItem.doClick();
                    return true;
                }
            });

            GuiDeferredExecutor.instance().execute(task);
            
            try {
                if (task.get()) return true;
            } catch (InterruptedException e) {
                logError("invokeMenuItem task interrupted");
                return false;
            } catch (ExecutionException e) {
                Throwable t = e.getCause();
                if (t instanceof IBControllerException) {
                    return false;
                }
                if (t instanceof RuntimeException) throw (RuntimeException)t;
                if (t instanceof Error) throw (Error)t;
            }
            
            pause(250);
        }
    }

    static void logError(String message) {
        getErrStream().println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
        getErrStream().println(formatMessage(message));
        getErrStream().println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
    }
    
    static void logException(Exception e) {
        getErrStream().println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
        getErrStream().println(formatMessage("An exception has occurred:"));
        e.printStackTrace(getErrStream());
        getErrStream().println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
    }
    
    /**
     * Writes a plain one-line text message to the console.
     * @param msg
     * The message to be written
     */
    static void logRawToConsole(String msg) {
        getOutStream().println(msg);
    }
    
    /**
     * Writes a text message prefixed with the current time to the console.
     * @param msg
     * The message to be written
     */
    static void logToConsole(String msg) {
        getOutStream().println(formatMessage(msg));
    }
    
    static PrintStream getErrStream() {
        if (sendConsoleOutputToTwsLog) {
            return System.err;
        } else {
            return err;
        }
    }
    
    static PrintStream getOutStream() {
        if (sendConsoleOutputToTwsLog) {
            return System.out;
        } else {
            return out;
        }
    }
    
    private static String formatMessage(String message) {
        return _DateFormatter.format(new Date()) + " IBController: " + message.substring(0,1).toUpperCase() + message.substring(1);
    }

    /**
     * sleeps for millis milliseconds, approximately.
     * 
     * Note that this method swallows the InterruptedException that may
     * result from a call to sleep().
     * @param millis
     * the number of milliseconds to sleep for
     */
    static void pause(int millis) {
        try {
            Thread.sleep(millis); // sleep a bit before trying again.
        } catch (InterruptedException ie) {
        }
    }
    
    /**
     * Selects the specified section in the Global Configuration dialog.
     * @param configDialog
     * the Global Configuration dialog
     * @param path
     * the path to the required configuration section in the Global Configuration dialog
     * @return
     * true if the specified section can be found; otherwise false
     * @throws IBControllerException
     * a UI component could not be found
     * @throws IllegalStateException
     * the method has not been called on the SWing event dispatch thread
     */
    static boolean selectConfigSection(final JDialog configDialog, final String[] path) throws IBControllerException, IllegalStateException {
        if (!SwingUtilities.isEventDispatchThread()) throw new IllegalStateException("selectConfigSection must be run on the event dispatch thread");
        
        JTree configTree = SwingUtils.findTree(configDialog);
        if (configTree == null) throw new IBControllerException("could not find the config tree in the Global Configuration dialog");

        Object node = configTree.getModel().getRoot();
        TreePath tp = new TreePath(node);

        for (String pathElement: path) {
            node = SwingUtils.findChildNode(configTree.getModel(), node, pathElement);
            if (node == null) return false;
            tp = tp.pathByAddingChild(node);
        }

        configTree.setExpandsSelectedPaths(true);
        configTree.setSelectionPath(tp);
        return true;
    }

    static void showTradesLogWindow() {
            MyCachedThreadPool.getInstance().execute(new Runnable () {
                @Override public void run() {invokeMenuItem(MainWindowManager.mainWindowManager().getMainWindow(), new String[] {"Account", "Trade Log"});}
            });
    }
    
    static void sendConsoleOutputToTwsLog(boolean value) {
        sendConsoleOutputToTwsLog = value;
    }

}