/*<copyright                                                          */
/* notice="cics-rm-source-program"                                    */
/* pids="CA0U"                                                        */
/* years="2014,2015"                                                  */
/* crc="1958969184" >                                                 */
/* 	Licensed Materials - Property of IBM                              */
/* 	"Restricted Materials of IBM"                                     */
/* 	CA0U                                                              */
/* 	(C) Copyright IBM Corp. 2014, 2015                                */
/* 	US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */
/* </copyright>                                                       */
package com.ibm.cics.ucd

import com.ibm.cics.core.comm.ConnectionConfiguration
import com.ibm.cics.core.comm.ConnectionException
import com.ibm.cics.core.comm.SSLProtocolEnablerDecorator
import com.ibm.cics.core.model.Context
import com.ibm.cics.core.model.ScopedContext
import com.ibm.cics.sm.comm.AbstractSystemManagerConnection
import com.ibm.cics.sm.comm.IContext
import com.ibm.cics.sm.comm.IResourceTables
import com.ibm.cics.sm.comm.IScopedContext
import com.ibm.cics.sm.comm.ISystemManagerConnection
import com.ibm.cics.sm.comm.SMConnectionRecord
import com.ibm.cics.sm.comm.SMConnectionResponse
import com.ibm.cics.sm.comm.SystemManagerConnectionException
import com.ibm.cics.sm.comm.cpsm.CPSMConnection
import com.ibm.cics.sm.comm.sm.SMConnection
import com.ibm.cics.sm.comm.sm.WrongCMCIPortException
import com.ibm.cics.sm.comm.sm.internal.CMCIConnectionFinder
import com.ibm.cics.sm.comm.sm.internal.SMCPSMConnection
import com.ibm.cics.ucd.msg.CICSUCDMessage
import com.ibm.cics.ucd.properties.BooleanProperty
import com.ibm.cics.ucd.properties.CommonCICSNameProperty
import com.ibm.cics.ucd.properties.HostProperty
import com.ibm.cics.ucd.properties.PortProperty
import com.ibm.cics.ucd.properties.PropertyException
import com.ibm.cics.ucd.properties.RequiredPropertyException
import com.ibm.cics.ucd.properties.StringProperty
import com.ibm.cics.ucd.ssl.safkeyring.SAFURLStreamHandlerFactory
import com.ibm.cics.ucd.steplabel.CICSUCDStepLabel
import com.ibm.cics.sm.comm.sm.internal.Messages

import static com.ibm.cics.ucd.StepLogHelper.printStepLog 

import java.security.KeyStore
import java.text.MessageFormat
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import javax.net.ssl.KeyManager
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManager
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509KeyManager
import javax.net.ssl.X509TrustManager


public class CMCIConnectionHelper {
	
	// <copyright 
	// notice="cics-rm-source-program" 
	// pids="CA0U" 
	// years="2014,2015" 
	// crc="1958969184" > 
	// 	Licensed Materials - Property of IBM 
	// 	"Restricted Materials of IBM" 
	// 	CA0U 
	// 	(C) Copyright IBM Corp. 2014, 2015 
	// 	US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. 
	// </copyright> 
	
	private def responseCICSplex
	private def responseCICSRGN
	private def detectCICSplexes = ''
	
	private final def password
	private final def username
	private final def port
	private final def hostname
	private final def cicsplex
	private final def cicsscope
	private final boolean ssl
	private final String ksLocation
	private final String ksType
	private final String ksPassword
	private final String tsLocation
	private final String tsType
	private final String tsPassword
	
	static def HOSTNAME_PROPERTY = 'hostname'
	static def PORT_PROPERTY = 'port'
	static def USERNAME_PROPERTY = 'username'
	static def PASSWORD_PROPERTY = 'password'
	static def CICSPLEX_PROPERTY = 'cicsplex'
	static def CICSSCOPE_PROPERTY = 'cicsscope'
	static def SSL_PROPERTY = 'ssl'
	static def KS_LOCATION_PROPERTY = 'ks_location'
	static def KS_TYPE_PROPERTY = 'ks_type'
	static def KS_PASSWORD_PROPERTY = 'ks_password'
	static def TS_LOCATION_PROPERTY = 'ts_location'
	static def TS_TYPE_PROPERTY = 'ts_type'
	static def TS_PASSWORD_PROPERTY = 'ts_password'
	
	static {
		try {
			URL.setURLStreamHandlerFactory(new SAFURLStreamHandlerFactory())
		} catch (Error e) {
			printStepLog(CICSUCDMessage.couldNotSetURLStreamHandlerFactory)
		}
	}
	
	public CMCIConnectionHelper(Properties props) throws PropertyException {
		hostname = new HostProperty(CICSUCDStepLabel.HOSTNAME, true).parse(props[HOSTNAME_PROPERTY]).getValue()
		port = new PortProperty(CICSUCDStepLabel.PORT, true).parse(props[PORT_PROPERTY]).getValue()
		username = new StringProperty(CICSUCDStepLabel.USERNAME, false).parse(props[USERNAME_PROPERTY]).getValue()
		password = new StringProperty(CICSUCDStepLabel.PASSWORD, false).parse(props[PASSWORD_PROPERTY]).getValue()
		cicsplex = new CommonCICSNameProperty(CICSUCDStepLabel.CICSPLEX, false).parse(props[CICSPLEX_PROPERTY]).getValue()
		cicsscope = new CommonCICSNameProperty(CICSUCDStepLabel.CICS_SCOPE, false).parse(props[CICSSCOPE_PROPERTY]).getValue()
		ssl = new BooleanProperty(CICSUCDStepLabel.SSL, false).parse(props[SSL_PROPERTY]).getValue()
		
		String ksLocationFromProperty = new StringProperty("Keystore Location", false).parse(props[KS_LOCATION_PROPERTY]).getValue();
		if (ksLocationFromProperty && ksLocationFromProperty.startsWith("/")) {
			ksLocation = "file://" + ksLocationFromProperty;
		} else {
			ksLocation = ksLocationFromProperty;
		}
		ksType = new StringProperty("Keystore Type", false).parse(props[KS_TYPE_PROPERTY]).getValue()
		ksPassword = new StringProperty("Keystore Password", false).parse(props[KS_PASSWORD_PROPERTY]).getValue()
		
		String tsLocationFromProperty = new StringProperty("Truststore Location", false).parse(props[TS_LOCATION_PROPERTY]).getValue();
		if (tsLocationFromProperty && tsLocationFromProperty.startsWith("/")) {
			tsLocation = "file://" + tsLocationFromProperty;
		} else {
			tsLocation = tsLocationFromProperty
		}
		tsType = new StringProperty("Truststore Type", false).parse(props[TS_TYPE_PROPERTY]).getValue()
		tsPassword = new StringProperty("Truststore Password", false).parse(props[TS_PASSWORD_PROPERTY]).getValue()
	}
	
	def ISystemManagerConnection establishCMCIConnection() throws ConnectionException {
		
		//establish CMCI connection
		ConnectionConfiguration config = new ConnectionConfiguration("conn-conf-id", "conn-conf-name", hostname, port, "cred-id", ssl)
		config.setUserID(username ? username : null)
		config.setPassword(password ? password : null)
		ISystemManagerConnection conn = createConnection()
		conn.setConfiguration(config)
		String msg = null
		try {
			conn.connect()
			msg = MessageFormat.format(CICSUCDMessage.connectionEstablished, hostname + ":" + port)
			printStepLog(msg)
		} catch (ConnectionException e) {
			printStepLog(createConnectionErrorMessage(e))
			throw e
		}
		
		return conn
	}
	
	protected ISystemManagerConnection createConnection() {
		SMConnection conn = new SMConnection()
		configureConnection(conn)
		return conn
	}

	protected ISystemManagerConnection configureConnection(SMConnection conn) {
		if (ssl) {
			SSLContext context = SSLContext.getInstance("TLS")
			KeyStore ks = null
			KeyStore ts = null

			KeyManager keyManager
			if (ksLocation?.trim()) {
				KeyStore store = KeyStore.getInstance(ksType ? ksType : KeyStore.getDefaultType())
				URL url = new URL(ksLocation.trim())
				store.load(url.openConnection().getInputStream(), ksPassword ? ksPassword.toCharArray() : null)
				ks = store

				KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
				keyManagerFactory.init(ks, ksPassword ? ksPassword.toCharArray() : null)
				for (KeyManager factoryKeyManager : keyManagerFactory.getKeyManagers()) {
					if (factoryKeyManager instanceof X509KeyManager) {
						keyManager = (X509KeyManager) factoryKeyManager
						break
					}
				}
			}

			TrustManager trustManager
			if (tsLocation?.trim()) {
				KeyStore store = KeyStore.getInstance(tsType ? tsType : KeyStore.getDefaultType())
				URL url = new URL(tsLocation.trim())
				store.load(url.openConnection().getInputStream(), tsPassword ? tsPassword.toCharArray() : null)
				ts = store

				TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
				trustManagerFactory.init(ts)
				for (TrustManager factoryTrustManager : trustManagerFactory.getTrustManagers()) {
					if (factoryTrustManager instanceof X509TrustManager) {
						trustManager = (X509TrustManager) factoryTrustManager
						break
					}
				}
			}

			KeyManager[] keyManagers = keyManager ? [keyManager] as KeyManager[] : null
			TrustManager[] trustManagers = trustManager ? [trustManager] as TrustManager[] : null
			context.init(keyManagers, trustManagers, null)

			SSLSocketFactory decoratedFactory = new SSLProtocolEnablerDecorator(context.getSocketFactory(), true)
			conn.setSSLSocketFactory(decoratedFactory)
		}
	}
	
	def String createConnectionErrorMessage(ConnectionException e) {
		Throwable cause = e;
		if (e.getCause() != null) {
			cause = e.getCause();
		}
		def msg
		if (cause instanceof UnknownHostException) {
			String unknownHost = cause.getMessage();
			msg = MessageFormat.format(CICSUCDMessage.unknownHost, unknownHost)
		} else {
			msg = MessageFormat.format(CICSUCDMessage.connectionFailedWithError, hostname + ":" + port, cause.getMessage())
		}
		return msg
	}
	
	def isCICSplex(ISystemManagerConnection conn) {
		SMConnectionResponse responseCMAS = null
		
		try{//attempt CICSPlex first, get PLEXNAME
			responseCMAS = conn.getResources(IResourceTables.MANAGER_CMAS,IContext.NULL_CONTEXT)   //SMRecord CMAS
			if (responseCMAS.getRecordCount() > 0) {
				responseCICSplex = conn.getResources(IResourceTables.MANAGER_CICSPLEX,IContext.NULL_CONTEXT)  //SMRecord CICSplex
				if (responseCICSplex.getRecordCount() > 0) {
					return true
				} else {
					throw new Exception('Missing CICSplex record')
				}
			} else {
				throw new Exception('Missing CMAS Record')
			}
		}catch(SystemManagerConnectionException e) {
			//then attempt SMSS, get APPLID
			responseCICSRGN = conn.getResources(IResourceTables.RESOURCE_CICSRGN,IContext.NULL_CONTEXT)//SMRecord CICSRGN
			if (responseCICSRGN.getRecordCount() > 0) {
				return false
			} else {
				throw new Exception('Missing regionRecord')
			}
		}
	}
	
	def IContext getContext(ISystemManagerConnection conn) throws Exception{
		//determine whether is CICSplex
		def isCICSplex = isCICSplex(conn)
		
		//set context
		IContext context = null
		String cicsPlexName = null
		
		if (isCICSplex == false) {//SMSS
			ignoreCICSplexScope()
			context = new ScopedContext(responseCICSRGN.getRecord(0).get('APPLID'), responseCICSRGN.getRecord(0).get('APPLID'))
		} else {//CICSplex
			if(responseCICSplex.getRecordCount() == 1) {	//one single CICSplex
				cicsPlexName = responseCICSplex.getRecord(0).get("PLEXNAME")
				if(noCICSplexSpecified()) {
					printStepLog(MessageFormat.format(CICSUCDMessage.setDefaultCICSplex, CICSUCDStepLabel.CICSPLEX, cicsPlexName))
				} else {
					if( !cicsPlexName.equalsIgnoreCase(cicsplex) )
						throw new Exception(MessageFormat.format(CICSUCDMessage.incorrectCICSplexSuggestion, CICSUCDStepLabel.CICSPLEX, cicsPlexName))
				}
			} else {					//more than one CICSplex
				if(noCICSplexSpecified()) {
					throw new RequiredPropertyException(MessageFormat.format(CICSUCDMessage.missingCICSplex, CICSUCDStepLabel.CICSPLEX))
				} else {
					if(findMatchingCICSplex())
						cicsPlexName = cicsplex
					else {//can not find matching cicsplex
						throw new Exception(MessageFormat.format(CICSUCDMessage.incorrectCICSplexSuggestion, CICSUCDStepLabel.CICSPLEX, detectCICSplexes))
					}
				}
			}
			
			context = new ScopedContext(new Context(cicsPlexName), cicsscope)
			
		}
			
		return context
	}
	
	def findMatchingCICSplex() {
		def countCICSplex = responseCICSplex.getRecordCount()
		for(int i = 0; i < countCICSplex; i++) {
			
			def detectCICSplexName = responseCICSplex.getRecord(i).get('PLEXNAME')
			detectCICSplexes += detectCICSplexName
			if(i < countCICSplex - 1)
				detectCICSplexes += '/'
			
			if(detectCICSplexName.equalsIgnoreCase(cicsplex)) {
				return true
			}
		}
		return false
	}
	
	def noCICSplexSpecified() {
		if(cicsplex == null || cicsplex.isEmpty())
			return true
		else 
			return false
	}
	
	def ignoreCICSplexScope() {
		if(cicsplex && cicsplex.length() > 0) 		//CICSplex is ignored
			printStepLog(MessageFormat.format(CICSUCDMessage.ignoreContextPropertySMSS, CICSUCDStepLabel.CICSPLEX))
		if(cicsscope && cicsscope.length() > 0) 		//Scope is ignored
			printStepLog(MessageFormat.format(CICSUCDMessage.ignoreScopePropertySMSS, CICSUCDStepLabel.CICS_SCOPE))
	}
}
