/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * Modified by
 * @author  Data Virtuality GmbH
 * @date    $Date$
 *  
 */
// https://issues.jboss.org/browse/TEIID-2302
package org.teiid.transport;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import org.junit.jupiter.api.Test;
import org.teiid.client.DQP;
import org.teiid.client.security.ILogon;
import org.teiid.client.security.InvalidSessionException;
import org.teiid.client.security.LogonException;
import org.teiid.client.security.LogonResult;
import org.teiid.client.security.SessionToken;
import org.teiid.client.util.ResultsFuture;
import org.teiid.client.util.ResultsReceiver;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.core.crypto.Cryptor;
import org.teiid.core.crypto.NullCryptor;
import org.teiid.core.util.ApplicationInfo;
import org.teiid.core.util.ObjectConverterUtil;
import org.teiid.dqp.internal.process.DQPWorkContext;
import org.teiid.net.CommunicationException;
import org.teiid.net.ConnectionException;
import org.teiid.net.HostInfo;
import org.teiid.net.TeiidURL;
import org.teiid.net.socket.Message;
import org.teiid.net.socket.SocketServerConnection;
import org.teiid.net.socket.SocketServerInstance;
import org.teiid.net.socket.SocketServerInstanceFactory;
import org.teiid.net.socket.SocketServerInstanceImpl;
import org.teiid.net.socket.UrlServerDiscovery;

@SuppressWarnings("nls")
public class TestSocketRemoting {
	
	public interface FakeService {
		
		ResultsFuture<Integer> asynchResult();
		
		String exceptionMethod() throws TeiidProcessingException;
		
		int lobMethod(InputStream is, Reader r) throws IOException;
		
		Reader getReader() throws IOException;
		
	}
	
	static class FakeServiceImpl implements FakeService {

		public ResultsFuture<Integer> asynchResult() {
			ResultsFuture<Integer> result = new ResultsFuture<Integer>();
			result.getResultsReceiver().receiveResults(new Integer(5));
			return result;
		}

		public String exceptionMethod() throws TeiidProcessingException {
			throw new TeiidProcessingException();
		}
		
		@Override
		public int lobMethod(InputStream is, Reader r) throws IOException {
			return ObjectConverterUtil.convertToByteArray(is).length + ObjectConverterUtil.convertToString(r).length();
		}

		@Override
		public Reader getReader() throws IOException {
			return new StringReader("hello world"); //$NON-NLS-1$
		}
		
	}
	
	private static class FakeClientServerInstance extends SocketServerInstanceImpl implements ClientInstance {
		
		ClientServiceRegistryImpl server;
		private ResultsReceiver<Object> listener;

		public FakeClientServerInstance(ClientServiceRegistryImpl server) throws UnknownHostException {
			super(new HostInfo("foo", new InetSocketAddress(InetAddress.getLocalHost(), 1)), 1000);
			this.server = server;
		}
		
		@Override
		public boolean isOpen() {
			return true;
		}
		
		@Override
		public void send(Message message, ResultsReceiver<Object> l,
				Serializable messageKey) throws CommunicationException,
				InterruptedException {
			ServerWorkItem workItem = new ServerWorkItem(this, messageKey, message, server);
			this.listener = l;
			workItem.run();
		}

		@Override
		public void shutdown() {
			
		}

		@Override
		public Cryptor getCryptor() {
			return new NullCryptor();
		}

		public DQPWorkContext getWorkContext() {
			return new DQPWorkContext();
		}

		public void send(Message message, Serializable messageKey) {
			this.listener.receiveResults(message.getContents());
		}

		@Override
 	    public String getServerVersion() {
			return ApplicationInfo.getInstance().getReleaseNumber();
		}
		
	}
	
	/**
	 * No server was supplied, will throw an NPE under the covers
	 */
	@Test
	public void testUnckedException() throws Exception {
		FakeClientServerInstance serverInstance = new FakeClientServerInstance(null);
		try {
			createFakeConnection(serverInstance);
			fail("expected exception"); //$NON-NLS-1$
		} catch (CommunicationException e) {
			assertEquals("TEIID20018 Unable to find a component used authenticate on to Teiid", e.getMessage()); //$NON-NLS-1$
		} catch(NullPointerException npe) {
		}
	}
	
	@Test
	public void testMethodInvocation() throws Exception {
		ClientServiceRegistryImpl csr = new ClientServiceRegistryImpl() {
			@Override
			public ClassLoader getCallerClassloader() {
				return getClass().getClassLoader();
			}
		};
		csr.registerClientService(ILogon.class, new ILogon() {

				public ResultsFuture<?> logoff()
						throws InvalidSessionException {
					ResultsFuture<?> result = new ResultsFuture<Void>();
					result.getResultsReceiver().exceptionOccurred(new TeiidComponentException("some exception")); //$NON-NLS-1$
					return result;
				}

				public LogonResult logon(Properties connectionProperties)
						throws LogonException, TeiidComponentException {
					return new LogonResult();
				}

				// tests asynch where we don't care about the result
				public ResultsFuture<?> ping() throws InvalidSessionException,
						TeiidComponentException {
					return null;
				}
				
				@Override
				public ResultsFuture<?> ping(Collection<String> sessions)
					throws TeiidComponentException, CommunicationException {
					return null;
				}
				
				@Override
				public void assertIdentity(SessionToken sessionId)
					throws InvalidSessionException,
					TeiidComponentException {
				}

				@Override
				public LogonResult neogitiateGssLogin(
						Properties connectionProperties, byte[] serviceToken, boolean createSession)
						throws LogonException {
					return null;
				}
			}, "foo"); //$NON-NLS-1$
		csr.registerClientService(FakeService.class, new FakeServiceImpl(), "foo"); //$NON-NLS-1$
		final FakeClientServerInstance serverInstance = new FakeClientServerInstance(csr);
		SocketServerConnection connection = createFakeConnection(serverInstance);
		ILogon logon = connection.getService(ILogon.class);
		Future<?> result = logon.ping();
		assertNull(result.get(0, TimeUnit.MILLISECONDS));
		result = logon.logoff();
		try {
			result.get(0, TimeUnit.MICROSECONDS);
			fail("exception expected"); //$NON-NLS-1$
		} catch (ExecutionException e) {
			assertTrue(e.getCause() instanceof TeiidComponentException);
		}
		FakeService service = connection.getService(FakeService.class);
		Future<Integer> asynchInteger = service.asynchResult();
		assertEquals(new Integer(5), asynchInteger.get(0, TimeUnit.MILLISECONDS));
		try {
			service.exceptionMethod();
			fail("exception expected"); //$NON-NLS-1$
		} catch (TeiidProcessingException e) {
			
		}
		DQP dqp = connection.getService(DQP.class);
		try {
			ResultsFuture<?> future = dqp.begin();
			future.get();
			fail("exception expected"); //$NON-NLS-1$
		} catch (Exception e) {
			assertTrue(e.getMessage().indexOf("Component not found:") != -1); //$NON-NLS-1$
		}
	}

	private SocketServerConnection createFakeConnection(
			final FakeClientServerInstance serverInstance)
			throws CommunicationException, ConnectionException {
		SocketServerConnection connection = new SocketServerConnection(new SocketServerInstanceFactory() {
		
			@Override
			public SocketServerInstance getServerInstance(HostInfo info)
					throws CommunicationException, IOException {
				return serverInstance;
			}
			
			@Override
			public void connected(SocketServerInstance instance,
					SessionToken session) {
				
			}
			
			@Override
			public void disconnected(SocketServerInstance instance,
					SessionToken session) {
				
			}
			
		}, false, new UrlServerDiscovery(new TeiidURL("0.0.0.0", 1, false)), new Properties()); //$NON-NLS-1$
		return connection;
	}
	
}
