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

package com.danger.terminal;

import danger.app.SettingsDB;
import danger.app.SettingsDBException;
import danger.util.StdActiveObject;
import java.util.Vector;


/* package */ class ServerInfo
    extends StdActiveObject
    implements Comparable
{
    public
    ServerInfo (SessionManager manager)
    {
        init(manager);
    }
    
    public
    ServerInfo (SessionManager manager, String hostname, String username, int port)
    {
        init(manager);

        /* override the defaults */
        mHostname = hostname;
        mUsername = username;
        mPort = port;
    }
    
    private void
    init (SessionManager manager)
    {
        mSessionManager = manager;
        mPrefs = manager.getSettingsDB();

        clear();

        /* some basic stuff */
        mDirty = false;
        mInSettings = false;

        mConsoles = new Vector();
        mStatus = STATUS_CLOSED;
        
        /* pull other defaults from the app preferences */
        mFont = mSessionManager.getTerminalPreferences().getDefaultFont();
        mUsername = null;
        
        mSessionNameBuffer = null;
        mTransient = false;
    }

    /**
     * When a host record is deleted from the list, this method clears out the
     * meaningful fields so we can save a relatively-empty record back to the
     * backing store.
     *
     * Notably, the index (mSettingsIndex) should NOT be cleared here.
     */
    /* package */ void
    clear ()
    {
        mDirty = true;

        mSessionNameIsDefault = true;
        mSessionName = "";
        mHostname = "";
        mUsername = "";
        mFont = "";
        mProtocol = PROT_TELNET;
        mPort = -1;
        mAutoWrap = true;
        mEmulator = VirtualConsole.EMULATE_VT100;
        mScrollback = 100;
        mBackspaceSendsDel = true;
        mCodec = "ISO-8859-1";
    }        

    // superclass's javadoc suffices
    public int
    compareTo (Object otherObject)
    {
        // force ClassCastException on uncomparable types
        ServerInfo other = (ServerInfo) otherObject;

        int isEqual = mHostname.compareTo(other.mHostname);
        if ((isEqual == 0) && (mUsername != null)) {
            isEqual = mUsername.compareTo(other.mUsername);
        }

        if (isEqual == 0) {
            isEqual = mProtocol - other.mProtocol;
        }

        if (isEqual == 0) {
            isEqual = mPort - other.mPort;
        }

        return isEqual;
    }

    public void
    setStatus(int status)
    {
        if ((status < STATUS_CLOSED) || (status > STATUS_OPEN)) {
            throw new IllegalArgumentException("status == " + status);
        }

        mStatus = status;
        update();
    }

    public int
    getStatus()
    {
        return mStatus;
    }
    
    public void
    setHostname (String hostname)
    {
        if (hostname != mHostname) {
            mDirty = true;
            mHostname = hostname;
            mSessionNameBuffer = null;
            mSessionManager.sessionChanged(this);
        }
    }
    
    public String
    getHostname ()
    {
        return mHostname;
    }
    
    /**
     * Return the hostname with the port attached, as in
     * <code>"www.yahoo.com:80"</code>.
     * 
     * @return hostname with port attached
     */
    public String
    getFullHostname ()
    {
        if (mPort == DEFAULT_PORT[mProtocol]) {
            return mHostname;
        } else {
            if (mPort < 0) {
                return mHostname + ":local";
            } else {
                return mHostname + ":" + mPort;
            }
        }
    }

    public void
    setSessionName (String name)
    {
        if (name != mSessionName) {
            mDirty = true;
            mSessionName = name;

            /* remember whether we need to keep the session name in sync... */
            mSessionNameIsDefault = mSessionName.equals(getDefaultSessionName());

            mSessionManager.sessionChanged(this);
        }
    }

    public String
    getSessionName()
    {
        /* if our session name is default and it's changed, then get the new one */
        if (mSessionNameBuffer == null && mSessionNameIsDefault) {
            setSessionName(getDefaultSessionName());
        }
        
        return mSessionName;
    }
    
    public void
    setUsername (String username)
    {
        if (username != mUsername) {
            mDirty = true;
            mUsername = username;
            mSessionNameBuffer = null;
            mSessionManager.sessionChanged(this);
        }
    }
    
    public String
    getUsername ()
    {
        return mUsername;
    }

    /* you cannot change the protocol field of an existing session, because
     * this may be an object descended from HostInfo, based on which
     * protocol we're using.
     */
    public void
    setProtocol (int protocol)
    {
        mProtocol = protocol;
    }

    public final int
    getProtocol()
    {    
        return mProtocol;
    }
    
    public String
    getProtocolName()
    {
        switch (mProtocol) {
        case PROT_TELNET:
            return Terminal.instance().getString(TerminalResources.PROTOCOL_TELNET);
        case PROT_RAW:
        default:
            return Terminal.instance().getString(TerminalResources.PROTOCOL_RAW);
        }
    }
    
    public final void
    setPort (int port)
    {
        if (port != mPort) {
            mDirty = true;
            mPort = port;
            mSessionNameBuffer = null;
            mSessionManager.sessionChanged(this);
        }
    }
        
    public final int
    getPort()
    {    
        return mPort;
    }

    public void
    setFont (String font)
    {
        if (font != mFont) {
            mDirty = true;
            mFont = font;
            mSessionManager.sessionChanged(this);
        }

        /* update the default font as well */
        /* BRAIN DAMAGE: I'm not sure this is the best place, but it is localized */
        mSessionManager.getTerminalPreferences().setDefaultFont(font);
    }

    public final boolean
    getAutoWrap ()
    {
        return mAutoWrap;
    }

    public final void
    setAutoWrap (boolean wrap)
    {
        if (wrap != mAutoWrap) {
            mDirty = true;
            mAutoWrap = wrap;
            mSessionManager.sessionChanged(this);
        }
    }

    public final int
    getEmulator ()
    {
        return mEmulator;
    }

    public final void
    setEmulator (int emulator)
    {
        mEmulator = emulator;
    }

    public final int
    getScrollback ()
    {
        return mScrollback;
    }

    public final void
    setScrollback (int scrollback)
    {
        mScrollback = scrollback;
    }

    public final boolean
    getBackspaceSendsDel ()
    {
        return mBackspaceSendsDel;
    }

    public final void
    setBackspaceSendsDel (boolean bsd)
    {
        mBackspaceSendsDel = bsd;
    }

    public final String
    getCodec ()
    {
        return mCodec;
    }

    public final void
    setCodec (String codec)
    {
        mCodec = codec;
    }

    public int
    getSettingsIndex ()
    {
        return mSettingsIndex;
    }
    
    public void
    setSettingsIndex (int index)
    {
        if (!mInSettings || (index != mSettingsIndex)) {
            mSettingsIndex = index;
            mInSettings = true;
            mDirty = true;
        }
    }

    public void
    setTransient (boolean trans)
    {
        mTransient = trans;
    }

    public String
    getFont ()
    {
        return mFont;
    }

    public String
    getDefaultSessionName ()
    {
        if ( mSessionNameBuffer == null ) {
            int len;
            boolean haveUsername;

            haveUsername = ( mUsername != null && mUsername.length() > 0 );

            len = mHostname.length();

            if ( haveUsername ) {
                len += mUsername.length();
            }

            /* add room for port - max ":65536" */
            mSessionNameBuffer = new StringBuffer(len + 6);

            if ( haveUsername ) {
                mSessionNameBuffer.append(mUsername);
                mSessionNameBuffer.append("@");
            }

            mSessionNameBuffer.append(mHostname);

            if (getPort() != DEFAULT_PORT[mProtocol]) {
                mSessionNameBuffer.append(":");
                mSessionNameBuffer.append(getPort());
            }
        }

        return mSessionNameBuffer.toString();
    }

    public boolean
    getSessionNameIsDefault()
    {
        return mSessionNameIsDefault;
    }

    public Vector
    getConsoles()
    {
        return mConsoles;
    }

    public void
    consoleChanged (TerminalConsole console)
    {
        /* BRAIN DAMAGE - huge hack */
        mSessionManager.consoleChanged(console);
    }

    public void
    addConsole (TerminalConsole console)
    {
        synchronized (mConsoles) {
            if (! mConsoles.contains(console)) {
                mConsoles.addElement(console);
            }
        }

        mSessionManager.consoleAdded(console);
    }
    
    public void
    removeConsole (TerminalConsole console)
    {
        boolean removeEntry = false;

        synchronized (mConsoles) {
            mConsoles.removeElement(console);
            if (mTransient && (mConsoles.size() == 0)) {
                removeEntry = true;
            }
        }

        mSessionManager.consoleRemoved(console);
        if (removeEntry) {
            mSessionManager.deleteServer(this);
        }
    }

    // so subclasses can override to add their own stuff
    protected void
    saveTLV (TLVSet tlv)
    {
        tlv.addString(TLV_HOSTNAME, mHostname);
        tlv.addInt(TLV_PORT, mPort);
        if (mUsername != null) {
            tlv.addString(TLV_USERNAME, mUsername);
        }

        tlv.addString(TLV_SESSION_NAME, mSessionName);
        tlv.addString(TLV_FONT, mFont);
        tlv.addInt(TLV_PROTOCOL, mProtocol);
        tlv.addBoolean(TLV_AUTO_WRAP, mAutoWrap);
        tlv.addInt(TLV_EMULATOR, mEmulator);
        tlv.addInt(TLV_SCROLLBACK, mScrollback);
        tlv.addBoolean(TLV_BACKSPACE, mBackspaceSendsDel);
        tlv.addString(TLV_CODEC, mCodec);
        
        tlv.getBytes(); // so toString will work
    }

    public void
    save ()
    {
        if (!mDirty) {
            return;
        }

        /* if we don't have a settings index, get one */
        if (!mInSettings) {
            mSettingsIndex = mSessionManager.addServer(this);
            mInSettings = true;
        }
        
        /* save into a single settings blob using TLV.  the pdm pirates say this
         * is much better than using smaller, typed fields.
         */
        TLVSet tlv = new TLVSet();
        saveTLV(tlv);
        mPrefs.setBytes(SERVER_ENTRY_KEY + "/" + mSettingsIndex, tlv.getBytes());

        // we wait till here to set prefs-version so we don't bother setting it when no servers are being saved
        mPrefs.setIntValue("prefs-version", 2);

        mDirty = false;
    }
    
    protected void
    loadTLV (TLVSet tlv)
    {
        mHostname = tlv.getString(TLV_HOSTNAME, "");
        mPort = tlv.getInt(TLV_PORT, DEFAULT_PORT[PROT_SSH2]);
        mUsername = tlv.getString(TLV_USERNAME, "");
        mFont = tlv.getString(TLV_FONT, Terminal.instance().getString(TerminalResources.DEFAULT_FONT));
        mProtocol = tlv.getInt(TLV_PROTOCOL, PROT_SSH2);
        mAutoWrap = tlv.getBoolean(TLV_AUTO_WRAP, true);
        mSessionName = tlv.getString(TLV_SESSION_NAME, null);
        if (mSessionName == null) {
            mSessionName = getDefaultSessionName();
            mDirty = true;
        }
        mEmulator = tlv.getInt(TLV_EMULATOR, VirtualConsole.EMULATE_VT100);
        mScrollback = tlv.getInt(TLV_SCROLLBACK, 100);
        mBackspaceSendsDel = tlv.getBoolean(TLV_BACKSPACE, true);
        mCodec = tlv.getString(TLV_CODEC, "ISO-8859-1");
        
        /* remember whether we need to keep the session name in sync... */
        mSessionNameIsDefault = mSessionName.equals(getDefaultSessionName());
    }
    

    // -- old cruft from using individual settings entries
    
    protected String
    getStringValue (String key, String defaultValue)
        throws SettingsDBException
    {
        if (!mInSettings) {
            return defaultValue;
        }

        String ret = mPrefs.getStringValue(key + "/" + mSettingsIndex);
        if (ret == null) {
            return defaultValue;
        } else {
            return ret;
        }
    }
    
    protected int
    getIntValue (String key)
        throws SettingsDBException
    {
        if (!mInSettings) {
            return -1;
        }
        
        return mPrefs.getIntValue(key + "/" + mSettingsIndex);
    }

    protected boolean
    getBooleanValue (String key, boolean def)
    {
        int value;
        
        if (!mInSettings) {
            return def;
        }

        try {
            value = mPrefs.getIntValue(key + "/" + mSettingsIndex);
            return ( ( value == 0 ) ? false : true );
        } catch (SettingsDBException x) {
            return def;
        }
    }

    protected void
    oldStyleLoadHook ()
        throws SettingsDBException
    {
        // pass
    }
    
    private void
    loadOldStyle ()
        throws SettingsDBException
    {
        if (!mInSettings) {
            System.out.println("terminal.HostInfo: Attempt to load unknown host from settings");
            throw new SettingsDBException("Not in settings");
        }

        mHostname = getStringValue(HOSTNAME_KEY, "");
        mUsername = getStringValue(USERNAME_KEY, "");

        mFont = getStringValue(FONT_KEY, Terminal.instance().getString(TerminalResources.DEFAULT_FONT));
        mPort = getIntValue(PORT_KEY);
        mProtocol = getIntValue(PROTOCOL_KEY);
        mAutoWrap = getBooleanValue(AUTO_WRAP_KEY, true);
        mSessionName = getStringValue(SESSIONNAME_KEY, null);
        if (mSessionName == null) {
            mSessionName = getDefaultSessionName();
        }

        try {
            mEmulator = getIntValue(EMULATOR_KEY);
        } catch (SettingsDBException x) {
            mEmulator = VirtualConsole.EMULATE_VT100;
        }

        try {
            mScrollback = getIntValue(SCROLLBACK_KEY);
        } catch (SettingsDBException x) {
            mScrollback = 100;
        }

        mBackspaceSendsDel = getBooleanValue(BACKSPACE_KEY, true);
        mCodec = getStringValue(CODEC_KEY, "ISO-8859-1");

        /* remember whether we need to keep the session name in sync... */
        mSessionNameIsDefault = mSessionName.equals(getDefaultSessionName());
        
        // now, erase the old icky prefs, and save them new-style
        System.err.println("Converting old-style server entry " + mSettingsIndex + " ...");
        mPrefs.remove(HOSTNAME_KEY + "/" + mSettingsIndex);
        mPrefs.remove(USERNAME_KEY + "/" + mSettingsIndex);
        mPrefs.remove(FONT_KEY + "/" + mSettingsIndex);
        mPrefs.remove(PORT_KEY + "/" + mSettingsIndex);
        mPrefs.remove(PROTOCOL_KEY + "/" + mSettingsIndex);
        mPrefs.remove(AUTO_WRAP_KEY + "/" + mSettingsIndex);
        mPrefs.remove(SESSIONNAME_KEY + "/" + mSettingsIndex);
        mPrefs.remove(EMULATOR_KEY + "/" + mSettingsIndex);
        mPrefs.remove(SCROLLBACK_KEY + "/" + mSettingsIndex);
        mPrefs.remove(BACKSPACE_KEY + "/" + mSettingsIndex);
        mPrefs.remove(CODEC_KEY + "/" + mSettingsIndex);
        
        oldStyleLoadHook();
        
        mDirty = true;
        save();
    }
    
    /* package */ static void
    remove (SettingsDB prefs, int index)
    {
        prefs.remove(PROTOCOL_KEY + "/" + index);
        prefs.remove(HOSTNAME_KEY + "/" + index);
        prefs.remove(USERNAME_KEY + "/" + index);
        prefs.remove(SERVER_ENTRY_KEY + "/" + index);
    }
    
    public static ServerInfo
    load (SessionManager sm, int index)
    {
        int protocol = 0;
        SettingsDB prefs = sm.getSettingsDB();
        TLVSet tlv = null;
        
        try {
            byte[] data = prefs.getBytes(SERVER_ENTRY_KEY + "/" + index);
            tlv = new TLVSet(data);
            protocol = tlv.getInt(TLV_PROTOCOL, PROT_SSH2);
        } catch (SettingsDBException x) {
            // just try loading from old prefs then
            try {
                protocol = prefs.getIntValue(PROTOCOL_KEY + "/" + index);
            } catch (SettingsDBException e) {
                return null;
            }
        } catch (TLVException x) {
            System.err.println("error: TLV exception: " + x);
            prefs.remove(SERVER_ENTRY_KEY + "/" + index);
            tlv = null;
            // fall thru and use old prefs, if present
        }

        ServerInfo info = createServerInfo(sm, protocol);
        info.setSettingsIndex(index);
        if (tlv != null) {
            info.loadTLV(tlv);
            // in case we added new info:
            info.save();
        } else {
            // old-style
            try {
                info.loadOldStyle();
            } catch (SettingsDBException x) {
                return null;
            }
        }
        
        return info;
    }
    
    public static ServerInfo
    createServerInfo (SessionManager sm, int protocol)
    {
        ServerInfo info = null;
        
        switch (protocol) {
        case ServerInfo.PROT_SSH2:
            info = new SSHServerInfo(sm);
            break;
        case ServerInfo.PROT_TELNET:
        case ServerInfo.PROT_RAW:
            info = new ServerInfo(sm);
            info.setProtocol(protocol);
            break;
        default:
            info = null;
            break;
        }

        return info;
    }


    protected String            mSessionName;
    protected String            mHostname;
    protected String            mUsername;
    protected String            mFont;
    protected int               mProtocol;
    protected int               mPort;
    protected boolean           mAutoWrap;
    protected int               mEmulator;
    protected int               mScrollback;
    protected boolean           mBackspaceSendsDel;
    protected String            mCodec;

    protected SettingsDB        mPrefs;
    protected SessionManager    mSessionManager;
    protected int               mSettingsIndex;
    protected boolean           mDirty;
    protected boolean           mInSettings;
    protected boolean           mSessionNameIsDefault;
    protected boolean           mTransient;
    
    protected StringBuffer      mSessionNameBuffer;
    
    private int                 mStatus;
    private Vector              mConsoles;

    private static final String         SESSIONNAME_KEY = "name";
    private static final String         HOSTNAME_KEY = "hostname";
    private static final String         USERNAME_KEY = "username";
    private static final String         PORT_KEY = "port";
    private static final String         PROTOCOL_KEY = "protocol";
    private static final String         FONT_KEY = "font";
    private static final String         AUTO_WRAP_KEY = "auto-wrap";
    private static final String         EMULATOR_KEY = "emulator";
    private static final String         SCROLLBACK_KEY = "scrollback";
    private static final String         BACKSPACE_KEY = "backspace";
    private static final String         CODEC_KEY = "codec";

    static public final int     STATUS_CLOSED = 0;
    static public final int     STATUS_CONNECTING = 1;
    static public final int     STATUS_OPEN = 2;
    
    // magic numbers to set protocol
    public static final int     PROT_RAW = 0;
    public static final int     PROT_TELNET = 1;
    public static final int     PROT_SSH2 = 2;
    public static final int     DEFAULT_PORT[] = { 23, 23, 22 };
    
    // TLV keys
    private static final int    TLV_SESSION_NAME = 1;
    private static final int    TLV_HOSTNAME = 2;
    private static final int    TLV_PORT = 3;
    private static final int    TLV_USERNAME = 4;
    private static final int    TLV_FONT = 5;
    private static final int    TLV_PROTOCOL = 6;
    private static final int    TLV_AUTO_WRAP = 7;
    private static final int    TLV_EMULATOR = 8;
    private static final int    TLV_SCROLLBACK = 9;
    private static final int    TLV_BACKSPACE = 10;
    private static final int    TLV_CODEC = 11;
    private static final String SERVER_ENTRY_KEY = "server-entry";
}
