/*
 *
 * 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.qpid.proton.engine;

import java.io.IOException;

import junit.framework.TestCase;

import org.apache.qpid.proton.engine.Event.Type;
import org.apache.qpid.proton.engine.EventExtensibilityTest.ExtraEvent.ExtraTypes;
import org.apache.qpid.proton.reactor.Reactor;
import org.apache.qpid.proton.reactor.Selectable;
import org.apache.qpid.proton.reactor.Task;
import org.junit.Test;

public class EventExtensibilityTest extends TestCase {

    // //////////////
    // / Extra package public API classes
    // //////////////

    /**
     * Sample additional information that gets generated and passed around with
     * the extension events. The information is stored inside
     * {@link Event#attachments()} see {@link ExtraEventImpl#getExtraInfo()}
     */
    public static class ExtraInfo {
        public int foo;
    }

    /**
     * Additional events generated by this extension.
     *
     */
    public interface ExtraEvent extends Event {
        /**
         * Enum is a convenient mechanism for defining new event types
         */
        public enum ExtraTypes implements EventType {
            FOO,
            BAR,
            NOT_A_EXTRA_EVENT {
                @Override public boolean isValid() { return false; }
            };

            @Override
            public boolean isValid() {
                return true;
            }
        }

        /**
         * typesafe access to event type generated by this extension useful for
         * handling in switch statements
         *
         * @return one of enum values. When invoked on an Event that is not an
         *         ExtraEvent the return value shall be
         */
        ExtraTypes getExtraEventType();

        /**
         * typesafe access to extra information attached to additional events
         *
         * @return ExtraInfo stored in the event attachment
         */
        ExtraInfo getExtraInfo();
    }

    /**
     * New handler methods for handling the extended event types. These methods
     * can take {@link ExtraEvent} as a parameter to get typesafe access to
     * {@link ExtraInfo}
     *
     * The interface needs to extend the {@link Handler} interface.
     */
    public interface ExtraHandler extends Handler {
        void onFoo(ExtraEvent e);

        void onBar(ExtraEvent e);
    }

    /**
     * Implementation of the default base class for ExtraHandler. All events
     * should be forwarded to the {@link Handler#onUnhandled(Event)} method
     */
    public static class ExtraBaseHandler extends BaseHandler implements ExtraHandler {

        @Override
        public void onFoo(ExtraEvent e) {
            this.onUnhandled(e);
        }

        @Override
        public void onBar(ExtraEvent e) {
            this.onUnhandled(e);
        }

        @Override
        public void handle(Event e) {
            EventType type = e.getEventType();
            if (type instanceof ExtraEvent.ExtraTypes) {
                final ExtraEvent event;
                if (e instanceof ExtraEvent) {
                    event = (ExtraEvent)e;
                } else {
                    event = new ExtraEventImpl(e);
                }
                switch((ExtraEvent.ExtraTypes)type) {
                case BAR:
                    onBar(event);
                    break;
                case FOO:
                    onFoo(event);
                    break;
                case NOT_A_EXTRA_EVENT:
                    super.handle(e);
                    break;
                }
            } else {
                super.handle(e);
            }
        }

    }

    // //////////////
    // / Extra package implementation classes
    // //////////////


    /**
     * Typesafe access to ExtraInfo attached to event
     *
     */
    public static class ExtraEventImpl implements ExtraEvent {
        /**
         * making this accessor public allows for easy binding to the scripting language
         */
        public static final ExtendableAccessor<Event, ExtraInfo> extraInfoAccessor = new ExtendableAccessor<>(ExtraInfo.class);

        private Event impl;

        public ExtraEventImpl(Event impl) {
            this.impl = impl;
        }

        @Override
        public ExtraTypes getExtraEventType() {
            EventType type = impl.getEventType();
            if (type instanceof ExtraTypes)
                return (ExtraTypes) type;
            else
                return ExtraTypes.NOT_A_EXTRA_EVENT;
        }

        @Override
        public ExtraInfo getExtraInfo() {
            return extraInfoAccessor.get(impl);
        }

        // ---- delegate methods for the Event

        @Override
		public Record attachments() {
            return impl.attachments();
        }

        @Override
		public EventType getEventType() {
            return impl.getEventType();
        }

        @Override
		public Type getType() {
            return impl.getType();
        }

        @Override
		public Object getContext() {
            return impl.getContext();
        }

        @Override
		public Handler getRootHandler() {
            return impl.getRootHandler();
        }

        @Override
		public void dispatch(Handler handler) throws HandlerException {
            impl.dispatch(handler);
        }

        @Override
		public void redispatch(EventType as_type, Handler handler) throws HandlerException {
            impl.redispatch(as_type, handler);
        }
        @Override
		public Connection getConnection() {
            return impl.getConnection();
        }

        @Override
		public Session getSession() {
            return impl.getSession();
        }

        @Override
		public Link getLink() {
            return impl.getLink();
        }

        @Override
        public Sender getSender() {
            return impl.getSender();
        }

        @Override
        public Receiver getReceiver() {
            return impl.getReceiver();
        }

        @Override
		public Delivery getDelivery() {
            return impl.getDelivery();
        }

        @Override
		public Transport getTransport() {
            return impl.getTransport();
        }

        @Override
		public Reactor getReactor() {
            return impl.getReactor();
        }

        @Override
		public Selectable getSelectable() {
            return impl.getSelectable();
        }

        @Override
		public Task getTask() {
            return impl.getTask();
        }

        @Override
		public Event copy() {
            return new ExtraEventImpl(impl.copy());
        }

        @Override
        public void delegate() throws HandlerException {
            impl.delegate();
        }
    }

    public class ExtendedTestEventGenerator extends BaseHandler {
        @Override
        public void onReactorInit(Event e) {
            ExtraInfo extra = new ExtraInfo();
            extra.foo = 1234;
            ExtraEventImpl.extraInfoAccessor.set(e, extra);
            e.redispatch(ExtraEvent.ExtraTypes.FOO, this);
        }
    }

    public class ExtendedTestHandler extends ExtraBaseHandler {
        public boolean didFoo = false;
        @Override
        public void onFoo(ExtraEvent e) {
            assertEquals(ExtraEvent.ExtraTypes.FOO, e.getEventType());
            assertEquals(ExtraEvent.ExtraTypes.FOO, e.getExtraEventType());
            assertEquals(Event.Type.NON_CORE_EVENT, e.getType());
            assertNotNull(e.getExtraInfo());
            assertEquals(1234, e.getExtraInfo().foo);
            didFoo = true;
        }
    }

    public class FooUnawareTestHandler extends BaseHandler {
        public boolean seenFoo = false;
        @Override
        public void onUnhandled(Event e) {
            if (e.getEventType() == ExtraTypes.FOO) {
                seenFoo = true;
            }
        }
    }

    @Test
    public void testExtendedType() throws IOException {
        Reactor r = Reactor.Factory.create();
        ExtendedTestEventGenerator gen = new ExtendedTestEventGenerator();
        BaseHandler empty = new BaseHandler();
        ExtendedTestHandler extra = new ExtendedTestHandler();
        FooUnawareTestHandler unaware = new FooUnawareTestHandler();
        gen.add(empty);
        empty.add(extra);
        extra.add(unaware);
        r.setGlobalHandler(gen);
        r.run();
        assertTrue(extra.didFoo);
        assertTrue(unaware.seenFoo);
    }

    @Test
    public void test() {
        Event.Type t = Type.NON_CORE_EVENT;
        switch (extracted(t)) {
        case CONNECTION_BOUND:
            fail();
        case CONNECTION_FINAL:
            fail();
        case CONNECTION_INIT:
            fail();
        case CONNECTION_LOCAL_CLOSE:
            fail();
        case CONNECTION_LOCAL_OPEN:
            fail();
        case CONNECTION_REMOTE_CLOSE:
            fail();
        case CONNECTION_REMOTE_OPEN:
            fail();
        case CONNECTION_UNBOUND:
            fail();
        case DELIVERY:
            fail();
        case LINK_FINAL:
            fail();
        case LINK_FLOW:
            fail();
        case LINK_INIT:
            fail();
        case LINK_LOCAL_CLOSE:
            fail();
        case LINK_LOCAL_DETACH:
            fail();
        case LINK_LOCAL_OPEN:
            fail();
        case LINK_REMOTE_CLOSE:
            fail();
        case LINK_REMOTE_DETACH:
            fail();
        case LINK_REMOTE_OPEN:
            fail();
        case REACTOR_FINAL:
            fail();
        case REACTOR_INIT:
            fail();
        case REACTOR_QUIESCED:
            fail();
        case SELECTABLE_ERROR:
            fail();
        case SELECTABLE_EXPIRED:
            fail();
        case SELECTABLE_FINAL:
            fail();
        case SELECTABLE_INIT:
            fail();
        case SELECTABLE_READABLE:
            fail();
        case SELECTABLE_UPDATED:
            fail();
        case SELECTABLE_WRITABLE:
            fail();
        case SESSION_FINAL:
            fail();
        case SESSION_INIT:
            fail();
        case SESSION_LOCAL_CLOSE:
            fail();
        case SESSION_LOCAL_OPEN:
            fail();
        case SESSION_REMOTE_CLOSE:
            fail();
        case SESSION_REMOTE_OPEN:
            fail();
        case TIMER_TASK:
            fail();
        case TRANSPORT:
            fail();
        case TRANSPORT_CLOSED:
            fail();
        case TRANSPORT_ERROR:
            fail();
        case TRANSPORT_HEAD_CLOSED:
            fail();
        case TRANSPORT_TAIL_CLOSED:
            fail();
        case NON_CORE_EVENT:
            break;
        }
    }

    private Type extracted(Event.Type t) {
        return t;
    }

}
