/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            multicast.cc
 *
 *  Mon Sep 26 12:25:22 CEST 2005
 *  Copyright  2005 Bent Bisballe Nyeng
 *  deva@aasimon.org
 ****************************************************************************/

/*
 *  This file is part of MIaV.
 *
 *  MIaV is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  MIaV is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with MIaV; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */
#include "config.h"
#include "multicast.h"

#include "multicast_configuration.h"

#include "miav_config.h"

#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/param.h>
#include <arpa/inet.h>
#include <sys/types.h>

#include <string.h>

// For IP_MTU
//#include <linux/in.h>
//#ifndef IP_MTU
//#define IP_MTU 14
//#endif

#include <errno.h>

Multicast::Multicast(Info *i, mcastconf_t &mcclientconf)
{
  info = i;
  udp_buffer = NULL;
  
  multicast_audio = mcclientconf.with_audio;
    
  // Open connection socket
  if(!UDPOpen(mcclientconf.addr.c_str(), mcclientconf.port)) 
    info->error("Error creating socket %s:%d", 
                mcclientconf.addr.c_str(),
                mcclientconf.port);

  int mtu = config->readInt("udp_packet_size");

  // Create buffer with the size of MTU
  //  socklen_t mtu_sz;
  //  if(getsockopt(sock, SOL_IP, IP_MTU, &mtu, &mtu_sz) != -1) {

  udp_buffer_size = mtu - 28;
  if(udp_buffer_size < 1) udp_buffer_size = 1;
  udp_buffer = new char[udp_buffer_size];
  udp_buffer_pointer = udp_buffer;
  info->info("UDP packet buffer size %db", udp_buffer_size);

  //} else {
  //    info->error("Error getting MTU size from socket: %s", strerror(errno));
  //    return;
  //}
}

Multicast::~Multicast()
{
  if(udp_buffer) delete udp_buffer;
}

int Multicast::Write(void* buf, int size)
{
  if(!udp_buffer) return 0; // no buffer to write in... better break out!

  //  info->info("To send: %d", size);

  char *p = (char*)buf;
  int left = udp_buffer_size - (udp_buffer_pointer - udp_buffer);
 
  while(size) {
    int to_copy = size > left ? left : size;
    
    memcpy(udp_buffer_pointer, p, to_copy);
  
    left-=to_copy;
    udp_buffer_pointer += to_copy;

    p+=to_copy;
    size-=to_copy;

    //    info->info("Copied %d - %d to go", to_copy, size);

    if(left == 0) {
      //      info->info("Sending full packet");
      if(write(sock, udp_buffer, udp_buffer_size) != udp_buffer_size) {
        info->error("Could not write entire buffer to socket.");
      }
      left = udp_buffer_size;
      udp_buffer_pointer = udp_buffer;
    }
  }
  return size;
}

bool Multicast::is_address_multicast(unsigned long address)
{
  if((address & 255) >= 224 && (address & 255) <= 239) {
    info->info("Address is multicast.");
    return true;
  }
    info->info("Address is NOT multicast.");
  return false;
}

/*
 * open UDP socket
 */
bool Multicast::UDPOpen(const char *address, int port)
{
  int enable = 1L;
  struct sockaddr_in stAddr;
  struct sockaddr_in stLclAddr;
  struct hostent * host;
  //  int sock;
  
  stAddr.sin_family = AF_INET;
  stAddr.sin_port = htons(port);
  if((host = gethostbyname(address)) == NULL) return false;
  stAddr.sin_addr = *((struct in_addr *) host->h_addr_list[0]);

  // Create a UDP socket
  if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
        return false;

  // Allow multiple instance of the client to share the same address and port
  if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof(unsigned long int)) < 0) 
    return false;

  // If the address is multicast, register to the multicast group
  if(is_address_multicast(stAddr.sin_addr.s_addr)) {
    struct ip_mreq stMreq;
    
    // Bind the socket to port
    stLclAddr.sin_family      = AF_INET;
    stLclAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    stLclAddr.sin_port        = stAddr.sin_port;
    if(bind(sock, (struct sockaddr*) & stLclAddr, sizeof(stLclAddr)) < 0) return false;

    // Register to a multicast address
    stMreq.imr_multiaddr.s_addr = stAddr.sin_addr.s_addr;
    stMreq.imr_interface.s_addr = INADDR_ANY;
    if(setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) & stMreq, sizeof(stMreq)) < 0) 
      return false;
  } else {
    // Bind the socket to port
    stLclAddr.sin_family      = AF_INET;
    stLclAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    stLclAddr.sin_port        = htons(0);
    if(bind(sock, (struct sockaddr*) & stLclAddr, sizeof(stLclAddr)) < 0)
        return false;
  }

  connect(sock, (struct sockaddr*) & stAddr, sizeof(stAddr));

  return true;
}