/* SocketThread.java
 * Copyright (C) 1996 by William Giel
 *
 * E-mail: rvdi@usa.nai.net
 * WWW: http://www.nai.net/~rvdi/home.htm
 *
 ***************************************************************************
 * Abstract
 * --------
 * This unit provides threaded socket functionality. This is implemented
 * this way in order to prevent stalling program operation while a socket
 * connection is made. For example, if the connection hangs, the program
 * can still be terminated by clicking on the close icon, or Cancel button,
 * or reset with the Reset button.
 ***************************************************************************
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation without fee for NON-COMMERCIAL purposes is hereby granted.
 * 
 * THE AUTHOR MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
 * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. THE AUTHOR SHALL NOT BE LIABLE
 * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
 * 
 ***************************************************************************/
 
import java.awt.*;
import java.lang.*;
import java.util.*;
import java.net.*;
import java.io.*;

///////////////////////////////
//Bring in our support classes
///////////////////////////////
import Julian;
import timeThread;
import Scroller;
import ttConst;
import Format;


class SocketThread extends Thread
{
    TextField serverTimeField;
    TextField correctionField;
    Scroller  scroller;
    double correction;
    boolean connected;
    String timeServer;
    timeThread serverTimeThread;
    Socket socket=null;

    
    public SocketThread(    String server,
                            TextField timeField,
                            TextField corrField,
                            Scroller s)
    {
        super();
        timeServer=server;
        serverTimeField=timeField;
        correctionField=corrField;
        scroller=s;
        correction=0;
        connected=false;
    }

    ///////////////////////////////////////////
    // Since we can't override Thread.stop()
    // we'll roll our own! We want to try to
    // close an open socket when the thread
    // is stopped.
    // (Does Socket.close() work yet in Win95?)
    ///////////////////////////////////////////
    public void stopSocket()
    {
        if(null != socket){
            try {
                    socket.close();
                    socket=null;
            }catch(Exception e) {}
        }
        super.stop();
    }

    ///////////////////////////////////////////////////////////////////////
    // snoopData attempts to evaluate each data item and reject it if its
    // residual exceeds three standard deviations. It returns a recomputed
    // mean value, based on only accepted data items.
    ///////////////////////////////////////////////////////////////////////
    double snoopData(Date sysDate[], String string[], int counter,
                                    double meanCorrection, double sigma)
    {
        double newCorrection = 0;
        int count=0;
        int rejects=0;

        scroller.newLine();
        scroller.appendText(ttConst.EVALUATING);                        

        for(int i=0; i < counter; i++){
            
            Date gmtDate = new Date(string[i]);

            ////////////////////////////////////
            //calculate correction from sysDate;
            ////////////////////////////////////
            double gmtJulian=Julian.date2julian(gmtDate);
            double sysJulian=Julian.date2julian(sysDate[i]);
            double correction = (gmtJulian-(sysJulian + (double)sysDate[i].getTimezoneOffset()/1440)) * 86400.0;

            ////////////////////////////////////////////////////////////
            // If residual is within three std. deviations, use it.
            ////////////////////////////////////////////////////////////
            if(Math.abs(correction-meanCorrection) < sigma * 3.0){
                newCorrection += correction;
                count++;
            }
            
            ///////////////////////////
            // otherwise, trash it.
            ///////////////////////////
            else rejects++;
        }

        /////////////////////////
        // Display what happened
        /////////////////////////
        if(rejects > 0){
            scroller.newLine();
            scroller.appendText(ttConst.REJECTS + rejects);
        }
        else{
            scroller.newLine();
            scroller.appendText(ttConst.NO_REJECTS);
        }

        /////////////////////////////////////////////
        // If everything is rejected (??) return the
        // original mean after displaying a warning.
        /////////////////////////////////////////////
        if(0 == count){
            scroller.newLine();
            scroller.appendText(ttConst.NO_DATA);
            return meanCorrection;
        }

        ///////////////////////////////////
        // Compute and return the new mean
        ///////////////////////////////////
        return newCorrection/(double)count;
    }

    /////////////////////////////////////////////////
    // getSigma calculates  and returns the standard
    // deviation of our data.
    /////////////////////////////////////////////////
    double getSigma(Date sysDate[], String string[], int counter, double meanCorrection)
    {
        double correction   =   0;
        double sum_v_sq     =   0;

        if(1 == counter)return 0;
        
        for(int i=0; i < counter; i++){
            
            Date gmtDate = new Date(string[i]);

            ////////////////////////////////////
            //calculate correction from sysDate;
            ////////////////////////////////////
            double gmtJulian=Julian.date2julian(gmtDate);
            double sysJulian=Julian.date2julian(sysDate[i]);
            correction = (gmtJulian-(sysJulian + (double)sysDate[i].getTimezoneOffset()/1440)) * 86400.0;
            double v = correction - meanCorrection;
            sum_v_sq += (v * v);
        }

        return Math.sqrt( sum_v_sq /(double)(counter - 1));
    }

    ////////////////////////////////////////////////////////////
    // getMean calculates the mean correction from our raw data
    ////////////////////////////////////////////////////////////
    double getMean(Date sysDate[], String string[], int counter)
    {
        double correction=0;
        
        for(int i=0; i < counter; i++){
            
            Date gmtDate = new Date(string[i]);
            
            ////////////////////////////////////
            //calculate correction from sysDate;
            ////////////////////////////////////
            double gmtJulian=Julian.date2julian(gmtDate);
            double sysJulian=Julian.date2julian(sysDate[i]);
            correction += (gmtJulian-(sysJulian + (double)sysDate[i].getTimezoneOffset()/1440)) * 86400.0;
        }
        
        //////////////////////////////
        // Compute and return the mean
        //////////////////////////////
        return correction/(double)counter;
    }

    public void run()
    {
        //////////////////////
        // Use 'daytime' port
        //////////////////////
        int PORT=13;


        /////////////////////////////////////////////////////
        // One-tic test...
        // Ocasionally, is network delay exceeds tolerances,
        // USNO sends only one tick, preceded by the message
        // "Only one tick will be sent." Normally, they send
        // as many as you want to grab. We try to grab
        // ttConst.SAMPLES (8)
        /////////////////////////////////////////////////////
        boolean oneTic=false;

        ////////////////////////////////
        // USNO format sends an asterisk
        // to mark the time it has just
        // displayed.
        ////////////////////////////////
        char HACK='*';
        
        socket          =   null;
        String timeline =   null;
        String temp     =   null;

        Date sysDate[]  =   new Date[ttConst.SAMPLES];
        String string[] =   new String[ttConst.SAMPLES];
        int counter=0;

        ////////////////////////////////////////
        // Initiallize these puppies to null
        ////////////////////////////////////////
        for(int i=0; i < ttConst.SAMPLES; i++){
            string[i]=null;
            sysDate[i]=null;
        }
 
        scroller.newLine();
        scroller.appendText(ttConst.CONTACTING + timeServer);
        
        try{
                socket = new Socket(timeServer,PORT);
                
                if(null != socket){
                    scroller.newLine();
                    scroller.appendText(ttConst.CONNECTED);
                    DataInputStream input = new DataInputStream(socket.getInputStream());

                    /////////////////////////////////
                    // We'll try for ttConst.SAMPLES
                    // data items.
                    /////////////////////////////////
                    while(counter<ttConst.SAMPLES){
                        
                        temp=input.readLine();

                        ////////////////////////////////
                        // This might be a HACK, so get
                        // the current system time, just
                        // in case we need it.
                        ////////////////////////////////
                        sysDate[counter]=new Date();

                        ////////////////////////////////////
                        // Check for the 'one tick' warning.
                        ////////////////////////////////////
                        if(-1 != temp.indexOf(ttConst.USNO_WARNING))oneTic=true;

                        /////////////////////////////////////
                        // Display what was received
                        /////////////////////////////////////
                        scroller.newLine();
                        scroller.appendText(temp);

                        ////////////////////////////////////////////
                        // If this line is a HACK (*) then the last
                        // line was a time string.
                        ////////////////////////////////////////////
                        if( null != temp && temp.indexOf(HACK)==0 ){
                            string[counter]=timeline;
                            counter++;
                            if(oneTic)break;
                        }
                        timeline=temp;
                    }
                }
        }catch(Exception e) {
            //////////////////////////////////////////////////
            // If we catch an exception, somethings not right
            // so we'd best discard anything that we received.
            // I know, this is a cop-out :)
            //////////////////////////////////////////////////
            counter = 0;}

        scroller.newLine();
        if(counter > 0)scroller.appendText(ttConst.FINISHED + counter);
        else{
            ///////////////////////////////////////
            // If we didn't get anything, there was
            // some kind of error.
            ///////////////////////////////////////
            scroller.appendText(ttConst.ERRORMSG);
            return;
        }               
     
        if(null != socket){
            try {
                    socket.close();
                    socket=null;
            }catch(Exception e) {}
        }

        ///////////////////////////////
        // We should not have any null
        // data.
        ///////////////////////////////
        for(int i=0; i<counter; i++)
            if(null == string[i] || null == (string[i] = Julian.usno2ietf(string[i]))){
                scroller.newLine();
                scroller.appendText(ttConst.ERRORMSG);
                return;
            }   

        /////////////////////////////////////////////
        // Calculate the mean correction
        /////////////////////////////////////////////
        correction = getMean(sysDate,string,counter);

        /////////////////////////////////////////////
        // If we've got more than one, let's try to
        // do statistics.
        /////////////////////////////////////////////
        if(counter > 1){
            ////////////////////////////////////////////
            // Calculate and display standard deviation
            ////////////////////////////////////////////
            double sigma = getSigma(sysDate,string,counter,correction);
            scroller.newLine();
            scroller.appendText(ttConst.STD_DEVIATION + Format.toString(sigma,1));

            ///////////////////////////////////////////
            // If sigma exceeds maximum expected, do
            // some snooping.
            ///////////////////////////////////////////
            if(sigma > ttConst.MAX_SIGMA)
                correction = snoopData(sysDate,string,counter,correction,sigma);
        }

        ///////////////////////////////////////
        // Convert correction to a whole number
        // of seconds, and place it in the
        // corresponding textField.
        ///////////////////////////////////////
        temp = new String(((correction < 0)? "-" : "+" ) + Long.toString((long)(Math.abs(correction) + .5)));
        correctionField.setText(temp);

        ///////////////////////////////////////
        // Fire-up the server time Thread.
        ///////////////////////////////////////
        serverTimeThread = new timeThread(serverTimeField,(long)correction,false);
        serverTimeThread.start();

        ////////////////////////////////////
        // Set connected to true, indicating
        // success.
        ////////////////////////////////////
        connected=true;

        scroller.newLine();
        scroller.appendText(ttConst.MEAN + Format.toString(correction,1));
     }

     public double  getCorrection()     { return correction; }
     
     public boolean isConnected()       { return connected; }
     
     public timeThread getTimeThread()  {return serverTimeThread;}
}

