//
//  ========================================================================
//  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.io;

import java.net.InetSocketAddress;
import java.util.concurrent.Executor;

import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.ExecutorThreadPool;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.SuspendToken;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.ConnectionState;
import org.eclipse.jetty.websocket.common.LogicalConnection;
import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
import org.junit.rules.TestName;

public class LocalWebSocketConnection implements LogicalConnection, IncomingFrames, ConnectionStateListener
{
    private static final Logger LOG = Log.getLogger(LocalWebSocketConnection.class);
    private final String id;
    private final ByteBufferPool bufferPool;
    private final Executor executor;
    private WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
    private IncomingFrames incoming;
    private IOState ioState = new IOState();

    public LocalWebSocketConnection(ByteBufferPool bufferPool)
    {
        this("anon",bufferPool);
    }

    public LocalWebSocketConnection(String id, ByteBufferPool bufferPool)
    {
        this.id = id;
        this.bufferPool = bufferPool;
        this.executor = new ExecutorThreadPool();
        this.ioState.addListener(this);
    }

    public LocalWebSocketConnection(TestName testname, ByteBufferPool bufferPool)
    {
        this(testname.getMethodName(),bufferPool);
    }
    
    @Override
    public Executor getExecutor()
    {
        return executor;
    }

    @Override
    public void close()
    {
        close(StatusCode.NORMAL,null);
    }

    @Override
    public void close(int statusCode, String reason)
    {
        if (LOG.isDebugEnabled())
            LOG.debug("close({}, {})",statusCode,reason);
        CloseInfo close = new CloseInfo(statusCode,reason);
        ioState.onCloseLocal(close);
    }

    public void connect()
    {
        if (LOG.isDebugEnabled())
            LOG.debug("connect()");
        ioState.onConnected();
    }

    @Override
    public void disconnect()
    {
        if (LOG.isDebugEnabled())
            LOG.debug("disconnect()");
    }

    @Override
    public ByteBufferPool getBufferPool()
    {
        return this.bufferPool;
    }

    @Override
    public String getId()
    {
        return this.id;
    }

    @Override
    public long getIdleTimeout()
    {
        return 0;
    }

    public IncomingFrames getIncoming()
    {
        return incoming;
    }

    @Override
    public IOState getIOState()
    {
        return ioState;
    }

    @Override
    public InetSocketAddress getLocalAddress()
    {
        return null;
    }

    @Override
    public long getMaxIdleTimeout()
    {
        return 0;
    }

    @Override
    public WebSocketPolicy getPolicy()
    {
        return policy;
    }

    @Override
    public InetSocketAddress getRemoteAddress()
    {
        return null;
    }

    @Override
    public void incomingError(Throwable e)
    {
        incoming.incomingError(e);
    }

    @Override
    public void incomingFrame(Frame frame)
    {
        incoming.incomingFrame(frame);
    }

    @Override
    public boolean isOpen()
    {
        return getIOState().isOpen();
    }

    @Override
    public boolean isReading()
    {
        return false;
    }

    @Override
    public void onConnectionStateChange(ConnectionState state)
    {
        if (LOG.isDebugEnabled())
            LOG.debug("Connection State Change: {}",state);
        switch (state)
        {
            case CLOSED:
                this.disconnect();
                break;
            case CLOSING:
                if (ioState.wasRemoteCloseInitiated())
                {
                    // send response close frame
                    CloseInfo close = ioState.getCloseInfo();
                    LOG.debug("write close frame: {}",close);
                    ioState.onCloseLocal(close);
                }
            default:
                break;
        }
    }

    public void open()
    {
        if (LOG.isDebugEnabled())
            LOG.debug("open()");
        ioState.onOpened();
    }

    @Override
    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
    {
    }

    @Override
    public void resume()
    {
    }

    @Override
    public void setMaxIdleTimeout(long ms)
    {
    }

    @Override
    public void setNextIncomingFrames(IncomingFrames incoming)
    {
        this.incoming = incoming;
    }

    public void setPolicy(WebSocketPolicy policy)
    {
        this.policy = policy;
    }

    @Override
    public SuspendToken suspend()
    {
        return null;
    }

    @Override
    public String toString()
    {
        return String.format("%s[%s]",LocalWebSocketConnection.class.getSimpleName(),id);
    }
}
