/**
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 */
package ch.qos.logback.core.net.server;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.Socket;
import java.net.SocketException;
import java.util.concurrent.BlockingQueue;

import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.util.CloseUtil;

/**
 * A {@link RemoteReceiverClient} that writes serialized logging events to an
 * {@link OutputStream}.
 *
 * @author Carl Harris
 */
class RemoteReceiverStreamClient extends ContextAwareBase implements RemoteReceiverClient {

    private final String clientId;
    private final Socket socket;
    private final OutputStream outputStream;

    private BlockingQueue<Serializable> queue;

    /**
     * Constructs a new client.
     * 
     * @param id     identifier string for the client
     * @param socket socket to which logging events will be written
     */
    public RemoteReceiverStreamClient(String id, Socket socket) {
        this.clientId = "client " + id + ": ";
        this.socket = socket;
        this.outputStream = null;
    }

    /**
     * Constructs a new client.
     * <p>
     * This constructor exists primarily to support unit tests where it is
     * inconvenient to have to create a socket for the test.
     * 
     * @param id           identifier string for the client
     * @param outputStream output stream to which logging Events will be written
     */
    RemoteReceiverStreamClient(String id, OutputStream outputStream) {
        this.clientId = "client " + id + ": ";
        this.socket = null;
        this.outputStream = outputStream;
    }

    /**
     * {@inheritDoc}
     */
    public void setQueue(BlockingQueue<Serializable> queue) {
        this.queue = queue;
    }

    /**
     * {@inheritDoc}
     */
    public boolean offer(Serializable event) {
        if (queue == null) {
            throw new IllegalStateException("client has no event queue");
        }
        return queue.offer(event);
    }

    /**
     * {@inheritDoc}
     */
    public void close() {
        if (socket == null)
            return;
        CloseUtil.closeQuietly(socket);
    }

    /**
     * {@inheritDoc}
     */
    public void run() {
        addInfo(clientId + "connected");

        ObjectOutputStream oos = null;
        try {
            int counter = 0;
            oos = createObjectOutputStream();
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    Serializable event = queue.take();
                    oos.writeObject(event);
                    oos.flush();
                    if (++counter >= CoreConstants.OOS_RESET_FREQUENCY) {
                        // failing to reset the stream periodically will result in a
                        // serious memory leak (as noted in AbstractSocketAppender)
                        counter = 0;
                        oos.reset();
                    }
                } catch (InterruptedException ex) {
                    Thread.currentThread().interrupt();
                }
            }
        } catch (SocketException ex) {
            addInfo(clientId + ex);
        } catch (IOException ex) {
            addError(clientId + ex);
        } catch (RuntimeException ex) {
            addError(clientId + ex);
        } finally {
            if (oos != null) {
                CloseUtil.closeQuietly(oos);
            }
            close();
            addInfo(clientId + "connection closed");
        }
    }

    private ObjectOutputStream createObjectOutputStream() throws IOException {
        if (socket == null) {
            return new ObjectOutputStream(outputStream);
        }
        return new ObjectOutputStream(socket.getOutputStream());
    }
}
