/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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 org.apache.hadoop.gateway.service.admin;

import org.apache.hadoop.gateway.services.GatewayServices;
import org.apache.hadoop.gateway.config.GatewayConfig;
import org.apache.hadoop.gateway.services.topology.TopologyService;
import org.apache.hadoop.gateway.topology.Topology;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static javax.ws.rs.core.MediaType.APPLICATION_XML;
import static javax.ws.rs.core.Response.ok;

@Path("/api/v1")
public class TopologiesResource {
  @Context
  private HttpServletRequest request;

  @GET
  @Produces({APPLICATION_JSON, APPLICATION_XML})
  @Path("topologies/{id}")
  public Topology getTopology(@PathParam("id") String id) {
    GatewayServices services = (GatewayServices) request.getServletContext()
        .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
    GatewayConfig config = (GatewayConfig) request.getServletContext().getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);

    TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);

    for (Topology t : ts.getTopologies()) {
      if(t.getName().equals(id)) {
        try {
          t.setUri(new URI( buildURI(t, config, request) ));
        } catch (URISyntaxException se) {
          t.setUri(null);
        }
        return t;
      }
    }
    return null;
  }

  @GET
  @Produces({APPLICATION_JSON, APPLICATION_XML})
  @Path("topologies")
  public SimpleTopologyWrapper getTopologies() {
    GatewayServices services = (GatewayServices) request.getServletContext()
        .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);


    TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);

    ArrayList<SimpleTopology> st = new ArrayList<SimpleTopology>();
    GatewayConfig conf = (GatewayConfig) request.getServletContext().getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);

    for (Topology t : ts.getTopologies()) {
      st.add(getSimpleTopology(t, conf));
    }

    Collections.sort(st, new TopologyComparator());
    SimpleTopologyWrapper stw = new SimpleTopologyWrapper();

    for(SimpleTopology t : st){
      stw.topologies.add(t);
    }

    return stw;

  }

  @PUT
  @Consumes({APPLICATION_JSON, APPLICATION_XML})
  @Path("topologies/{id}")
  public Topology uploadTopology(@PathParam("id") String id, Topology t) {

    GatewayServices gs = (GatewayServices) request.getServletContext()
        .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);

    t.setName(id);
    TopologyService ts = gs.getService(GatewayServices.TOPOLOGY_SERVICE);

    ts.deployTopology(t);

    return getTopology(id);
  }

  @DELETE
  @Produces(APPLICATION_JSON)
  @Path("topologies/{id}")
  public Response deleteTopology(@PathParam("id") String id) {
    boolean deleted = false;
    if(!id.equals("admin")) {
      GatewayServices services = (GatewayServices) request.getServletContext()
          .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);

      TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);

      for (Topology t : ts.getTopologies()) {
        if(t.getName().equals(id)) {
          ts.deleteTopology(t);
          deleted = true;
        }
      }
    }else{
      deleted = false;
    }
    return ok().entity("{ \"deleted\" : " + deleted + " }").build();
  }


  private class TopologyComparator implements Comparator<SimpleTopology> {
    @Override
    public int compare(SimpleTopology t1, SimpleTopology t2) {
      return t1.getName().compareTo(t2.getName());
    }
  }

   String buildURI(Topology topology, GatewayConfig config, HttpServletRequest req){
    String uri = buildXForwardBaseURL(req);

//    Strip extra context
    uri = uri.replace(req.getContextPath(), "");

//    Add the gateway path
    String gatewayPath;
    if(config.getGatewayPath() != null){
      gatewayPath = config.getGatewayPath();
    }else{
      gatewayPath = "gateway";
    }
    uri += "/" + gatewayPath;

    uri += "/" + topology.getName();
    return uri;
  }

   String buildHref(Topology t, HttpServletRequest req) {
    String href = buildXForwardBaseURL(req);
//    Make sure that the pathInfo doesn't have any '/' chars at the end.
    String pathInfo = req.getPathInfo();
    if(pathInfo.endsWith("/")) {
      while(pathInfo.endsWith("/")) {
        pathInfo = pathInfo.substring(0, pathInfo.length() - 1);
      }
    }

    href += pathInfo + "/" + t.getName();
    return href;
  }

  private SimpleTopology getSimpleTopology(Topology t, GatewayConfig config) {
    String uri = buildURI(t, config, request);
    String href = buildHref(t, request);
    return new SimpleTopology(t, uri, href);
  }

  private String buildXForwardBaseURL(HttpServletRequest req){
    final String X_Forwarded = "X-Forwarded-";
    final String X_Forwarded_Context = X_Forwarded + "Context";
    final String X_Forwarded_Proto = X_Forwarded + "Proto";
    final String X_Forwarded_Host = X_Forwarded + "Host";
    final String X_Forwarded_Port = X_Forwarded + "Port";
    final String X_Forwarded_Server = X_Forwarded + "Server";

    String baseURL = "";

//    Get Protocol
    if(req.getHeader(X_Forwarded_Proto) != null){
      baseURL += req.getHeader(X_Forwarded_Proto) + "://";
    } else {
      baseURL += req.getProtocol() + "://";
    }

//    Handle Server/Host and Port Here
    if (req.getHeader(X_Forwarded_Host) != null && req.getHeader(X_Forwarded_Port) != null){
//        Double check to see if host has port
      if(req.getHeader(X_Forwarded_Host).contains(req.getHeader(X_Forwarded_Port))){
        baseURL += req.getHeader(X_Forwarded_Host);
      } else {
//        If there's no port, add the host and port together;
        baseURL += req.getHeader(X_Forwarded_Host) + ":" + req.getHeader(X_Forwarded_Port);
      }
    } else if(req.getHeader(X_Forwarded_Server) != null && req.getHeader(X_Forwarded_Port) != null){
//      Tack on the server and port if they're available. Try host if server not available
      baseURL += req.getHeader(X_Forwarded_Server) + ":" + req.getHeader(X_Forwarded_Port);
    } else if(req.getHeader(X_Forwarded_Port) != null) {
//      if we at least have a port, we can use it.
      baseURL += req.getServerName() + ":" + req.getHeader(X_Forwarded_Port);
    } else {
//      Resort to request members
      baseURL += req.getServerName() + ":" + req.getLocalPort();
    }

//    Handle Server context
    if( req.getHeader(X_Forwarded_Context) != null ) {
      baseURL += req.getHeader( X_Forwarded_Context );
    } else {
      baseURL += req.getContextPath();
    }

    return baseURL;
  }

//  Uses member request variable
  private String buildXForwardBaseURL(){
    return buildXForwardBaseURL(request);
  }

  @XmlAccessorType(XmlAccessType.NONE)
  public static class SimpleTopology {

    @XmlElement
    private String name;
    @XmlElement
    private String timestamp;
    @XmlElement
    private String uri;
    @XmlElement
    private String href;

    public SimpleTopology() {}

    public SimpleTopology(Topology t, String uri, String href) {
      this.name = t.getName();
      this.timestamp = Long.toString(t.getTimestamp());
      this.uri = uri;
      this.href = href;
    }

    public String getName() {
      return name;
    }

    public void setName(String n) {
      name = n;
    }

    public String getTimestamp() {
      return timestamp;
    }

    public void setTimestamp(String timestamp) {
      this.timestamp = timestamp;
    }

    public String getUri() {
      return uri;
    }

    public void setUri(String uri) {
      this.uri = uri;
    }

    public String getHref() {
      return href;
    }

    public void setHref(String href) {
      this.href = href;
    }
  }

  @XmlAccessorType(XmlAccessType.FIELD)
  public static class SimpleTopologyWrapper{

    @XmlElement(name="topology")
    @XmlElementWrapper(name="topologies")
    private List<SimpleTopology> topologies = new ArrayList<SimpleTopology>();

    public List<SimpleTopology> getTopologies(){
      return topologies;
    }

    public void setTopologies(List<SimpleTopology> ts){
      this.topologies = ts;
    }

  }




}

