//
//  ========================================================================
//  Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.websocket.common.events;

import java.io.InputStream;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.events.annotated.AbstractMethodAnnotationScanner;
import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod;
import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.events.annotated.OptionalSessionCallableMethod;

public class JettyAnnotatedScanner extends AbstractMethodAnnotationScanner<JettyAnnotatedMetadata>
{
    private static final Logger LOG = Log.getLogger(JettyAnnotatedScanner.class);

    /**
     * Parameter list for &#064;OnWebSocketMessage (Binary mode)
     */
    private static final ParamList validBinaryParams;

    /**
     * Parameter list for &#064;OnWebSocketConnect
     */
    private static final ParamList validConnectParams;

    /**
     * Parameter list for &#064;OnWebSocketClose
     */
    private static final ParamList validCloseParams;

    /**
     * Parameter list for &#064;OnWebSocketError
     */
    private static final ParamList validErrorParams;

    /**
     * Parameter list for &#064;OnWebSocketFrame
     */
    private static final ParamList validFrameParams;

    /**
     * Parameter list for &#064;OnWebSocketMessage (Text mode)
     */
    private static final ParamList validTextParams;

    static
    {
        validConnectParams = new ParamList();
        validConnectParams.addParams(Session.class);

        validCloseParams = new ParamList();
        validCloseParams.addParams(int.class,String.class);
        validCloseParams.addParams(Session.class,int.class,String.class);

        validErrorParams = new ParamList();
        validErrorParams.addParams(Throwable.class);
        validErrorParams.addParams(Session.class,Throwable.class);

        validTextParams = new ParamList();
        validTextParams.addParams(String.class);
        validTextParams.addParams(Session.class,String.class);
        validTextParams.addParams(Reader.class);
        validTextParams.addParams(Session.class,Reader.class);

        validBinaryParams = new ParamList();
        validBinaryParams.addParams(byte[].class,int.class,int.class);
        validBinaryParams.addParams(Session.class,byte[].class,int.class,int.class);
        validBinaryParams.addParams(InputStream.class);
        validBinaryParams.addParams(Session.class,InputStream.class);

        validFrameParams = new ParamList();
        validFrameParams.addParams(Frame.class);
        validFrameParams.addParams(Session.class,Frame.class);
    }

    @Override
    public void onMethodAnnotation(JettyAnnotatedMetadata metadata, Class<?> pojo, Method method, Annotation annotation)
    {
        if (LOG.isDebugEnabled())
            LOG.debug("onMethodAnnotation({}, {}, {}, {})",metadata,pojo,method,annotation);

        if (isAnnotation(annotation,OnWebSocketConnect.class))
        {
            assertValidSignature(method,OnWebSocketConnect.class,validConnectParams);
            assertUnset(metadata.onConnect,OnWebSocketConnect.class,method);
            metadata.onConnect = new CallableMethod(pojo,method);
            return;
        }

        if (isAnnotation(annotation,OnWebSocketMessage.class))
        {
            if (isSignatureMatch(method,validTextParams))
            {
                // Text mode
                assertUnset(metadata.onText,OnWebSocketMessage.class,method);
                metadata.onText = new OptionalSessionCallableMethod(pojo,method);
                return;
            }

            if (isSignatureMatch(method,validBinaryParams))
            {
                // Binary Mode
                // TODO
                assertUnset(metadata.onBinary,OnWebSocketMessage.class,method);
                metadata.onBinary = new OptionalSessionCallableMethod(pojo,method);
                return;
            }

            throw InvalidSignatureException.build(method,OnWebSocketMessage.class,validTextParams,validBinaryParams);
        }

        if (isAnnotation(annotation,OnWebSocketClose.class))
        {
            assertValidSignature(method,OnWebSocketClose.class,validCloseParams);
            assertUnset(metadata.onClose,OnWebSocketClose.class,method);
            metadata.onClose = new OptionalSessionCallableMethod(pojo,method);
            return;
        }

        if (isAnnotation(annotation,OnWebSocketError.class))
        {
            assertValidSignature(method,OnWebSocketError.class,validErrorParams);
            assertUnset(metadata.onError,OnWebSocketError.class,method);
            metadata.onError = new OptionalSessionCallableMethod(pojo,method);
            return;
        }

        if (isAnnotation(annotation,OnWebSocketFrame.class))
        {
            assertValidSignature(method,OnWebSocketFrame.class,validFrameParams);
            assertUnset(metadata.onFrame,OnWebSocketFrame.class,method);
            metadata.onFrame = new OptionalSessionCallableMethod(pojo,method);
            return;
        }
    }

    public JettyAnnotatedMetadata scan(Class<?> pojo)
    {
        JettyAnnotatedMetadata metadata = new JettyAnnotatedMetadata();
        scanMethodAnnotations(metadata,pojo);
        return metadata;
    }
}
