/* See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Esri Inc. licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.esri.gpt.catalog.publication;
import com.esri.gpt.catalog.arcgis.metadata.AGSProcessor;
import com.esri.gpt.catalog.arcgis.metadata.AGSProcessorConfig;
import com.esri.gpt.framework.collection.StringAttributeMap;
import com.esri.gpt.framework.http.CredentialProvider;
import com.esri.gpt.framework.http.HttpClientRequest;
import com.esri.gpt.framework.security.credentials.UsernamePasswordCredentials;
import com.esri.gpt.framework.util.Val;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * A factory for instantiating suitable resource processors.
 */
public class ProcessorFactory {
  
  /** constructors ============================================================ */
  
  /** Default constructor. */
  public ProcessorFactory() {}

  /**
   * Interrogates a resource URL to determine an appropriate resource processor.
   * @param context the resource processing context
   * @param resourceUrl the resource URL to be interrogated
   * @return a processor capable of handling the endpoint (can be null)
   * @throws IOException if a communication exception occurs
   */
  public ResourceProcessor interrogate(ProcessingContext context, String resourceUrl)
    throws IOException {
    return interrogate(context, resourceUrl, null);
  }

  /**
   * Interrogates a resource URL to determine an appropriate resource processor.
   * @param context the resource processing context
   * @param resourceUrl the resource URL to be interrogated
   * @param credentials credentials
   * @return a processor capable of handling the endpoint (can be null)
   * @throws IOException if a communication exception occurs
   */
  public ResourceProcessor interrogate(ProcessingContext context, String resourceUrl, UsernamePasswordCredentials credentials)
    throws IOException {
    
    // it's xml or ags json or ags soap
    
    // determine the base URL (sans query string)
    resourceUrl = Val.chkStr(resourceUrl);
    String baseUrl = resourceUrl;
    if (baseUrl.indexOf("?") != -1) {
      baseUrl = baseUrl.substring(0,baseUrl.indexOf("?"));
    }
    if (baseUrl.endsWith("/")) {
      baseUrl = baseUrl.substring(0,baseUrl.length() - 1);
    }
    
    // determine the protocol
    boolean isHttpProtocol = false;
    boolean isFtpProtocol = false;
    boolean isFileProtocol = false;
    boolean isFile = false;
    boolean isDirectory = false;
    boolean wasMalformed = false;
    boolean isWMS = false;
    URL url = null;
    File file = null;
    try {
      url = new URL(resourceUrl);
      String protocol = Val.chkStr(url.getProtocol()).toLowerCase();
      isHttpProtocol = protocol.equals("http") || protocol.equals("https");
      isFtpProtocol = protocol.equals("ftp") || protocol.equals("ftps");
      isFileProtocol = protocol.equals("file");
      isWMS = url.getQuery()!=null? url.getQuery().toLowerCase().contains("service=wms") || url.getQuery().toLowerCase().contains("service=wmts"): false;
    } catch (MalformedURLException e) {
      wasMalformed = true;
    }
    
    // check to see if this is a reference to a server side file or directory
    // TODO: should we allow this??
    if (isFileProtocol || wasMalformed) {
      String filePath = resourceUrl;
      if (isFileProtocol) {
        filePath = url.getPath();
      }
      try {
        file = new File(filePath);
        if (file.exists()) {
          isFile = file.isFile();
          isDirectory = file.isDirectory();
        }
      } catch (SecurityException se) {
        // read only
      }
    }
    
    if (isDirectory) {
      // no applicable processor
    } else if (isWMS) {
      WMSProcessor wmsp = new WMSProcessor(context, resourceUrl);
      return wmsp;
    } else if (isFile) {
      SingleXmlProcessor xp = new SingleXmlProcessor(context,file);
      return xp;
    } else if (isFtpProtocol) {
      // no applicable processor
    } else if (isHttpProtocol) {
      return this.interrogateHttpEndpoint(context,url,credentials);
    } else {
      // no applicable processor
    }
    
    return null;
  }
  
  /**
   * Interrogates an HTTP endpoint.
   * @param context the resource processing context
   * @param url the URL to be interrogated
   * @param credentials credentials
   * @return a processor capable of handling the endpoint (can be null)
   * @throws IOException if a communication exception occurs
   */
  private ResourceProcessor interrogateHttpEndpoint(ProcessingContext context, URL url, UsernamePasswordCredentials credentials)
    throws IOException {
    
    // read the character response from the endpoint
    String fullUrl = url.toExternalForm();
    HttpClientRequest httpClient = context.getHttpClient();
    httpClient.setUrl(fullUrl);
    httpClient.setCredentialProvider(
      credentials==null || credentials.getUsername().length()==0 || credentials.getPassword().length()==0?
        null:
        new CredentialProvider(credentials.getUsername(),credentials.getPassword())
    );
    String response = Val.chkStr(httpClient.readResponseAsCharacters());
    
    if (response.length() > 0) {
      
      // first try ArcGIS Server
      boolean interrofgateAGS = true;
      StringAttributeMap params = context.getRequestContext().getCatalogConfiguration().getParameters();
      if (Val.chkStr(params.getValue("AGSProcessor.interrogation.enabled")).equalsIgnoreCase("false")) {
        interrofgateAGS = false;
      } else if (!AGSProcessorConfig.isAvailable()) {
        interrofgateAGS = false;
      }
      if (interrofgateAGS) {
        AGSProcessor ags = new AGSProcessor(context);
        ags.setCredentials(credentials);
        try {
          if (ags.interrogate(url,response)) {
            return ags;
          }
        } catch (IOException ioe) {
          if (ags.getTarget().getWasRecognized()) {
            throw ioe;
          }
        }
      }

    } 
    
    // default to s single xml processor
    SingleXmlProcessor xp = new SingleXmlProcessor(context,url.toExternalForm(),response);
    return xp;
  }
  
}
