package usda.weru.soil.arssql;

import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.file.TFileWriter;
import java.awt.EventQueue;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.*;
import java.util.*;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.swing.JOptionPane;
import org.apache.log4j.Logger;
import usda.weru.util.Util;

/**
 * Telnet - connect to a given host and service
 * This does not hold a candle to a real Telnet client, but
 * shows some ideas on how to implement such a thing.
 * @version $Id: CommunicateWithARS.java,v 1.9 2008-05-19 20:49:36 wjr Exp $
 */
public class CommunicateWithARS {

    protected Logger logger;

    protected URL url;
    protected String host;
    protected String protocol;
    protected int portNum;

    protected SAXParse doc;
    protected Thread readThread = null;
    
    protected String[] soapMsgBody;
    protected static final String soapMsgBlanks = "                         \n";
    protected String[] soapMsgHdr;

    // Default query
    public CommunicateWithARS(String server) {
        this (server, "SELECT lkey,areasymbol,areaname FROM legend ORDER BY areasymbol");
    }
    

    // current default server : "https://SDMDataAccess.sc.egov.usda.gov"
    // specified in data file : /db/soil/SDMDataAccess.nrcs.usda.gov.db
    
    public CommunicateWithARS(String server, String query) {
        logger = Logger.getLogger(CommunicateWithARS.class);
        
        if (!server.toLowerCase().startsWith("http")) {
            server = "http://" + server;
        }
        try {
            url = new URL (server);
            
            protocol = url.getProtocol() + "://";
            host = url.getHost();
            if ((portNum = url.getPort()) == -1) {
                portNum = url.getDefaultPort();
            }
//            System.out.println ("CommunicateWithARS: connecting with soil database at:"+protocol+host+":"+portNum);

            initSoapMsgData(query);
            doSoapCall();
        } catch (Exception ex) {
            logger.error("CommunicateWithARS, invlaid server url specified: " +  server, ex);
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    JOptionPane.showMessageDialog(null, "Unable to connect/communicate to Soil Datamart.\n\n"
                            + "Verify your internet connection is working.", "Connection Error", JOptionPane.ERROR_MESSAGE);
                }
            });
        }
    }

    /**
     *
     * @return
     */
    public SAXParse getXMLDoc() {
        try {
            while (readThread.isAlive()) {
                Thread.sleep(10);
            }
        } catch (InterruptedException e) {
            logger.info("getXMLDoc interrupted", e);
        }
        return doc;
    }
    

    private void doSoapCall() throws IOException {
        ArrayList<String> payloadArr;
        
        payloadArr = createPayload ();
        try {
            //Socket s = new Socket(host, portNum);
            SSLSocket s =  (SSLSocket)SSLSocketFactory.getDefault().createSocket(host, portNum);
            
            readThread = new PipeFromServer(s.getInputStream(), System.out, this.soapMsgBody[6]);
            readThread.start();

            // Connect our stdin to the remote
            //			new Pipe(System.in, s.getOutputStream()).start();
            // Connect the remote to our stdout
            new PipeToServer(payloadArr, s.getOutputStream()).start();
        } catch (UnknownHostException uhe) {
            logger.error("Unable to resolve soil datamart host.  Internet access may be down.", uhe);
            throw (uhe);
        }
    }


    protected void initSoapMsgData (String query) {
        
        // defaults
        soapMsgBody = new String [] {
            "SOAPAction: \"http://SDMDataAccess.nrcs.usda.gov/Tabular/SDMTabularService.asmx/RunQuery\"",
            "",
            "<?xml version=\"1.0\" encoding=\"utf-8\"?>",
            "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
                + "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "
                + "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">",
            "  <soap:Body>",
            "    <RunQuery xmlns=\"http://SDMDataAccess.nrcs.usda.gov/Tabular/SDMTabularService.asmx\">",
            "      <Query>SELECT lkey,areasymbol,areaname FROM legend ORDER BY areasymbol</Query>",
            //"      <Query>SELECT mukey,musym,muname FROM mapunit WHERE lkey = '11231' ORDER BY muname</Query>",
            "    </RunQuery>",
            "  </soap:Body>",
            "</soap:Envelope>",
            //                  "                                                                       \n";
            ""
        };
        
        // updates / specifics
        // Presently (11/9/2016), looks like the SOAP call on the Soil Data Mart side has these values
        //   hardcoded: even though the server is now residesat and is connected to at a new address, 
        //   these parameters need to match with the old address value.
        // Might need to be updated eventually?
        //soapMsgBody[0] = "SOAPAction: \"" + protocol + host + "/Tabular/SDMTabularService.asmx/RunQuery\"";
        //soapMsgBody[5] = "    <RunQuery xmlns=\"" + protocol + host + "/Tabular/SDMTabularService.asmx\">";
       soapMsgBody[6] = "      <Query>" + query + "</Query>";
        
        // defaults
        soapMsgHdr = new String [] {
            "POST /Tabular/SDMTabularService.asmx HTTP/1.1",
            "Host: SDMDataAccess.nrcs.usda.gov",
            "Content-Type: text/xml; charset=utf-8",
            "Content-Length: "
        };
        
        // updates / specifics
        soapMsgHdr[1] = "Host: " + host;
    }
    

    private ArrayList<String> createPayload () {
        ArrayList<String> payLoad = new ArrayList<String>();
        int len = 0;
        
        for (String body1 : soapMsgBody) {
            len += body1.length() + 1;
        }
        soapMsgHdr[3] = "Content-Length: " + String.valueOf(len);
        payLoad.addAll(Arrays.asList(soapMsgHdr));
        payLoad.addAll(Arrays.asList(soapMsgBody));
        for (int idx = 0; idx < 3; idx++) {
            payLoad.add(soapMsgBlanks);
        }
        return payLoad;
    }
    
    
    /**
     * This class handles one half of a full-duplex connection.
     * Line-at-a-time mode.
     */
    class PipeFromServer extends Thread {

        BufferedReader bis;
        String tableName;

        /**
         * Construct a Pipe to read from is and write to os
         */
        PipeFromServer(InputStream is, OutputStream os, String qStr) {
            this.bis = new BufferedReader(new InputStreamReader(is));
            this.tableName = qStr;
            tableName = tableName.substring(tableName.indexOf(" FROM ") + 6).trim();
            int a = tableName.indexOf(" ");
            a = a < 0 ? Integer.MAX_VALUE : a;
            int b = tableName.indexOf("</");
            b = b < 0 ? Integer.MAX_VALUE : b;
            tableName = tableName.substring(0, Math.min(a, b)).trim();
            //System.out.println("tableName " + tableName);
        }

        /**
         * Do the reading and writing.
         */
        @Override
        public void run() {
            String line;
            String filNam = "tmpsoil/soil.xml";

            try {
                TFile tmpf;
                tmpf = new TFile(TFile.createTempFile(tableName, ".xml"));
                tmpf.deleteOnExit();
                filNam = tmpf.getAbsolutePath();
            } catch (IOException ioe) {
                logger.error("can't create temp file", ioe);
            }

            try {
                long length = 0;
                while (true) {
                    line = bis.readLine();
                    if (line == null) {
                    } else if (line.trim().toLowerCase().startsWith("content-length")) {
                        String[] parts = line.split(":");
                        length = Long.parseLong(parts[1].trim());
                    } else if (line.trim().length() == 0) {
                        try (PrintWriter outf = new PrintWriter(new BufferedWriter(new TFileWriter(new TFile(filNam))))) {
                            for (int idx = 0; idx < length; idx++) {
                                int tchr = bis.read();
                                outf.print((char) tchr);
                            }
                            outf.println();
                        }
                        doc = SAXParse.parse(filNam);

                        return;
                    }

                }
            } catch (IOException | NumberFormatException e) {
                JOptionPane.showMessageDialog(null, e.getLocalizedMessage(), "Connection Error:\n"
                        + Util.fileContents(new TFile(filNam)), JOptionPane.ERROR_MESSAGE);
                logger.error(e);
            }
        }
    }


    /**
     * This class handles one half of a full-duplex connection.
     * Line-at-a-time mode.
     */
    static class PipeToServer extends Thread {

        PrintStream os;
        ArrayList<String> payloadArr;

        /** Construct a Pipe to read from is and write to os */
        PipeToServer (ArrayList<String> payloadArr, OutputStream os) {
            this.payloadArr = payloadArr;
            this.os = new PrintStream(os);
        }

        /** Do the reading and writing. */
        @Override
        public void run() {
            String line;
            for (Iterator<String> cmdIterator = payloadArr.iterator(); cmdIterator.hasNext();) {
                line = cmdIterator.next();
                if (line == null) {
                    return;
                }
                os.print(line + "\r\n");
                os.flush();
            }
        }
    }

    /**
     *
     * @param argv
     * @throws IOException
     */
    public static void main(String[] argv) throws IOException {
        new CommunicateWithARS(argv[0]);
    }
}
