/*
 * JBoss, Home of Professional Open Source.
 *
 * Copyright 2012 Red Hat, Inc. and/or its affiliates, and individual
 * contributors as indicated by the @author tags.
 *
 * Licensed 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.xnio;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.xnio.AssertReadWrite.assertReadMessage;
import static org.xnio.AssertReadWrite.assertWrittenMessage;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.NonWritableChannelException;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.Permission;
import java.security.PrivilegedAction;
import java.util.ServiceConfigurationError;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;

import org.junit.Test;
import org.xnio.channels.Channels;
import org.xnio.mock.ConnectedStreamChannelMock;
import org.xnio.ssl.XnioSsl;

/**
 * Test for {@link Xnio}.
 * 
 * @author <a href="mailto:flavia.rainone@jboss.com">Flavia Rainone</a>
 *
 */
public class XnioTestCase {

    static {
        String securityPolicyFile = XnioTestCase.class.getClassLoader().getResource("security.policy").getFile();
        AccessController.doPrivileged(new SetSecurityPolicyAction(securityPolicyFile));
    }

    private static final String DEFAULT_KEY_STORE = "keystore.jks";
    private static final String DEFAULT_KEY_STORE_PASSWORD = "apiTest";

    @Test
    public void allowBlocking() {
        assertTrue(Xnio.isBlockingAllowed());
        Xnio.checkBlockingAllowed();
        Xnio.allowBlocking(true);
        assertTrue(Xnio.isBlockingAllowed());
        Xnio.checkBlockingAllowed();

        Xnio.allowBlocking(false);
        assertFalse(Xnio.isBlockingAllowed());
        IllegalStateException expected = null;
        try {
            Xnio.checkBlockingAllowed();
        } catch (IllegalStateException e) {
            expected = e;
        }
        assertNotNull(expected);

        Xnio.allowBlocking(false);
        assertFalse(Xnio.isBlockingAllowed());
        expected = null;
        try {
            Xnio.checkBlockingAllowed();
        } catch (IllegalStateException e) {
            expected = e;
        }
        assertNotNull(expected);

        Xnio.allowBlocking(true);
        assertTrue(Xnio.isBlockingAllowed());
        Xnio.checkBlockingAllowed();
        Xnio.allowBlocking(true);
        assertTrue(Xnio.isBlockingAllowed());
        Xnio.checkBlockingAllowed();
    }

    @Test
    public void allowBlockingWithSecurity() {
        final SecurityManager securityManager = new SecurityManager();
        System.setSecurityManager(securityManager);
        assertTrue(Xnio.isBlockingAllowed());
        Xnio.checkBlockingAllowed();

        AccessControlException expected = null;
        try {
            Xnio.allowBlocking(true);
        } catch (AccessControlException e) {
            expected = e;
        }
        assertNotNull(expected);

        expected = null;
        try {
            Xnio.allowBlocking(false);
        } catch (AccessControlException e) {
            expected = e;
        }
        assertNotNull(expected);
        AccessController.doPrivileged(new GrantAllPermissionsAction());
        allowBlocking();
        AccessController.doPrivileged(new ResetSecurityManagerAction());
    }

    @Test
    public void retrieveInstance() {
        final Xnio xnio = Xnio.getInstance();
        assertNotNull(xnio);
        assertSame(xnio, Xnio.getInstance(getClass().getClassLoader()));
        assertSame(xnio, Xnio.getInstance("xnio-mock"));

        IllegalArgumentException expectedException = null;
        try {
            Xnio.getInstance("xnio");
        } catch (IllegalArgumentException e) {
            expectedException = e;
        }
        assertNotNull(expectedException);

        assertNotNull(xnio.toString());
        assertEquals("xnio-mock", xnio.getName());
    }

    @Test
    public void retrieveSslProvider() throws GeneralSecurityException {
        final Xnio xnio = Xnio.getInstance();
        final OptionMap optionMap = OptionMap.create(Options.SSL_CLIENT_AUTH_MODE, SslClientAuthMode.REQUIRED, Options.SSL_STARTTLS, true);
        final XnioSsl sslProvider = xnio.getSslProvider(optionMap);
        assertNotNull(sslProvider);
    }

    @Test
    public void retrieveSslProviderWithTrustAndKeyManagers() throws GeneralSecurityException, FileNotFoundException, IOException {
        final Xnio xnio = Xnio.getInstance();
        final OptionMap optionMap = OptionMap.create(Options.SSL_CLIENT_AUTH_MODE, SslClientAuthMode.REQUIRED, Options.SSL_STARTTLS, true);

        final KeyStore keyStore = KeyStore.getInstance("JKS");
        final String keyStorePath = XnioTestCase.class.getClassLoader().getResource(DEFAULT_KEY_STORE).getFile();
        keyStore.load(new FileInputStream(keyStorePath),  DEFAULT_KEY_STORE_PASSWORD.toCharArray());
        final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(keyStore, DEFAULT_KEY_STORE_PASSWORD.toCharArray());
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);

        final XnioSsl sslProvider = xnio.getSslProvider(keyManagerFactory.getKeyManagers(),
                trustManagerFactory.getTrustManagers(), optionMap);
        assertNotNull(sslProvider);
    }

    private File createTempFile() throws IOException {
        final File file = File.createTempFile("test", ".txt");
        file.deleteOnExit();
        return file;
    }

    private void checkReadOnlyFileChannel(FileChannel fileChannel) throws IOException {
        try {
            assertNotNull(fileChannel);
            NonWritableChannelException expected = null;
            try {
                fileChannel.write(ByteBuffer.allocate(10));
            } catch (NonWritableChannelException e) {
                expected = e;
            }
            assertNotNull(expected);
            fileChannel.position(0);
            ByteBuffer buffer = ByteBuffer.allocate(10);
            fileChannel.read(buffer);
            assertReadMessage(buffer);
        } finally {
            fileChannel.close();
        }
    }

    private void checkReadWriteFileChannel(FileChannel fileChannel) throws IOException {
        try {
            final ConnectedStreamChannelMock channelMock = new ConnectedStreamChannelMock();
            channelMock.setReadData("test");
            channelMock.enableRead(true);
            assertNotNull(fileChannel);
            final ByteBuffer buffer = ByteBuffer.allocate(10);
            buffer.put("test".getBytes("UTF-8")).flip();
            assertEquals(4, fileChannel.write(buffer));
            fileChannel.position(0);
            Channels.transferBlocking(channelMock, fileChannel, 0, 4);
            assertWrittenMessage(channelMock, "test");
        } finally {
            fileChannel.close();
        }
    }

    @Test
    public void openFileWithReadOnlyOption() throws IOException {
        final File file = createTempFile();
        final OptionMap optionMap = OptionMap.create(Options.FILE_ACCESS, FileAccess.READ_ONLY);
        checkReadOnlyFileChannel(Xnio.getInstance().openFile(file, optionMap));
    }

    @Test
    public void openFileWithReadWriteOption() throws IOException {
        final File file = createTempFile();
        final OptionMap optionMap = OptionMap.create(Options.FILE_ACCESS, FileAccess.READ_WRITE);
        checkReadWriteFileChannel(Xnio.getInstance().openFile(file, optionMap));
    }

    @Test
    public void openFileWithEmptyOptionMap() throws IOException {
        final File file = createTempFile();
        checkReadWriteFileChannel(Xnio.getInstance().openFile(file, OptionMap.EMPTY));
    }

    @Test
    public void openFileNameWithReadOnlyOption() throws IOException {
        final File file = createTempFile();
        final OptionMap optionMap = OptionMap.create(Options.FILE_ACCESS, FileAccess.READ_ONLY);
        checkReadOnlyFileChannel(Xnio.getInstance().openFile(file.getAbsolutePath(), optionMap));
    }

    @Test
    public void openFileNameWithReadWriteOption() throws IOException {
        final File file = createTempFile();
        final OptionMap optionMap = OptionMap.create(Options.FILE_ACCESS, FileAccess.READ_WRITE);
        checkReadWriteFileChannel(Xnio.getInstance().openFile(file.getAbsolutePath(), optionMap));
    }

    @Test
    public void openFileNameWithEmptyOptionMap() throws IOException {
        final File file = createTempFile();
        checkReadWriteFileChannel(Xnio.getInstance().openFile(file.getAbsolutePath(), OptionMap.EMPTY));
    }

    @Test
    public void openFileWithReadOnlyFileAccess() throws IOException {
        final File file = createTempFile();
        checkReadOnlyFileChannel(Xnio.getInstance().openFile(file, FileAccess.READ_ONLY));
    }

    @Test
    public void openFileWithReadWriteFileAccess() throws IOException {
        final File file = createTempFile();
        checkReadWriteFileChannel(Xnio.getInstance().openFile(file, FileAccess.READ_WRITE));
    }

    @Test
    public void openFileNameWithReadOnlyFileAccess() throws IOException {
        final File file = createTempFile();
        checkReadOnlyFileChannel(Xnio.getInstance().openFile(file.getAbsolutePath(), FileAccess.READ_ONLY));
    }

    @Test
    public void openFileNameWithReadWriteFileAccess() throws IOException {
        final File file = createTempFile();
        checkReadWriteFileChannel(Xnio.getInstance().openFile(file.getAbsolutePath(), FileAccess.READ_WRITE));
    }

    @Test
    public void invalidOpenFileName() throws IOException {
        final File file = createTempFile();
        final Xnio xnio = Xnio.getInstance();
        Exception expected = null;
        try {
            xnio.openFile(file.getAbsolutePath(), (FileAccess) null);
        } catch (IllegalArgumentException e) {
            expected = e;
        }
        assertNotNull(expected);

        expected = null;
        try {
            xnio.openFile(file.getAbsolutePath(), (OptionMap) null);
        } catch (IllegalArgumentException e) {
            expected = e;
        }
        assertNotNull(expected);

        expected = null;
        try {
            xnio.openFile((String) null, FileAccess.READ_WRITE);
        } catch (IllegalArgumentException e) {
            expected = e;
        }
        assertNotNull(expected);

        expected = null;
        try {
            xnio.openFile((String) null, OptionMap.EMPTY);
        } catch (IllegalArgumentException e) {
            expected = e;
        }
        assertNotNull(expected);

        expected = null;
        try {
            xnio.openFile(file, (FileAccess) null);
        } catch (IllegalArgumentException e) {
            expected = e;
        }
        assertNotNull(expected);

        expected = null;
        try {
            xnio.openFile(file, (OptionMap) null);
        } catch (IllegalArgumentException e) {
            expected = e;
        }
        assertNotNull(expected);

        expected = null;
        try {
            xnio.openFile((File) null, FileAccess.READ_WRITE);
        } catch (IllegalArgumentException e) {
            expected = e;
        }
        assertNotNull(expected);

        expected = null;
        try {
            xnio.openFile((File) null, OptionMap.EMPTY);
        } catch (IllegalArgumentException e) {
            expected = e;
        }
        assertNotNull(expected);
    }

    @Test
    public void createWorker() throws IllegalArgumentException, IOException {
        final Xnio xnio = Xnio.getInstance();
        final XnioWorker xnioWorker1 = xnio.createWorker(OptionMap.EMPTY);
        assertNotNull(xnioWorker1);
        assertSame(xnio, xnioWorker1.getXnio());
        assertNull(xnioWorker1.getTerminationTask());
        final XnioWorker xnioWorker2 = xnio.createWorker(Thread.currentThread().getThreadGroup(), OptionMap.EMPTY);
        assertNotNull(xnioWorker2);
        assertSame(xnio, xnioWorker2.getXnio());
        assertNull(xnioWorker2.getTerminationTask());
    }

    @Test
    public void propertiesRetrieval() {
        final Xnio xnio = Xnio.getInstance();
        assertNull(xnio.getProperty("xnio.test.prop"));
        assertEquals("foo", xnio.getProperty("xnio.test.prop", "foo"));
        System.setProperty("xnio.test.prop", "foo2");
        assertEquals("foo2", xnio.getProperty("xnio.test.prop"));
        assertEquals("foo2", xnio.getProperty("xnio.test.prop"));
        AccessController.doPrivileged(new GrantAllPermissionsAction());
        assertNull(xnio.getProperty("xnio.prop.test"));
        assertEquals("aaa", xnio.getProperty("xnio.prop.test", "aaa"));
        System.setProperty("xnio.prop.test", "bbb");
        assertEquals("bbb", xnio.getProperty("xnio.prop.test"));
        assertEquals("bbb", xnio.getProperty("xnio.prop.test"));
        AccessController.doPrivileged(new ResetSecurityManagerAction());
    }

    @Test
    public void illegalPropertiesRetrieval() {
        System.setSecurityManager(null);
        final Xnio xnio = Xnio.getInstance();
        
        SecurityException expected = null;
        try {
            xnio.getProperty("any prop that does not start with xnio.");
        } catch (SecurityException e) {
            expected = e;
        }
        assertNotNull(expected);
        expected = null;
        try {
            xnio.getProperty("xnio - any prop that does not start with xnio.", "default");
        } catch (SecurityException e) {
            expected = e;
        }
        assertNotNull(expected);
        
    }

    private static class SetSecurityPolicyAction implements PrivilegedAction<Void> {

        private final String propertyValue;

        public SetSecurityPolicyAction(String fileName) {
            propertyValue = fileName;
        }

        @Override
        public Void run() {
            System.setProperty("java.security.policy", propertyValue);
            return null;
        }
        
    }

    private static class GrantAllPermissionsAction implements PrivilegedAction<Void> {

        @Override
        public Void run() {
            System.setSecurityManager(new SecurityManager() {
                @Override
                public void checkPermission(Permission permission, Object context) {}
                @Override
                public void checkPermission(Permission permission) {}
            });
            return null;
        }
        
    }

    private static class ResetSecurityManagerAction implements PrivilegedAction<Void> {

        @Override
        public Void run() {
            System.setSecurityManager(null);
            return null;
        }
        
    }
}
