// -*- Mode: java; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-

package com.danger.terminal;

import danger.app.Event;
import danger.app.Listener;
import danger.app.SettingsDB;
import danger.app.SettingsDBException;
import danger.ui.Menu;
import danger.ui.MenuItem;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;


class SessionManager
    extends Listener
    implements TerminalResources
{
    public static interface SessionListener
    {
        public void serverListChanged ();
        public void serverAdded (ServerInfo info);
        public void serverRemoved (ServerInfo info);
        public void serverUpdated (ServerInfo info);
        public void sessionAdded (TerminalConsole console);
        public void sessionRemoved (TerminalConsole console);
        public void sessionUpdated (TerminalConsole console);
    }
    
    
    public
    SessionManager (SettingsDB prefs)
    {
        mPrefs = prefs;
        mPrefs.setAutoSyncNotifyee(this);

        mTerminalPreferences = new TerminalPreferences(this);
        
        mPrefsLockCount = 0;
        
        mServers = new ServerInfo[MAX_NUM_HOSTS];
        
        mConsoleList = new ArrayList();
        
        mNumHosts = 0;
    }

    public void
    registerListener (SessionListener target)
    {
        mListener = target;
    }

    public boolean
    receiveEvent (Event e)
    {
        if (e.type == Event.EVENT_DATASTORE_RESTORED) {
            System.out.println("SessionManager: EVENT_DATASTORE_RESTORED");
            loadSessions();
            return true;
        }
        
        return super.receiveEvent(e);
    }
    
    public void
    loadSessions()
    {
        // make sure we're on v2, or else erase everything
        int version = 0;
        try {
            version = mPrefs.getIntValue("prefs-version");
        } catch (SettingsDBException x) { }

        if (version < 2) {
            if (mPrefs.getNumEntries() > 0) {
                mPrefs.clearAll();
            }
        }

        /* load the app defaults */
        mTerminalPreferences.load();
        holdPrefsUpdate();

        for (int i = 0; i < MAX_NUM_HOSTS; ++i) {
            ServerInfo info = ServerInfo.load(this, i);
            if (info == null) {
                continue;
            }
            
            String hostname = info.getHostname();
            if ((hostname == null) || (hostname.length() == 0)) {
                // empty record
                continue;
            }

            if (inHostList(info)) {
                // same entry is somewhere in the list already: skip.
                continue;
            }

            if (mServers[i] != null) {
                // this slot is already taken by a different host.  reassign to a new slot (if possible).
                addServer(info, false);
            } else {
                mServers[i] = info;
                ++mNumHosts;
            }
        }

        packHostList();

        releasePrefsUpdate();
        
        /* tell anyone that cares */
        mListener.serverListChanged();
    }

    private boolean
    inHostList (ServerInfo info)
    {
        for (int i = 0; i < MAX_NUM_HOSTS; i++) {
            if ((mServers[i] != null) && (mServers[i].compareTo(info) == 0)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Remove any holes in the flat array used to store the host list.
     * 
     * This really should only do work once: the first time you run MonkeyTerm
     * using your old server list.  After that, all the servers should be in
     * order and won't need squashing anymore.
     * 
     * Although this will also get called when a server is deleted.
     */
    private void
    packHostList ()
    {
        int fill = 0;

        for (int i = 0; i < MAX_NUM_HOSTS; i++) {
            if (mServers[i] == null) {
                continue;
            }

            if (fill != i) {
                mServers[fill] = mServers[i];
                mServers[fill].setSettingsIndex(fill);
                mServers[fill].save();
            }
            fill++;
        }

        for (int i = fill; i < MAX_NUM_HOSTS; i++) {
            if (mServers[i] != null) {
                mServers[i] = null;
                ServerInfo.remove(mPrefs, i);
            }
        }
    }            

    public void
    holdPrefsUpdate ()
    {
        mPrefsLockCount++;
    }

    public void
    releasePrefsUpdate ()
    {
        if (mPrefsLockCount > 0) {
            if (--mPrefsLockCount == 0) {
                saveSessionPrefs();
            }
        }
    }
    
    public void
    saveSessionPrefs ()
    {
        /* unless we're locked, run through our session list and save anyone that's dirty */
        if (mPrefsLockCount > 0) {
            return;
        }
        
        /* write the sessions */
        for (int i = 0; i < mNumHosts; ++i) {
            ServerInfo info = (ServerInfo) mServers[i];
            if (info != null) {
                info.save();
            }
        }

        if (mPrefs.find("auto-wrap/-1") != -1) {
            /* we've had a lot of crap in the settingsdb in the past, so make a
             * small effort here to blow it away.
             */
            mPrefs.remove("auto-wrap/-1");
            mPrefs.remove("emulator/-1");
            mPrefs.remove("font/-1");
            mPrefs.remove("hostname/-1");
            mPrefs.remove("name/-1");
            mPrefs.remove("port/-1");
            mPrefs.remove("protocol/-1");
            mPrefs.remove("username/-1");
            for (int i = 0; i < 16; i++) {
                mPrefs.remove("hostIndex" + i);
            }
        }
    }

    /**
     * Return the host list so SessionListView can display it.
     */
    public List
    getServerList ()
    {
        List out = new ArrayList();
        for (int i = 0; i < mNumHosts; i++) {
            out.add(mServers[i]);
        }
        return out;
    }

    /**
     * After adjusting the indexes of ServerInfo objects returned from
     * {@link #getServerList()}, call this method to change the order of the
     * internal server list to match.
     */
    public void
    reorderServers ()
    {
        for (Iterator i = getServerList().iterator(); i.hasNext(); ) {
            ServerInfo info = (ServerInfo) i.next();
            mServers[info.getSettingsIndex()] = info;
        }
    }
    
    /* package */ boolean
    hostListIsFull ()
    {
        return (mNumHosts >= MAX_NUM_HOSTS);
    }

    public int
    addServer (ServerInfo info)
    {
        return addServer(info, true);
    }

    public int
    addServer (ServerInfo info, boolean flush)
    {
        if (hostListIsFull()) {
            System.out.println("CANNOT add another host!");
            return -1;
        }

        // find a free slot
        for (int i = 0; i < MAX_NUM_HOSTS; i++) {
            if (mServers[i] == null) {
                ++mNumHosts;

                mServers[i] = info;
                info.setSettingsIndex(i);

                if (flush) {
                    saveSessionPrefs();

                    /* tell anyone that cares */
                    mListener.serverAdded(info);
                }

                return i;
            }
        }

        System.out.println("out session count is out of synch with the index?");
        return -1;
    }

    public void
    deleteServer (ServerInfo info)
    {
        int index = info.getSettingsIndex();

        info.clear();
        // must call save before changing the index, since it uses the index to save
        info.save();

        // give the session a new index so that it goes away in the prefs
        info.setSettingsIndex(-1);

        // now clear it from our list
        mServers[index] = null;
        mNumHosts--;

        packHostList();

        /* save our prefs at a high level so we get the new index */
        saveSessionPrefs();

        /* tell anyone that cares */
        mListener.serverRemoved(info);
    }

    public void
    sessionChanged (ServerInfo info)
    {
        saveSessionPrefs();
        mListener.serverUpdated(info);
    }

    public void
    replaceSession (ServerInfo oldInfo, ServerInfo info)
    {
        int index = oldInfo.getSettingsIndex();

        // copy whatever's absolutely necessary
        info.mHostname = oldInfo.mHostname;

        mServers[index] = info;
        info.setSettingsIndex(index);

        saveSessionPrefs();
        mListener.serverListChanged();
    }

    public void
    consoleChanged (TerminalConsole console)
    {
        mListener.sessionUpdated(console);
    }

    public void
    consoleAdded (TerminalConsole console)
    {
        ServerInfo info = console.getHostInfo();

        synchronized (mConsoleList) {
            // awkward way to keep the list sorted.
            boolean added = false;

            for (int i = 0; i < mConsoleList.size(); i++) {
                TerminalConsole tc = (TerminalConsole) mConsoleList.get(i);
                if (info.getSettingsIndex() < tc.getHostInfo().getSettingsIndex()) {
                    mConsoleList.add(i, console);
                    added = true;
                    break;
                }
            }

            if (! added) {
                mConsoleList.add(console);
            }
        }
        
        mListener.sessionAdded(console);
    }

    public void
    consoleRemoved (TerminalConsole console)
    {
        synchronized (mConsoleList) {
            mConsoleList.remove(console);
        }

        mListener.sessionRemoved(console);
    }

    /**
     * Returns the console at a given index from the session list, or null if
     * the index is out of range.  For convenience, it also follows python
     * index conventions (negative indices count from the end of the list).
     */
    public TerminalConsole
    consoleAt (int index)
    {
        synchronized (mConsoleList) {
            if (index < 0) {
                index += mConsoleList.size();
            }
            try {
                return (TerminalConsole) mConsoleList.get(index);
            } catch (ArrayIndexOutOfBoundsException x) {
                return null;
            }
        }
    }

    public int
    getConsoleIndex (TerminalConsole console)
    {
        int numConsoles = mConsoleList.size();
        if ((numConsoles == 0) || (console == null)) {
            return -1;
        }
        
        for (int i = 0; i < numConsoles; i++) {
            TerminalConsole consoleAt = consoleAt(i);
            if ((consoleAt != null) && (consoleAt == console)) {
                return i;
            }
        }
        
        return -1;
    }

    public int
    getNextConsoleIndex (TerminalConsole console)
    {
        int i = getConsoleIndex(console);

        if (i >= 0) {
            i++;
            if (i >= mConsoleList.size()) {
                i = 0;
            }
        }
        return i;
    }

    public int
    getPreviousConsoleIndex (TerminalConsole console)
    {
        int i = getConsoleIndex(console);

        if (i >= 0) {
            i--;
            if (i < 0) {
                i = mConsoleList.size() - 1;
            }
        }
        return i;
    }

    private boolean
    consoleNameExists (String name)
    {
        int sz = mConsoleList.size();
        for (int i = 0; i < sz; i++) {
            TerminalConsole console = consoleAt(i);
            if (console.getName().equals(name)) {
                return true;
            }
        }
        
        return false;
    }

    /* package */ String
    createUniqueConsoleName (ServerInfo info)
    {
        String name = info.getSessionName();

        synchronized (mConsoleList) {
            int index = 1;
            while (consoleNameExists(name)) {
                index++;
                name = info.getSessionName() + " [" + index + "]";
            }
            return name;
        }
    }

    /* package */ void
    addActiveSessionsToMenu (Menu actionMenu, TerminalConsole current)
    {
        boolean divided = false;

        synchronized (mConsoleList) {
            int sz = mConsoleList.size();
            for (int i = 0; i < sz; i++) {
                TerminalConsole console = (TerminalConsole) mConsoleList.get(i);
                if (console != null) {
                    if (!divided) {
                        actionMenu.addDivider();
                        divided = true;
                    }

                    MenuItem item = new MenuItem(console.getName(), EVENT_SWITCH_CONSOLE + i, 0, null,
                                                 Terminal.instance().getTerminalScreen());
                    if (console == current) {
                        item.check();
                    }

                    actionMenu.addItem(item);
                }
            }            
        }
    }

    /* package */ void
    broadcastResize (int width, int height)
    {
        synchronized (mConsoleList) {
            int sz = mConsoleList.size();
            for (int i = 0; i < sz; i++) {
                TerminalConsole tc = (TerminalConsole) mConsoleList.get(i);
                tc.resize(width, height);
            }
        }
    }

    public SettingsDB
    getSettingsDB()
    {
        return mPrefs;
    }
    
    public TerminalPreferences
    getTerminalPreferences()
    {
        return mTerminalPreferences;
    }
    
    
    private static SettingsDB   mPrefs;
    
    private TerminalPreferences mTerminalPreferences;
    private int                 mNumHosts;
    private ServerInfo          mServers[];
    private int                 mPrefsLockCount;
    private List                mConsoleList;
    private SessionListener     mListener;
    
    private static final int    MAX_NUM_HOSTS = 16;
}
