/*
 * 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.karaf.shell.console.impl.jline;

import static org.junit.Assert.assertEquals;
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 java.io.InputStream;
import java.io.PrintStream;
import java.security.PrivilegedAction;

import javax.security.auth.Subject;

import org.apache.felix.gogo.api.CommandSessionListener;
import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl;
import org.apache.felix.service.command.CommandProcessor;
import org.apache.felix.service.command.CommandSession;
import org.apache.felix.service.command.Converter;
import org.apache.karaf.jaas.boot.principal.RolePrincipal;
import org.apache.karaf.shell.console.impl.jline.ConsoleImpl.DelegateSession;
import org.apache.karaf.shell.security.impl.SecuredCommandProcessorImpl;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.junit.Test;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;

public class ConsoleImplTest {
    @Test
    public void testConsoleImpl() throws Exception {
        ServiceReference<?> cmRef = EasyMock.createMock(ServiceReference.class);
        EasyMock.expect(cmRef.getProperty(CommandProcessor.COMMAND_SCOPE)).andReturn("myscope").anyTimes();
        EasyMock.expect(cmRef.getProperty(CommandProcessor.COMMAND_FUNCTION)).andReturn("myfunction").anyTimes();
        EasyMock.replay(cmRef);
        ServiceReference<?>[] cmRefs = new ServiceReference[] {cmRef};

        BundleContext bc = EasyMock.createMock(BundleContext.class);
        EasyMock.expect(bc.getServiceReference((Class<?>) EasyMock.anyObject())).andReturn(null).anyTimes();
        EasyMock.expect(bc.getService((ServiceReference<?>) EasyMock.anyObject())).andReturn(null).anyTimes();
        bc.addServiceListener(EasyMock.isA(ServiceListener.class), EasyMock.isA(String.class));
        EasyMock.expectLastCall().anyTimes();
        EasyMock.expect(bc.getServiceReferences((String) null,
                "(&(osgi.command.scope=*)(osgi.command.function=*)" +
                        "(|(org.apache.karaf.service.guard.roles=myrole)(!(org.apache.karaf.service.guard.roles=*))))")).andReturn(cmRefs).anyTimes();
        EasyMock.expect(bc.getServiceReferences(Converter.class.getName(), null)).andReturn(null).anyTimes();
        EasyMock.expect(bc.getServiceReferences(CommandSessionListener.class.getName(), null)).andReturn(null).anyTimes();
        EasyMock.expect(bc.createFilter(EasyMock.isA(String.class))).andAnswer(new IAnswer<Filter>() {
            @Override
            public Filter answer() throws Throwable {
                return FrameworkUtil.createFilter((String) EasyMock.getCurrentArguments()[0]);
            }
        }).anyTimes();
        EasyMock.replay(bc);

        final ConsoleImpl console = new ConsoleImpl(null, new ThreadIOImpl(), System.in, System.out, System.err, null, "UTF-8", null, bc, true);
        assertTrue(console.session instanceof DelegateSession);

        console.session.put("foo", "bar");

        final DelegateSession ds = (DelegateSession) console.session;
        assertNull("Precondition", ds.delegate);

        Subject subject = new Subject();
        subject.getPrincipals().add(new RolePrincipal("myrole"));

        Subject.doAs(subject, new PrivilegedAction<Object>() {
            @Override
            public Object run() {
                SecuredCommandProcessorImpl secCP = console.createSecuredCommandProcessor();
                assertNotNull(ds.delegate);
                assertEquals("Attributes set before the delegate was set should have been transferred",
                        "bar", ds.get("foo"));
                assertEquals("Attributes set before the delegate was set should have been transferred",
                        "bar", ds.delegate.get("foo"));
                assertSame(System.out, ds.delegate.getConsole());
                assertSame(System.out, ds.getConsole());

                assertTrue(secCP.getCommands().contains("myscope:myfunction"));

                return null;
            }
        });
    }

    @Test
    public void testDelegateSession() throws Exception {
        DelegateSession ds = new DelegateSession();

        ds.put("a", "b");
        assertEquals("b", ds.get("a"));

        TestSession ts = new TestSession();

        assertNull("Precondition", ts.lastInvoked);

        ds.setDelegate(ts);
        assertEquals("put(a,b)", ts.lastInvoked);

        ds.put("c", "d");
        assertEquals("put(c,d)", ts.lastInvoked);

        ds.execute("hello 1234");
        assertEquals("execute(hello 1234)", ts.lastInvoked);

        ds.close();
        assertEquals("close", ts.lastInvoked);

        ds.getKeyboard();
        assertEquals("getKeyboard", ts.lastInvoked);

        ds.getConsole();
        assertEquals("getConsole", ts.lastInvoked);

        ds.get("xyz");
        assertEquals("get(xyz)", ts.lastInvoked);

        ds.format("foo", 12);
        assertEquals("format(foo,12)", ts.lastInvoked);

        ds.convert(TestSession.class, "a string");
        assertEquals("convert(TestSession,a string)", ts.lastInvoked);
    }

    static class TestSession implements CommandSession {
        String lastInvoked;

        @Override
        public Object execute(CharSequence commandline) throws Exception {
            lastInvoked = "execute(" + commandline + ")";
            return null;
        }

        @Override
        public void close() {
            lastInvoked = "close";
        }

        @Override
        public InputStream getKeyboard() {
            lastInvoked = "getKeyboard";
            return null;
        }

        @Override
        public PrintStream getConsole() {
            lastInvoked = "getConsole";
            return null;
        }

        @Override
        public Object get(String name) {
            lastInvoked = "get(" + name + ")";
            return null;
        }

        @Override
        public void put(String name, Object value) {
            lastInvoked = "put(" + name + "," + value + ")";
        }

        @Override
        public CharSequence format(Object target, int level) {
            lastInvoked = "format(" + target + "," + level + ")";
            return null;
        }

        @Override
        public Object convert(Class<?> type, Object instance) {
            lastInvoked = "convert(" + type.getSimpleName() + "," + instance + ")";
            return null;
        }
    }
}