import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.StringReader;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

/** Apache HttpClient is required **/
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.*;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

public class FMEServerWebServicesAPIDemo
{
      private HttpClient            client_ = null;
      // Used to read content from the user
      private BufferedReader        br_            = null;
      // Security token for authentication
      private String                token_         = null;
      
      private String                host_          = null;
      private int                   port_          = 0;

      public FMEServerWebServicesAPIDemo(String host, int port) throws Exception
      {
         client_ = new HttpClient();
         br_ = new BufferedReader(new InputStreamReader(System.in));

         host_ = host;
         port_ = port;
         
         try
         {
            getConnectionInfo();
         }
         catch(IOException e)
         {
            System.err.println(e.getMessage());
         }
      }

      public void getConnectionInfo() throws Exception
      {
         System.out.println("Please enter server connection credentials.");
         System.out.print("UserId:");
         String userid_ = br_.readLine();

         if(!userid_.equals(""))
         {
            System.out.print("Password:");
            String password_ = br_.readLine();
            // We must first generate a security token for authentication purposes
            String fmeUrl = "http://"+host_+":"+port_+"/fmetoken/generate";

            PostMethod method = new PostMethod(fmeUrl);
            method.addParameter("user", userid_);
            method.addParameter("password", password_);
            method.addParameter("expiration", "1");
            method.addParameter("timeunit", "hour");
            
            if (client_.executeMethod(method) == 200) 
            {
               token_ = method.getResponseBodyAsString();
            }
            else 
            {
               throw new Exception("Authentication failed");
            }
         }
      }

      public void execute()
      {
         try
         {
            displayMainMenu();
         }
         catch(NumberFormatException e)
         {
            System.err.println("Invalid input!");
         }
         catch(Exception e)
         {
            e.printStackTrace();
            System.err.println(e.getMessage());
         }
      }

      private void displayMainMenu() throws Exception
      {
         selectRepository();
      }
      
      private void selectRepository() throws Exception {
         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories.xml?token="+token_;

         GetMethod method = new GetMethod(fmeUrl);
         
         client_.executeMethod(method);
         String contents = method.getResponseBodyAsString();
         
         Document doc = parseXML(contents);
         NodeList nl = doc.getElementsByTagName("repository");

         String msg = "Repositories:\n";
         String[] repositories = new String[nl.getLength()];
         
         for (int s = 0; s < nl.getLength(); s++) {
            repositories[s] = ((Element)nl.item(s)).getElementsByTagName("name").item(0).getTextContent();       
            msg += (s+1) + ". " + repositories[s] + "\n";
         }
         
         String repository = getInput(msg + "Please select:");
         
         selectWorkspace(repositories[Integer.parseInt(repository)-1]);
      }
      
      private void selectWorkspace(String repository) throws Exception {
         
         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories/"+repository+".xml?token="+token_;
         GetMethod method = new GetMethod(fmeUrl);
         
         client_.executeMethod(method);
         String contents = method.getResponseBodyAsString();

         Document doc = parseXML(contents);
         NodeList nl = doc.getElementsByTagName("workspace");

         String msg = "Workspaces:\n";
         String[] workspaces = new String[nl.getLength()];
         
         for (int s = 0; s < nl.getLength(); s++) {
            String workspace = ((Element)nl.item(s)).getElementsByTagName("name").item(0).getTextContent(); 
            // do NOT include the workspace extension, otherwise, the next url will become a request to download the file.
            workspaces[s] = workspace.substring(0, workspace.lastIndexOf('.'));
            msg += (s+1) + ". " + workspaces[s] + "\n";
         }
         
         String workspace = getInput(msg + "Please select:");
         
         selectService(repository, workspaces[Integer.parseInt(workspace)-1]);
      }
      
      private void selectService(String repository, String workspace) throws Exception {
         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories/"+repository+"/"+workspace+".xml?token="+token_;
         GetMethod method = new GetMethod(fmeUrl);
         
         client_.executeMethod(method);
         String contents = method.getResponseBodyAsString();

         Document doc = parseXML(contents);
         NodeList nl = doc.getElementsByTagName("service");

         String msg = "Services:\n";
         String[] services = new String[nl.getLength()];

         for (int s = 0; s < nl.getLength(); s++) {
            services[s] = ((Element)nl.item(s)).getElementsByTagName("uri").item(0).getTextContent(); 
            String serviceName = ((Element)nl.item(s)).getElementsByTagName("name").item(0).getTextContent();
            msg += (s+1) + ". " + serviceName + "\n";
         }
         
         String service = getInput(msg + "Please select:");
         
         selectParameters(repository, workspace, services[Integer.parseInt(service)-1]);         
      }
      
      private void selectParameters(String repository, String workspace, String serviceURI) throws Exception {
         
         String fmeUrl = "http://"+host_+":"+port_+ serviceURI+".xml?token="+token_;

         GetMethod method = new GetMethod(fmeUrl);
         
         client_.executeMethod(method);
         String contents = method.getResponseBodyAsString();

         System.out.println(contents);
         Document doc = parseXML(contents);
         
         NodeList nl_url = doc.getElementsByTagName("service");
         NodeList nl_workspace = doc.getElementsByTagName("item");
         String serviceURL = ((Element)nl_url.item(0)).getElementsByTagName("urlPattern").item(0).getTextContent()
                           + "/" + repository + "/" 
                           + ((Element)nl_workspace.item(0)).getElementsByTagName("name").item(0).getTextContent()
                           + "?opt_responseformat=xml&token=" + token_;


         // for the request to run the service
         PostMethod runMethod = new PostMethod(serviceURL);
         
         NodeList nl_parameters = doc.getElementsByTagName("parameter");
         // store parameters
         //HashMap<String, String> parameters = new HashMap<String, String>();
         // get value for each parameter
         for (int s = 0; s < nl_parameters.getLength(); s++) 
         {
               Element el = (Element)nl_parameters.item(s);
               String name = el.getElementsByTagName("name").item(0).getTextContent(); 
               String display = el.getElementsByTagName("description").item(0).getTextContent(); 
               String defaultValue = el.getElementsByTagName("defaultValue").item(0).getTextContent(); 
               String optionsType = el.getElementsByTagName("optionsType").item(0).getTextContent(); 
               
               if (optionsType.equals("SINGLECHOICE_CONFIG")) {
                  // single choice, so choose it
                  NodeList nl_options = el.getElementsByTagName("option");
                  String msg = display + "\n";
                  String[] options = new String[nl_options.getLength()];

                  for (int i = 0; i < nl_options.getLength(); i++) {
                     options[i] = ((Element)nl_options.item(i)).getElementsByTagName("value").item(0).getTextContent(); 
                     msg += (i+1) + ". " + options[i] + "\n";
                  }
                  
                  String selected = getInput(msg + "Please select the number (default = "+defaultValue+"):");
                  if (selected.equals("")) {
                     runMethod.addParameter(name, defaultValue);
                  } else {
                     //parameters.put(name, options[Integer.parseInt(selected)-1]);  
                     runMethod.addParameter(name, options[Integer.parseInt(selected)-1]);
                  }
                  
               } else if (optionsType.equals("MULTICHOICE_CONFIG")) {
                  // prompt the user for each choice
                  NodeList nl_options = el.getElementsByTagName("option");
                  System.out.println(display + "(Multiple options, enter 'Y'/'N' or return for the default value.)");

                  for (int i = 0; i < nl_options.getLength(); i++) {
                     String value = ((Element)nl_options.item(i)).getElementsByTagName("value").item(0).getTextContent(); 
                     String choice = getInput(value + " (default = '" + (value.equals(defaultValue) ? "Y" : "N") + "') ?");
                     
                     // add the choice if the user specified "Y" or the default choice was selected (and was "Yes")
                     if (choice.equals("Y") || choice.equals("y") || 
                           (choice.equals("") && defaultValue.equals(value))) {
                        runMethod.addParameter(name+"[]", value);
                     }
                  }
                  
               } else {
                   // for everything else, default to a text input
                   String value = getInput(display + "(default = '" + defaultValue + "') ");
                   // use default if nothing provided
                   if (value == "") {
                      value = defaultValue;
                   }
                   //parameters.put(name, value);
                   runMethod.addParameter(name, value);
               }
         }
         
         client_.executeMethod(runMethod);
         String contentType = runMethod.getResponseHeader("Content-Type").getValue();
         if (contentType.contains("text/xml") || contentType.contains("text/html") || contentType.contains("text/json")) 
         {
            // print this out to the user
            String runContents = runMethod.getResponseBodyAsString();
            System.out.println(runContents);
         } 
         else 
         {
            // we had best download this file to a location
            String filePath = getInput("Please enter the location you would like to save this file to: ");
            
            FileOutputStream file = new FileOutputStream(filePath);
            PrintStream fileStream = new PrintStream(file);
            
            if (client_.executeMethod(method) != 200) 
            {
               throw new Exception("Getting workspace failed");
            }   
            
            byte[] responseBody = method.getResponseBody();

            // Deal with the response.
            // Use caution: ensure correct character encoding and is not binary data
            fileStream.println(new String(responseBody));
            
            fileStream.close();
            file.close();
         }
      }


      
      private Document parseXML(String contents) throws Exception
      {
         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
         DocumentBuilder db = dbf.newDocumentBuilder();
         InputSource is = new InputSource();
         is.setCharacterStream(new StringReader(contents));
         Document doc = db.parse(is);
         return doc;
      }
  
      public void listParameters() throws Exception {

         String repositoryName = getInput("Please enter the repository name for the workspace to view the parameters of:");
         String workspaceName = getInput("Please enter the name of the workspace to view the parameters of:");
         
         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories/"+repositoryName+"/"+workspaceName+"/parameters.xml?token="+token_;
         
         GetMethod method = new GetMethod(fmeUrl);

         client_.executeMethod(method);
         String contents = method.getResponseBodyAsString();

         Document doc = parseXML(contents);
         NodeList nl = doc.getElementsByTagName("parameter");
         
         for (int s = 0; s < nl.getLength(); s++)
         {
            System.out.println(((Element)nl.item(s)).getElementsByTagName("name").item(0).getTextContent());
         }
      }

      private String getInput(String promptMessage) throws Exception
      {
         System.out.print(promptMessage + "\t");
         return br_.readLine();
      }

      /**
       * This program accepts two parameters. First parameter is the FME Server
       * host. ie. localhost Second parameter is the FME Server port. ie. 7071
       */
      public static void main(String args[]) throws Exception
      {
         String host = null;
         int port = 0;

         // Extract the arguments from the command line
         if(args.length != 2)
         {
            argError();
         }
         else
         {
            host = args[0];
            try
            {
               port = Integer.parseInt(args[1]);
            }
            catch(NumberFormatException nfe)
            {
               argError();
            }
         }

         FMEServerWebServicesAPIDemo demo = new FMEServerWebServicesAPIDemo(host, port);

         while(true)
         {
            demo.execute();
         }
      }

      private static void argError()
      {
         System.err.println("USAGE:\n\tFMEServerWebServicesAPIDemo <host> <port>");
         System.exit(-1);
      }
}