/* 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.server.csw.provider.local;
import com.esri.gpt.framework.collection.StringSet;
import com.esri.gpt.framework.context.RequestContext;
import com.esri.gpt.framework.util.Val;
import com.esri.gpt.framework.xml.DomUtil;
import com.esri.gpt.framework.xml.XmlIoUtil;
import com.esri.gpt.server.csw.components.CapabilityOptions;
import com.esri.gpt.server.csw.components.CswNamespaces;
import com.esri.gpt.server.csw.components.IResponseGenerator;
import com.esri.gpt.server.csw.components.OperationContext;
import com.esri.gpt.server.csw.components.ServiceProperties;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Generates a CSW Capabilities response.
 */
public class GetCapabilitiesResponse implements IResponseGenerator {
  
  /** class variables ========================================================= */
  
  /** The Logger. */
  private static Logger LOGGER = Logger.getLogger(GetCapabilitiesResponse.class.getName());
  
  /** constructors ============================================================ */
  
  /** Default constructor */
  public GetCapabilitiesResponse() {}
          
  /** methods ================================================================= */
  
  /**
   * Generates the response.
   * @param context the operation context
   * @throws Exception if a processing exception occurs
   */
  public void generateResponse(OperationContext context) throws Exception {
    
    // initialize
    LOGGER.finer("Generatiing csw:Capabilities response...");
    CapabilityOptions cOptions = context.getRequestOptions().getCapabilityOptions();
    ServiceProperties svcProps = context.getServiceProperties();
    
    // TODO these URL's
    String httpContextPath = Val.chkStr(svcProps.getHttpContextPath());
    String cswBaseUrl = Val.chkStr(svcProps.getCswBaseURL());
    String cswSoapUrl = "";
    
    // check for https protocol
    if (httpContextPath.length() > 0) {
      RequestContext reqContext = context.getRequestContext();
      if ((reqContext != null) && (reqContext.getServletRequest() != null) &&
          (reqContext.getServletRequest() instanceof HttpServletRequest)) {
        HttpServletRequest httpReq = (HttpServletRequest)reqContext.getServletRequest();
        StringBuffer requestURL = httpReq.getRequestURL();
        String s = requestURL.toString().toLowerCase();
        if (s.startsWith("https://") && httpContextPath.toLowerCase().startsWith("http:")) {
          httpContextPath = "https"+httpContextPath.substring(4);
        }
      }
    }
    
    // load the resource document
    String loc = cOptions.getCapabilitiesLocation();
    //loc = "gpt/metadata/cswCapabilities-INSPIRE.xml";
    Document dom = DomUtil.makeDomFromResourcePath(loc,true);
    //dom.getDocumentElement().normalize();   
    
    // make an XPath for the CSW name space context
    CswNamespaces ns = CswNamespaces.CSW_202;
    XPath xpath = XPathFactory.newInstance().newXPath();
    xpath.setNamespaceContext(ns.makeNamespaceContext());
    
    // update the OperationsMetadata endpoints
    NodeList nlRefs = (NodeList)xpath.evaluate("//@xlink:href",dom,XPathConstants.NODESET);
    for (int i=0; i<nlRefs.getLength(); i++) {
      Node ndRef = nlRefs.item(i);
      String href = ndRef.getNodeValue();
      Node ndParent = ((Attr)ndRef).getOwnerElement();
      String nsParent = Val.chkStr(ndParent.getNamespaceURI());
      String parentName = ndParent.getLocalName();
      if (nsParent.equals(ns.URI_OWS())) {
        if (parentName.equals("Get") || parentName.equals("Post")) {
          if ((href == null) || !href.startsWith("http")) {
            href = Val.chkStr(href);
            if (href.length() > 0) {
              href = httpContextPath+href;
            } else {
              href = cswBaseUrl;
            }
            if (parentName.equals("Post")) {
              String val = xpath.evaluate("ows:Constraint[@name='PostEncoding']/ows:Value",ndParent);
              if (Val.chkStr(val).equalsIgnoreCase("SOAP")) {
                href = cswSoapUrl;
              }
            }
            ndRef.setNodeValue(href);
          }
        }
      }
    }
    
    // remove sections that were not requested
    StringSet sections = cOptions.getSections();
    if ((sections != null) && (sections.size() > 0)) {
      HashMap<String,String> keep = new HashMap<String,String>();
      for (String section: sections) {
        if (section.equalsIgnoreCase("ServiceIdentification")) {
          keep.put(ns.URI_OWS()+"#ServiceIdentification","");
        } else if (section.equalsIgnoreCase("ServiceProvider")) {
          keep.put(ns.URI_OWS()+"#ServiceProvider","");
        } else if (section.equalsIgnoreCase("OperationsMetadata")) {
          keep.put(ns.URI_OWS()+"#OperationsMetadata","");
        } else if (section.equalsIgnoreCase("Filter_Capabilities")) {
          keep.put(CswNamespaces.URI_OGC+"#Filter_Capabilities","");
        }
      }
      
      // always keep the filter capabilities
      keep.put(CswNamespaces.URI_OGC+"#Filter_Capabilities","");
      
      ArrayList<Node> remove = new ArrayList<Node>();
      Node root = (Node)xpath.evaluate("/csw:Capabilities",dom,XPathConstants.NODE);
      NodeList children = root.getChildNodes();
      for (int i=0;i<children.getLength();i++) {
        Node node = children.item(i);
        String namespace = node.getNamespaceURI();
        String localname = node.getLocalName();
        if (!keep.containsKey(namespace+"#"+localname)) {
          remove.add(node);
        }
      }
      for (Node node: remove) {
        try {
          node.getParentNode().removeChild(node);
        } catch (Exception e) {
          LOGGER.warning("Unable to remove section, reason= "+e.toString());
        }
      }
    }
    
    // check for a supplied language code (INSPIRE)
    String requestedLang = Val.chkStr(cOptions.getLanguageCode());
    String exprLang = "/csw:Capabilities/ows:OperationsMetadata/inspire_ds:ExtendedCapabilities/inspire_common:SupportedLanguages/inspire_common:SupportedLanguage/inspire_common:Language";
    String exprDef = "/csw:Capabilities/ows:OperationsMetadata/inspire_ds:ExtendedCapabilities/inspire_common:SupportedLanguages/inspire_common:DefaultLanguage/inspire_common:Language";
    String exprCur = "/csw:Capabilities/ows:OperationsMetadata/inspire_ds:ExtendedCapabilities/inspire_common:ResponseLanguage/inspire_common:Language";
    NodeList nlLang = (NodeList)xpath.evaluate(exprLang,dom,XPathConstants.NODESET);
    Node ndDefLang = (Node)xpath.evaluate(exprDef,dom,XPathConstants.NODE);
    Node ndCurLang = (Node)xpath.evaluate(exprCur,dom,XPathConstants.NODE);
    if ((ndDefLang != null) && (ndCurLang != null)) {
      String sDefLang = Val.chkStr(ndDefLang.getTextContent());
      String sCurLang = Val.chkStr(ndCurLang.getTextContent());
      String sRespLang = sDefLang;
      if (sRespLang.length() == 0) sRespLang = sCurLang;
      
      if ((requestedLang.length() > 0) && !requestedLang.equalsIgnoreCase(sRespLang)) {
        if ((nlLang!= null) && (nlLang.getLength() > 0)) {
          for (int i=0; i<nlLang.getLength(); i++) {
            Node ndLang = nlLang.item(i);
            String sLang = Val.chkStr(ndLang.getTextContent());
            if (requestedLang.equalsIgnoreCase(sLang)) {
              sRespLang = sLang;
              break;
            }
          }
        }
      }
      if (sRespLang.length() > 0) {
        cOptions.setResponseLanguageCode(sRespLang);
        
        // update the current response language
        if (!sRespLang.equalsIgnoreCase(sCurLang)) {
          ndCurLang.setTextContent(sRespLang);
        }
        
        // remove nodes not associated with the response language
        NodeList nlLangRefs = (NodeList)xpath.evaluate("//@xml:lang",dom,XPathConstants.NODESET);
        ArrayList<Node> remove = new ArrayList<Node>();
        ArrayList<Attr> removeAttr = new ArrayList<Attr>();
        for (int i=0;i<nlLangRefs.getLength();i++) {
          Node node = nlLangRefs.item(i);
          if (node instanceof Attr) {
            String sLang = Val.chkStr(node.getTextContent());
            if (sRespLang.equalsIgnoreCase(sLang)) {
              removeAttr.add((Attr)node);
            } else {
              Node ndToRemode = ((Attr)node).getOwnerElement();
              remove.add(ndToRemode);
            }
          } 
        }
        for (Node node: remove) {
          try {
            node.getParentNode().removeChild(node);
          } catch (Exception e) {
            LOGGER.warning("Unable to remove non-requested language node, reason= "+e.toString());
          }
        }
        for (Attr attr: removeAttr) {
          try {
            attr.getOwnerElement().removeAttributeNode(attr);
          } catch (Exception e) {
            LOGGER.warning("Unable to remove xml:lang sttribute, reason= "+e.toString());
          }
        }
      }
      
    }
             
    // set the response string
    String xml = XmlIoUtil.domToString(dom);
    context.getOperationResponse().setResponseXml(xml);  
  }
  
}
