/*
 * Copyright (C) 2005-2008 Jive Software. All rights reserved.
 *
 * Licensed 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.jivesoftware.openfire.mediaproxy;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Listen packets from defined dataSocket and send packets to the defined host.
 *
 * @author Thiago Camargo
 */
abstract class Channel implements Runnable {
    
    private static final Logger Log = LoggerFactory.getLogger(Channel.class);

    protected byte[] buf = new byte[5000];
    protected DatagramSocket dataSocket;
    protected DatagramPacket packet;
    protected boolean enabled = true;

    List<DatagramListener> listeners = new ArrayList<>();

    protected InetAddress host;
    protected int port;

    /**
     * Creates a Channel according to the parameters.
     *
     * @param dataSocket
     * @param host
     * @param port
     */
    public Channel(DatagramSocket dataSocket, InetAddress host, int port) {
        this.dataSocket = dataSocket;
        this.host = host;
        this.port = port;
    }

    /**
     * Get the host that the packet will be sent to.
     *
     * @return remote host address
     */
    public InetAddress getHost() {
        return host;
    }

    /**
     * Set the host that the packet will be sent to.
     */
    protected void setHost(InetAddress host) {
        this.host = host;
    }

    /**
     * Get the port that the packet will be sent to.
     *
     * @return The remote port number
     */
    public int getPort() {
        return port;
    }

    /**
     * Set the port that the packet will be sent to.
     *
     * @param port
     */
    protected void setPort(int port) {
        this.port = port;
    }

    /**
     * Adds a DatagramListener to the Channel
     *
     * @param datagramListener
     */
    public void addListener(DatagramListener datagramListener) {
        listeners.add(datagramListener);
    }

    /**
     * Remove a DatagramListener from the Channel
     *
     * @param datagramListener
     */
    public void removeListener(DatagramListener datagramListener) {
        listeners.remove(datagramListener);
    }

    /**
     * Remove every Listeners
     */
    public void removeListeners() {
        listeners.removeAll(listeners);
    }

    public void cancel() {
        this.enabled = false;
        if (dataSocket != null){
            dataSocket.close();
        }
    }

    /**
     * Thread override method
     */
    @Override
    public void run() {
        try {
            while (enabled) {
                // Block until a datagram appears:
                packet = new DatagramPacket(buf, buf.length);
                dataSocket.receive(packet);

                if (handle(packet)) {
                    boolean resend = true;

                    for (DatagramListener dl : listeners) {
                        boolean send = dl.datagramReceived(packet);
                        if (resend && !send) {
                            resend = false;
                        }
                    }

                    if (resend) {
                        relayPacket(packet);
                    }
                }
            }
        }
        catch (UnknownHostException uhe) {
            if (enabled) {
                Log.error("Unknown Host", uhe);
            }
        }
        catch (SocketException se) {
            if (enabled) {
                Log.error("Socket closed", se);
            }
        }
        catch (IOException ioe) {
            if (enabled) {
                Log.error("Communication error", ioe);
            }
        }
    }

    public void relayPacket(DatagramPacket packet) {
        try {
            DatagramPacket echo = new DatagramPacket(packet.getData(), packet.getLength(), host, port);
            dataSocket.send(echo);
        }
        catch (IOException e) {
            Log.error(e.getMessage(), e);
        }
    }

    /**
     * Handles received packet and returns true if the packet should be processed by the channel.
     *
     * @param packet received datagram packet
     * @return true if listeners will be alerted that a new packet was received.
     */
    abstract boolean handle(DatagramPacket packet);
}
