/*
 * ******************************************************
 * Copyright VMware, Inc. 2010-2023.  All Rights Reserved.
 * ******************************************************
 *
 * DISCLAIMER. THIS PROGRAM IS PROVIDED TO YOU "AS IS" WITHOUT
 * WARRANTIES OR CONDITIONS # OF ANY KIND, WHETHER ORAL OR WRITTEN,
 * EXPRESS OR IMPLIED. THE AUTHOR SPECIFICALLY # DISCLAIMS ANY IMPLIED
 * WARRANTIES OR CONDITIONS OF MERCHANTABILITY, SATISFACTORY # QUALITY,
 * NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE.
 */

package com.vmware.vsan.connection;

import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import jakarta.xml.ws.BindingProvider;
import jakarta.xml.ws.handler.MessageContext;

import org.w3c.dom.Document;

import com.vmware.connection.ConnectionException;
import com.vmware.connection.ConnectionMalformedUrlException;
import com.vmware.vsan.sdk.InvalidLocaleFaultMsg;
import com.vmware.vsan.sdk.InvalidLoginFaultMsg;
import com.vmware.vsan.sdk.ManagedObjectReference;
import com.vmware.vsan.sdk.RuntimeFaultFaultMsg;
import com.vmware.vsan.sdk.ServiceContent;
import com.vmware.vsan.sdk.UserSession;
import com.vmware.vsan.sdk.VsanhealthService;
import com.vmware.vsan.sdk.VsanhealthVimPortType;


/**
 * This simple object shows how to set up a vSphere connection with vSAN SDK.
 *
 * It is intended as a utility class for use by Samples that will need to access
 * vSphere APIs with vSAN SDK. This class is different from the class
 * com.vmware.connectin.BasicConnection in vSphere SDK which is used to
 * set up a connection to vSphere service using standard vSphere SDK,
 * though they both can be used to call vSphere APIs. The difference is
 * that, this com.vmware.vsan.connection.VimConnection is associated with
 * vSAN SDK types in addition to standard vSphere SDK types. So this
 * connection will be useful to call some vSphere APIs (for example,
 * ConfigureHCI API on a cluster) that may take input parameters that
 * spans both vSphere SDK types and vSAN SDK types.
 *
 * For most vSphere APIs that do not requires vSAN API types as input
 * or output, it is okay to use either connection:
 * com.vmware.connectin.BasicConnection or
 * com.vmware.vsan.connection.VimConnection.
 */
public class VimConnection {
   private VsanhealthService vimService;
   private VsanhealthVimPortType vimPort;
   private ServiceContent serviceContent;
   private UserSession userSession;
   private ManagedObjectReference svcInstRef;

   private URL url;
   private String username;
   private String password = "";
   @SuppressWarnings("rawtypes")
   private Map headers;

   public VimConnection() {}
   public VimConnection(VsanHealthConnection connection) {
      // Copy the connection configurations from a vSAN service connection.
      setUrl(connection.getUrl());
      setUsername(connection.getUsername());
      setPassword(connection.getPassword());
   }

   public void setUrl(String url) {
      try {
         this.url = new URL(url);
      } catch (MalformedURLException e) {
         throw new ConnectionMalformedUrlException(
               "malformed URL argument: '" + url + "'", e);
      }
   }

   public String getUrl() {
      return url.toString();
   }

   public String getHost() {
      return url.getHost();
   }

   public Integer getPort() {
      int port = url.getPort();
      return port;
   }

   public void setUsername(String username) {
      this.username = username;
   }

   public String getUsername() {
      return username;
   }

   public void setPassword(String password) {
      this.password = password;
   }

   public String getPassword() {
      return this.password;
   }

   public VsanhealthService getVimService() {
      return vimService;
   }

   public VsanhealthVimPortType getVimPort() {
      return vimPort;
   }

   public ServiceContent getServiceContent() {
      return serviceContent;
   }

   public UserSession getUserSession() {
      return userSession;
   }

   public String getServiceInstanceName() {
      return "ServiceInstance";
   }

   @SuppressWarnings("rawtypes")
      public Map getHeaders() {
      return headers;
   }

   public ManagedObjectReference getServiceInstanceReference() {
      if (svcInstRef == null) {
         ManagedObjectReference ref = new ManagedObjectReference();
         ref.setType(this.getServiceInstanceName());
         ref.setValue(this.getServiceInstanceName());
         svcInstRef = ref;
      }
      return svcInstRef;
   }

   public VimConnection connect() {
      if (!isConnected()) {
         try {
            _connect();
         } catch (Exception e) {
            Throwable cause = (e.getCause() != null)?e.getCause():e;
            throw new VimConnectionException(
                  "failed to connect: " + e.getMessage() + " : "
                        + cause.getMessage(),
                  cause);
         }
      }
      return this;
   }

   @SuppressWarnings("rawtypes")
   private void _connect() throws RuntimeFaultFaultMsg,
                                  InvalidLocaleFaultMsg,
                                  InvalidLoginFaultMsg {
      System.out.println("Connecting to vSphere Service...");
      System.out.println("===========================================");
      vimService = new VsanhealthService();
      vimPort = (VsanhealthVimPortType)vimService.getVsanhealthVimPort();
      Map<String, Object> ctxt =
            ((BindingProvider) vimPort).getRequestContext();

      ctxt.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, url.toString());
      ctxt.put(BindingProvider.SESSION_MAINTAIN_PROPERTY, true);

      serviceContent = vimPort.retrieveServiceContent(
            this.getServiceInstanceReference());

      userSession = vimPort.login(
            serviceContent.getSessionManager(),
            username,
            password,
            null);

      headers = (Map)((BindingProvider) vimPort).getResponseContext().get(
            MessageContext.HTTP_RESPONSE_HEADERS);

      System.out.println("vSphere service connection established...");
      System.out.println("============================================\n");
   }

   public boolean isConnected() {
      if (userSession == null) {
         return false;
      }
      long startTime = userSession.getLastActiveTime().
            toGregorianCalendar().getTime().getTime();

      // 30 minutes in milliseconds =
      //       30 minutes * 60 seconds * 1000 milliseconds
      return new Date().getTime() < startTime + 30 * 60 * 1000;
   }

   public VimConnection disconnect() {
      if (this.isConnected()) {
         try {
            vimPort.logout(serviceContent.getSessionManager());
         } catch (Exception e) {
            Throwable cause = e.getCause();
            throw new VimConnectionException(
                  "failed to disconnect properly: " + e.getMessage() + " : "
                        + cause.getMessage(),
                  cause
            );
         } finally {
            // A connection is very memory intensive, garbage collection here.
            userSession = null;
            serviceContent = null;
            vimPort = null;
            vimService = null;
         }
      }
      return this;
   }

   public URL getURL() {
      return this.url;
   }

   /*
    * This is a utility function to get the latest version supported by remote
    * vSphere service under "urn:vim25" namespace.
    */
   public static String getVimVmodlVersion(String hostAddr) {
      String USER_AGENT = "Mozilla/5.0";
      String vimNsXmlFile = "https://" + hostAddr +
                            "/sdk/vimServiceVersions.xml";
      HttpURLConnection connection = null;
      String defaultVersion = "6.5";

      try {
         URL url = new URL(vimNsXmlFile);
         connection = (HttpURLConnection) url.openConnection();
         connection.setRequestMethod("GET");
         connection.setRequestProperty("User-Agent", USER_AGENT);
         connection.setRequestProperty("Content-Language", "en-US");
         int responseCode = connection.getResponseCode();
         if (responseCode == 200) {
            DocumentBuilderFactory factory =
                  DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.parse(connection.getInputStream());
            doc.getDocumentElement().normalize();
            String apiVersion =
                  doc.getElementsByTagName("version").item(0).getTextContent();
            return apiVersion;
         }
         return defaultVersion;
      } catch (Exception ex) {
         System.out.println("Failed to process vim service XML file: " + ex);
         return defaultVersion;
      }
   }


   private class VimConnectionException extends ConnectionException {
      private static final long serialVersionUID = 1L;
      public VimConnectionException(String s, Throwable t) {
         super(s, t);
      }
   }
}
