package dataServerDomain;

import sharedInterfaces.*;
import sharedDataInterfaces.*;

import java.io.*;
import java.net.*;
import javax.swing.*;

public class DataServerFacade
    implements DataServerFacadeInterface
{
    private String serverAddrCPort;
    private String experiment;
    private int shot;

    private boolean isConnected;
    private boolean isOpen;

    private Socket socket; // socket for connecting to MDSPlus server
    private DataOutputStream output; // output stream for socket
    private DataInputStream input; // input stream for socket

    private MDSTree treeModel = null; // tree model to return to GUI
    private MDSTreeNode treeNode;

    public DataServerFacade()
    {
        serverAddrCPort = null;
        experiment = null;
        shot = -1;

        isConnected = false;
        isOpen = false;

        socket = null;
        input = null;
        output = null;
        treeNode = new MDSTreeNode();
        treeModel = new MDSTree(this, treeNode);
    }


    public void constructTree(JTree tree) throws IOException
    {
        treeModel.getTree();
        tree.setModel(treeModel);
    }

    /** Attempts to open a new connection to the MDSPlus server.
        If no port given the default is port 8000. Sets flags
        @param _source The address of the MDSPlus server and port
        @throws IOException If already connected */
    public void connect(String _source) throws IOException
    {
        serverAddrCPort = _source;

        if (isConnected)
        {
            throw new IOException("Already connected");
        }

        /* First check if a port is specified in the string */
        int i = serverAddrCPort.indexOf(":");
        String addr;
        int port;
        if (i == -1)
        {
            addr = serverAddrCPort;
            port = 8000;
        }
        else
        {
            addr = serverAddrCPort.substring(0, i);
            port = Integer.parseInt(
                serverAddrCPort.substring(i + 1, serverAddrCPort.length()));
        }

        /* Connect to remote server */
        socket = new Socket(addr, port);
        output = new DataOutputStream(new
                                      BufferedOutputStream(socket.
            getOutputStream()));
        input = new DataInputStream(new
                                    BufferedInputStream(socket.getInputStream()));

        /* Send login name */
        MDSMessage message = new MDSMessage("JAVA_USER");

        message.send(output);
        message.receive(input);

        /* Flag that we are now connected */
        isConnected = true;
    }

    /** Disconnects from the MDSPlus server, and resets flags.
        @throws IOException If not already connected */
    public void disconnect() throws IOException
    {
        if (!isConnected)
        {
            throw new IOException("Not connected");
        }

        socket.close();
        socket = null;
        output = null;
        input = null;
        isConnected = false;
        isOpen = false;
    }

    /**  @throws IOException If not connected, already open, or if the server
         is down or not responding */
    public void open(String _experiment, int _shot) throws IOException
    {
        MDSDescriptor status;

        if (!isConnected)
        {
            throw new IOException("Not connected");
        }

        if (isOpen)
        {
            throw new IOException("Already open");
        }

        status = sendMessage("JavaOpen(\"" + _experiment +
                             "\"," + _shot + ")");

        if (status == null)
        {
            throw new IOException("Null response from server");
        }
        else
        {
            if (status.getIntData() != null && status.getIntData().length > 0)
            { // diagnostic write of return data from server
                System.out.println("MDSNetworkSource::Open:" +
                                   "result = " + status.getIntData()[0]);
                //An Odd status from MDSplus means success
                if ( (status.getIntData()[0] & 1) != 0)
                {
                    experiment = _experiment;
                    shot = _shot;
                    isOpen = true;
                }
                else
                    throw new IOException("Cannot open MDSplus database ");
            }
            else
                throw new IOException("Cannot open MDSplus database");
        }
    }

    /** @throws IOException if not connected to a server or no experiment open. */
    public void close() throws IOException
    {

        if (!isConnected)
        {
            throw new IOException("Not connected");
        }
        if (!isOpen)
        {
            throw new IOException("Not open");
        }
        sendMessage("JavaClose(\"" + experiment + "\"," + shot + ")");

        isOpen = false;
    }

    /**
         Sends a message to the MDSPlus server and returns the response.
     This is a high level method and calls the more complicated sendMessage()
     method which hides the more complex implementation necessary to communicate
         with an MDSPlus server.
         @param expression The message to be sent to the server
         @return The response from the server
     @throws IOException If not connected to a server or no experiment open.
     */
    public MDSDescriptor evaluate(String expression) throws IOException
    {
        if (!isConnected)
        {
            throw new IOException("Not connected");
        }
        if (!isOpen)
        {
            throw new IOException("Not open");
        }
        return sendMessage(expression);
    }

    /** Sends a message to the MDSPlus server and returns the response.
         This method works at a lower level than evaluate() and requires
        the use of a MDSMessage object.
         @param expression The message to be sent to the server
         @return The response from the server
         @throws IOException passed up from MDSMessage send or receive methods
     */
    private MDSDescriptor sendMessage(String msg) throws IOException
    {
        MDSMessage message = new MDSMessage(msg);
        message.send(output);
        message.receive(input);
        return message.toDescriptor();
    }

    // get and set and status methods
    public boolean isConnected()
    {
        return isConnected;
    }

    public boolean isOpen()
    {
        return isOpen;
    }

    public String getExperiment()
    {
        return experiment;
    }

    public int getShot()
    {
        return shot;
    }

    public GraphDataInterface getPlotData(String shortFileAddress)
    {
        MDSDescriptor result1 = null, result2 = null, result3 = null, result4 = null;
        String expression1 = "", expression2 = "", expression3 = "",
            expression4 = "";
        boolean hasGraphData = false, isDataError = false;
        double[] xVals = null, yVals = null; // plot data arrays
        String xUnits = "", yUnits = "";
        int xLen = 0, yLen = 0;

        String fullPath = "\\" + getExperiment().toUpperCase()
            + "::" + "TOP";
        fullPath = fullPath + shortFileAddress;

        // try to read the data
        try
        {
            expression1 = "units_of(" + shortFileAddress + ")"; // Y title
            result1 = evaluate(expression1);

            expression2 = shortFileAddress; //Y values
            result2 = evaluate(expression2);

            expression3 = "units_of(dim_of(" + shortFileAddress + "))"; // X title
            result3 = evaluate(expression3);

            expression4 = "dim_of(" + shortFileAddress + ")"; // X values
            result4 = evaluate(expression4);
        }
        catch (IOException e)
        {
            System.out.println("Failed to communicate with server: "
                               + e.getMessage());
            System.exit( -1);
        }

        // check x and y axis label types
        if ( (result1.getDtype() != result1.DTYPE_CSTRING) ||
            (result3.getDtype() != result3.DTYPE_CSTRING))
        {
            System.out.println(
                "Error: String descriptor not returned for one of:");
            System.out.println("        " + expression1 + " and " + expression3);
            System.out.println(
                "(should never happen if MDSPlus database is correct)");
            isDataError = true;
        }

        // get x and y labels
        yUnits = result1.getCstringData();
        xUnits = result3.getCstringData();

        // check xVals and yVals types
        // - double => legal data
        // - strings => some data does not exist; No y data => no data
        if ( (result2.getDtype() != result2.DTYPE_DOUBLE))
        {
            System.out.println("Error: Double descriptor not returned for:");
            System.out.println(" yVals:       " + expression2);
            isDataError = true;
        }
        if ( (result4.getDtype() != result4.DTYPE_CSTRING) &&
            (result4.getDtype() != result4.DTYPE_DOUBLE))
        {
            System.out.println(
                "Error: Double or String descriptor not returned for:");
            System.out.println(" xVals:       " + expression4);
            isDataError = true;
        }

        if ( (!isDataError) && (result2.getDtype() == result2.DTYPE_DOUBLE))
        { // legal y data exists
            yLen = result2.getDoubleData().length;
            if (yLen < 2)
            {
                System.out.println(
                    "Error: " + expression2 + " is not an array");
                isDataError = true;
            }
            else
            {
                boolean isXData = false;
                if (result4.getDtype() == result4.DTYPE_DOUBLE)
                {
                    isXData = true;
                    xLen = result4.getDoubleData().length;
                }
                else
                {
                    xLen = yLen;
                }

                if (xLen != yLen)
                {
                    System.out.println("Warning: xLen not yLen in data");
                    System.out.println("xLen = " + xLen + " yLen= " + yLen);
                    System.out.println("(xData will be ignored for plot)");
                    xLen = yLen;
                    isXData = false;
                }

                yVals = new double[yLen];
                xVals = new double[yLen];

                for (int i = 0; i < yLen; i++)
                { // get y data
                    yVals[i] = result2.getDoubleDataElement(i);
                }

                for (int i = 0; i < yLen; i++)
                {
                    if (isXData) // get x data
                    {
                        xVals[i] = result4.getDoubleDataElement(i);
                    }
                    else // set x array to indices
                    {
                        xVals[i] = i;
                    }
                }
            }
        }
        // create a new GraphData object
        GraphData data = new GraphData();
        data.initialise(xVals, yVals, xUnits, yUnits,
                        shortFileAddress, getShot(), isDataError);
        return data;
    }
}
