/*
 *  $Id: connection.c,v 1.16 2010/08/03 10:51:53 schmirl Exp $
 */
 
#include "server/connection.h"
#include "server/setup.h"
#include "server/suspend.h"
#include "common.h"

#include <vdr/tools.h>
#include <vdr/thread.h>
#include <vdr/transfer.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>

class cSwitchLive {
private:
	cMutex         mutex;
	cCondWait      switched;
	cDevice        *device;
	const cChannel *channel;
public:
	cDevice* Switch(cDevice *Device, const cChannel *Channel);
	void Switch(void);
	cSwitchLive(void);
};

cSwitchLive::cSwitchLive(): device(NULL), channel(NULL)
{
}

cDevice* cSwitchLive::Switch(cDevice *Device, const cChannel *Channel)
{
	mutex.Lock();
	device = Device;
	channel = Channel;
	mutex.Unlock();
	switched.Wait();
	return device;
}

void cSwitchLive::Switch(void)
{
	mutex.Lock();
	if (channel && device) {
		cDevice::SetAvoidDevice(device);
		if (!Channels.SwitchTo(cDevice::CurrentChannel())) {
			if (StreamdevServerSetup.SuspendMode == smAlways) {
				Channels.SwitchTo(channel->Number());
				Skins.Message(mtInfo, tr("Streaming active"));
			}
			else {
				esyslog("streamdev: Can't receive channel %d (%s) from device %d. Moving live TV to other device failed (PrimaryDevice=%d, ActualDevice=%d)", channel->Number(), channel->Name(), device->CardIndex(), cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex());
				device = NULL;
			}
		}
		// make sure we don't come in here next time
		channel = NULL;
		switched.Signal();
	}
	mutex.Unlock();
}

cServerConnection::cServerConnection(const char *Protocol, int Type):
		cTBSocket(Type),
		m_Protocol(Protocol),
		m_DeferClose(false),
		m_Pending(false),
		m_ReadBytes(0),
		m_WriteBytes(0),
		m_WriteIndex(0)
{
	m_SwitchLive = new cSwitchLive();
}

cServerConnection::~cServerConnection() 
{
	delete m_SwitchLive;
}

const cChannel* cServerConnection::ChannelFromString(const char *String, int *Apid, int *Dpid) {
	const cChannel *channel = NULL;
	char *string = strdup(String);
	char *ptr, *end;
	int apididx = 0;
	
	if ((ptr = strrchr(string, '+')) != NULL) {
		*(ptr++) = '\0';
		apididx = strtoul(ptr, &end, 10);
		Dprintf("found apididx: %d\n", apididx);
	}

	if (isnumber(string)) {
		int temp = strtol(String, NULL, 10);
		if (temp >= 1 && temp <= Channels.MaxNumber())
			channel = Channels.GetByNumber(temp);
	} else {
		channel = Channels.GetByChannelID(tChannelID::FromString(string));

		if (channel == NULL) {
			int i = 1;
			while ((channel = Channels.GetByNumber(i, 1)) != NULL) {
				if (String == channel->Name())
					break;

				i = channel->Number() + 1;
			}
		}
	}

	if (channel != NULL && apididx > 0) {
		int apid = 0, dpid = 0;
		int index = 1;

		for (int i = 0; channel->Apid(i) != 0; ++i, ++index) {
			if (index == apididx) {
				apid = channel->Apid(i);
				break;
			}
		}

		if (apid == 0) {
			for (int i = 0; channel->Dpid(i) != 0; ++i, ++index) {
				if (index == apididx) {
					dpid = channel->Dpid(i);
					break;
				}
			}
		}

		if (Apid != NULL) 
			*Apid = apid;
		if (Dpid != NULL) 
			*Dpid = dpid;
	}

	free(string);
	return channel;
}

bool cServerConnection::Read(void) 
{
	int b;
	if ((b = cTBSocket::Read(m_ReadBuffer + m_ReadBytes,
	                         sizeof(m_ReadBuffer) - m_ReadBytes - 1)) < 0) {
		esyslog("ERROR: read from client (%s) %s:%d failed: %m",
		        m_Protocol, RemoteIp().c_str(), RemotePort());
		return false;
	}

	if (b == 0) {
		isyslog("client (%s) %s:%d has closed connection",
		        m_Protocol, RemoteIp().c_str(), RemotePort());
		return false;
	}

	m_ReadBytes += b;
	m_ReadBuffer[m_ReadBytes] = '\0';

	char *end;
	bool result = true;
	while ((end = strchr(m_ReadBuffer, '\012')) != NULL) {
		*end = '\0';
		if (end > m_ReadBuffer && *(end - 1) == '\015')
			*(end - 1) = '\0';

		if (!Command(m_ReadBuffer))
			return false;

		m_ReadBytes -= ++end - m_ReadBuffer;
		if (m_ReadBytes > 0)
			memmove(m_ReadBuffer, end, m_ReadBytes);
	}

	if (m_ReadBytes == sizeof(m_ReadBuffer) - 1) {
		esyslog("ERROR: streamdev: input buffer overflow (%s) for %s:%d",
		        m_Protocol, RemoteIp().c_str(), RemotePort());
		return false;
	}
	
	return result;
}

bool cServerConnection::Write(void) 
{
	int b;
	if ((b = cTBSocket::Write(m_WriteBuffer + m_WriteIndex, 
	                          m_WriteBytes - m_WriteIndex)) < 0) {
		esyslog("ERROR: streamdev: write to client (%s) %s:%d failed: %m",
		        m_Protocol, RemoteIp().c_str(), RemotePort());
		return false;
	}

	m_WriteIndex += b;
	if (m_WriteIndex == m_WriteBytes) {
		m_WriteIndex = 0;
		m_WriteBytes = 0;
		if (m_Pending)
			Command(NULL);
		if (m_DeferClose)
			return false;
		Flushed();
	}
	return true;
}

bool cServerConnection::Respond(const char *Message, bool Last, ...) 
{
	char *buffer;
	int length;
	va_list ap;
	va_start(ap, Last);
	length = vasprintf(&buffer, Message, ap);
	va_end(ap);

	if (length < 0) {
		esyslog("ERROR: streamdev: buffer allocation failed (%s) for %s:%d",
				m_Protocol, RemoteIp().c_str(), RemotePort());
		return false;
	}

	if (m_WriteBytes + length + 2 > sizeof(m_WriteBuffer)) {
		esyslog("ERROR: streamdev: output buffer overflow (%s) for %s:%d", 
		        m_Protocol, RemoteIp().c_str(), RemotePort());
		free(buffer);
		return false;
	}
	Dprintf("OUT: |%s|\n", buffer);
	memcpy(m_WriteBuffer + m_WriteBytes, buffer, length);
	free(buffer);

	m_WriteBytes += length;
	m_WriteBuffer[m_WriteBytes++] = '\015';
	m_WriteBuffer[m_WriteBytes++] = '\012';
	m_Pending = !Last;
	return true;
}

#if APIVERSNUM >= 10700
static int GetClippedNumProvidedSystems(int AvailableBits, cDevice *Device)
{
  int MaxNumProvidedSystems = (1 << AvailableBits) - 1;
  int NumProvidedSystems = Device->NumProvidedSystems();
  if (NumProvidedSystems > MaxNumProvidedSystems) {
     esyslog("ERROR: device %d supports %d modulation systems but cDevice::GetDevice() currently only supports %d delivery systems which should be fixed", Device->CardIndex() + 1, NumProvidedSystems, MaxNumProvidedSystems);
     NumProvidedSystems = MaxNumProvidedSystems;
     }
  else if (NumProvidedSystems <= 0) {
     esyslog("ERROR: device %d reported an invalid number (%d) of supported delivery systems - assuming 1", Device->CardIndex() + 1, NumProvidedSystems);
     NumProvidedSystems = 1;
     }
  return NumProvidedSystems;
}
#endif

/*
 * copy of cDevice::GetDevice(...) but without side effects (not detaching receivers)
 */
cDevice* cServerConnection::CheckDevice(const cChannel *Channel, int Priority, bool LiveView, const cDevice *AvoidDevice)
{
  //cDevice *AvoidDevice = avoidDevice;
  //avoidDevice = NULL;
  // Collect the current priorities of all CAM slots that can decrypt the channel:
  int NumCamSlots = CamSlots.Count();
  int SlotPriority[NumCamSlots];
  int NumUsableSlots = 0;
  if (Channel->Ca() >= CA_ENCRYPTED_MIN) {
     for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) {
         SlotPriority[CamSlot->Index()] = MAXPRIORITY + 1; // assumes it can't be used
         if (CamSlot->ModuleStatus() == msReady) {
            if (CamSlot->ProvidesCa(Channel->Caids())) {
               if (!ChannelCamRelations.CamChecked(Channel->GetChannelID(), CamSlot->SlotNumber())) {
                  SlotPriority[CamSlot->Index()] = CamSlot->Priority();
                  NumUsableSlots++;
                  }
               }
            }
         }
     if (!NumUsableSlots)
        return NULL; // no CAM is able to decrypt this channel
     }

  bool NeedsDetachReceivers = false;
  cDevice *d = NULL;
  //cCamSlot *s = NULL;

  uint32_t Impact = 0xFFFFFFFF; // we're looking for a device with the least impact
  for (int j = 0; j < NumCamSlots || !NumUsableSlots; j++) {
      if (NumUsableSlots && SlotPriority[j] > MAXPRIORITY)
         continue; // there is no CAM available in this slot
      for (int i = 0; i < cDevice::NumDevices(); i++) {
          cDevice *device = cDevice::GetDevice(i);
          if (device == AvoidDevice)
             continue; // we've been asked to skip this device
          if (Channel->Ca() && Channel->Ca() <= CA_DVB_MAX && Channel->Ca() != device->CardIndex() + 1)
             continue; // a specific card was requested, but not this one
          if (NumUsableSlots && !CamSlots.Get(j)->Assign(device, true))
             continue; // CAM slot can't be used with this device
          bool ndr;
          if (device->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basicly able to do the job
             if (NumUsableSlots && device->CamSlot() && device->CamSlot() != CamSlots.Get(j))
                ndr = true; // using a different CAM slot requires detaching receivers
             // Put together an integer number that reflects the "impact" using
             // this device would have on the overall system. Each condition is represented
             // by one bit in the number (or several bits, if the condition is actually
             // a numeric value). The sequence in which the conditions are listed corresponds
             // to their individual severity, where the one listed first will make the most
             // difference, because it results in the most significant bit of the result.
             uint32_t imp = 0;
             imp <<= 1; imp |= LiveView ? !device->IsPrimaryDevice() || ndr : 0;                                  // prefer the primary device for live viewing if we don't need to detach existing receivers
             imp <<= 1; imp |= !device->Receiving() && (device != cTransferControl::ReceiverDevice() || device->IsPrimaryDevice()) || ndr; // use receiving devices if we don't need to detach existing receivers, but avoid primary device in local transfer mode
             imp <<= 1; imp |= device->Receiving();                                                               // avoid devices that are receiving
#if APIVERSNUM >= 10700
             imp <<= 4; imp |= GetClippedNumProvidedSystems(4, device) - 1;                                       // avoid cards which support multiple delivery systems
#endif
             imp <<= 1; imp |= device == cTransferControl::ReceiverDevice();                                      // avoid the Transfer Mode receiver device
             imp <<= 8; imp |= min(max(device->Priority() + MAXPRIORITY, 0), 0xFF);                               // use the device with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used)
             imp <<= 8; imp |= min(max((NumUsableSlots ? SlotPriority[j] : 0) + MAXPRIORITY, 0), 0xFF);              // use the CAM slot with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used)
             imp <<= 1; imp |= ndr;                                                                                  // avoid devices if we need to detach existing receivers
#if VDRVERSNUM < 10719
             imp <<= 1; imp |= device->IsPrimaryDevice();                                                         // avoid the primary device
#endif
             imp <<= 1; imp |= NumUsableSlots ? 0 : device->HasCi();                                              // avoid cards with Common Interface for FTA channels
#if VDRVERSNUM < 10719
             imp <<= 1; imp |= device->HasDecoder();                                                              // avoid full featured cards
#else
             imp <<= 1; imp |= device->AvoidRecording();                                                          // avoid SD full featured cards
#endif
             imp <<= 1; imp |= NumUsableSlots ? !ChannelCamRelations.CamDecrypt(Channel->GetChannelID(), j + 1) : 0; // prefer CAMs that are known to decrypt this channel
#if VDRVERSNUM >= 10719
             imp <<= 1; imp |= device->IsPrimaryDevice();                                                         // avoid the primary device
#endif
             if (imp < Impact) {
                // This device has less impact than any previous one, so we take it.
                Impact = imp;
                d = device;
                NeedsDetachReceivers = ndr;
                }
             }
          }
      if (!NumUsableSlots)
         break; // no CAM necessary, so just one loop over the devices
      }
  return d;
}

bool cServerConnection::UsedByLiveTV(cDevice *device)
{
	return device == cTransferControl::ReceiverDevice() ||
		(device->IsPrimaryDevice() && device->HasDecoder() && !device->Replaying());
}

cDevice *cServerConnection::GetDevice(const cChannel *Channel, int Priority) 
{
	// turn off the streams of this connection
	Detach();
	// This call may detach receivers of the device it returns
	cDevice *device = cDevice::GetDevice(Channel, Priority, false);

 	if (device && !device->IsTunedToTransponder(Channel)
			&& UsedByLiveTV(device)) {
		// now we would have to switch away live tv...let's see if live tv
		// can be handled by another device
		device = m_SwitchLive->Switch(device, Channel);
	}

	if (!device) {
		// can't switch - continue the current stream
		Attach();
		dsyslog("streamdev: GetDevice failed for channel %d (%s) at priority %d (PrimaryDevice=%d, ActualDevice=%d)", Channel->Number(), Channel->Name(), Priority, cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex());
	}
	return device;
}

bool cServerConnection::ProvidesChannel(const cChannel *Channel, int Priority) 
{
	cDevice *device = CheckDevice(Channel, Priority, false);
	if (!device || (StreamdevServerSetup.SuspendMode != smAlways
			&& !device->IsTunedToTransponder(Channel)
			&& UsedByLiveTV(device))) {
		// no device available or the device is in use for live TV and suspend mode doesn't allow us to switch it:
		// maybe a device would be free if THIS connection did turn off its streams?
		Detach();
		device = CheckDevice(Channel, Priority, false);
		Attach();
		if (device && StreamdevServerSetup.SuspendMode != smAlways
				&& !device->IsTunedToTransponder(Channel)
				&& UsedByLiveTV(device)) {
			// now we would have to switch away live tv...let's see if live tv
			// can be handled by another device
			const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel());
			cDevice *newdev = current ? CheckDevice(current, 0, true, device) : NULL;
			if (newdev) {
				dsyslog("streamdev: Providing channel %d (%s) at priority %d requires moving live TV to device %d (PrimaryDevice=%d, ActualDevice=%d)", Channel->Number(), Channel->Name(), Priority, newdev->CardIndex(), cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex());
			}
			else {
				device = NULL;
				dsyslog("streamdev: Not providing channel %d (%s) at priority %d - live TV not suspended", Channel->Number(), Channel->Name(), Priority);
			}
		}
		else if (!device)
			dsyslog("streamdev: No device provides channel %d (%s) at priority %d", Channel->Number(), Channel->Name(), Priority);
	}
	return device;
}

void cServerConnection::MainThreadHook()
{
	m_SwitchLive->Switch();
}