// cae_server.cpp
//
// Network server for caed(8).
//
//   (C) Copyright 2019-2021 Fred Gleason <fredg@paravelsystems.com>
//
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License version 2 as
//   published by the Free Software Foundation.
//
//   This program 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 this program; if not, write to the Free Software
//   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//

#include <ctype.h>
#include <stdio.h>

#include <qbytearray.h>
#include <qstringlist.h>

#include <rdapplication.h>

#include "cae_server.h"

//
// Uncomment this to send all protocol messages to syslog (DEBUG priority)
//
// #define __CAE_SERVER_LOG_PROTOCOL_MESSAGES

CaeServerConnection::CaeServerConnection(QTcpSocket *sock)
{
  socket=sock;
  authenticated=false;
  accum="";
  meter_port=0;
  for(int i=0;i<RD_MAX_CARDS;i++) {
    meters_enabled[i]=false;
  }
}


CaeServerConnection::~CaeServerConnection()
{
  socket->deleteLater();
}




CaeServer::CaeServer(RDConfig *config,QObject *parent)
  : QObject(parent)
{
  cae_config=config;

  cae_server=new QTcpServer(this);
  connect(cae_server,SIGNAL(newConnection()),this,SLOT(newConnectionData()));

  cae_ready_read_mapper=new QSignalMapper(this);
  connect(cae_ready_read_mapper,SIGNAL(mapped(int)),
	  this,SLOT(readyReadData(int)));

  cae_connection_closed_mapper=new QSignalMapper(this);
  connect(cae_connection_closed_mapper,SIGNAL(mapped(int)),
	  this,SLOT(connectionClosedData(int)));
}


QList<int> CaeServer::connectionIds() const
{
  QList<int> ret;

  for(QMap<int,CaeServerConnection *>::const_iterator it=
	cae_connections.begin();it!=cae_connections.end();it++) {
    ret.push_back(it.key());
  }

  return ret;
}


QHostAddress CaeServer::peerAddress(int id) const
{
  return cae_connections[id]->socket->peerAddress();
}


uint16_t CaeServer::peerPort(int id) const
{
  return cae_connections[id]->socket->peerPort();
}


uint16_t CaeServer::meterPort(int id) const
{
  return cae_connections[id]->meter_port;
}


void CaeServer::setMeterPort(int id,uint16_t port)
{
  cae_connections[id]->meter_port=port;
}


bool CaeServer::metersEnabled(int id,unsigned card) const
{
  return cae_connections[id]->meters_enabled[card];
}


void CaeServer::setMetersEnabled(int id,unsigned card,bool state)
{
  cae_connections[id]->meters_enabled[card]=state;
}


bool CaeServer::listen(const QHostAddress &addr,uint16_t port)
{
  return cae_server->listen(addr,port);
}


void CaeServer::sendCommand(const QString &cmd)
{
  for(QMap<int,CaeServerConnection *>::const_iterator it=
	cae_connections.begin();it!=cae_connections.end();it++) {
    if(it.value()->authenticated) {
      sendCommand(it.key(),cmd);
    }
  }
}


void CaeServer::sendCommand(int id,const QString &cmd)
{
#ifdef __CAE_SERVER_LOG_PROTOCOL_MESSAGES
  RDApplication::syslog(cae_config,LOG_DEBUG,
			"send[%d]: %s",id,(const char *)cmd.toUtf8());
#endif  // __CAE_SERVER_LOG_PROTOCOL_MESSAGES
  cae_connections.value(id)->socket->write(cmd.toUtf8());
}


void CaeServer::newConnectionData()
{
  QTcpSocket *sock=cae_server->nextPendingConnection();

  cae_connection_closed_mapper->setMapping(sock,sock->socketDescriptor());
  connect(sock,SIGNAL(disconnected()),cae_connection_closed_mapper,SLOT(map()));

  cae_ready_read_mapper->setMapping(sock,sock->socketDescriptor());
  connect(sock,SIGNAL(readyRead()),cae_ready_read_mapper,SLOT(map()));

  cae_connections[sock->socketDescriptor()]=new CaeServerConnection(sock);

  RDApplication::syslog(cae_config,LOG_DEBUG,
			"added connection %d [%s:%u]",sock->socketDescriptor(),
			sock->peerAddress().toString().toUtf8().constData(),
			0xFFFF&sock->peerPort());
}


void CaeServer::readyReadData(int id)
{
  QByteArray data=cae_connections.value(id)->socket->readAll();
  for(int i=0;i<data.size();i++) {
    char c=0xFF&data[i];
    switch(c) {
    case '!':
      if(ProcessCommand(id,cae_connections.value(id)->accum)) {
	return;
      }
      break;

    case 10:
    case 13:
      break;

    default:
      cae_connections.value(id)->accum+=c;
      break;
    }
  }
}


void CaeServer::connectionClosedData(int id)
{
  QString logmsg=
    QString::asprintf("removed connection %d [%s:%u]",
		      id,
		      peerAddress(id).toString().toUtf8().constData(),
		      0xFFFF&peerPort(id));
  int priority=LOG_DEBUG;
  if(!cae_connections.value(id)->authenticated) {
    logmsg=
      QString::asprintf("removed never authenticated connection %d [%s:%u]",
			id,
			peerAddress(id).toString().toUtf8().constData(),
			0xFFFF&peerPort(id));
    priority=LOG_WARNING;
  }
  emit connectionDropped(id);
  cae_connections.value(id)->socket->disconnect();
  delete cae_connections.value(id);
  cae_connections.remove(id);

  RDApplication::syslog(cae_config,priority,"%s",logmsg.toUtf8().constData());
}


bool CaeServer::ProcessCommand(int id,const QString &cmd)
{
  //  rda->syslog(LOG_NOTICE,"processing command: \"%s\"",cmd.toUtf8().constData());
  CaeServerConnection *conn=cae_connections.value(id);
  bool ok=false;
  QString cmdstr=cmd;
  QStringList f0=cmd.split(" ",QString::SkipEmptyParts);

  if(f0.size()==0) {
    return false;
  }
#ifdef __CAE_SERVER_LOG_PROTOCOL_MESSAGES
  RDApplication::syslog(cae_config,LOG_DEBUG,
			"recv[%d]: %s",id,(const char *)cmd.toUtf8());
#endif  // __CAE_SERVER_LOG_PROTOCOL_MESSAGES

  cae_connections.value(id)->accum="";

  //
  // Unpriviledged Commands
  //
  if(f0.at(0)=="DC") {
    connectionClosedData(id);
    return true;
  }

  if(f0.at(0)=="PW") {
    if((f0.size()==2)&&(f0.at(1)==cae_config->password())) {
      conn->authenticated=true;
      sendCommand(id,"PW +!");
      RDApplication::syslog(cae_config,LOG_DEBUG,
			    "PASSED authentication: connection %d [%s:%u]",
			    id,
			    peerAddress(id).toString().toUtf8().constData(),
			    0xFFFF&peerPort(id));
    }
    else {
      conn->authenticated=false;
      sendCommand(id,"PW -!");
      RDApplication::syslog(cae_config,LOG_WARNING,
			    "FAILED authentication: connection %d [%s:%u]",
			    id,
			    peerAddress(id).toString().toUtf8().constData(),
			    0xFFFF&peerPort(id));
      connectionClosedData(id);
      return true;
    }
    return false;
  }  

  //
  // Priviledged Commands
  // Authentication required to execute these!
  //
  if(!conn->authenticated) {
    RDApplication::syslog(cae_config,LOG_WARNING,
			  "unauthenticated connection %d [%s:%u] sent command \"%s\"",
			  id,
			  peerAddress(id).toString().toUtf8().constData(),
			  0xFFFF&peerPort(id),
			  cmdstr.toUtf8().constData());
    connectionClosedData(id);
    return true;
  }
  bool was_processed=false;

  if((f0.at(0)=="LP")&&(f0.size()==3)) {  // Load Playback
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      emit loadPlaybackReq(id,card,f0.at(2));
      was_processed=true;
    }
  }
  if((f0.at(0)=="UP")&&(f0.size()==2)) {  // Unload Playback
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok) {
      emit unloadPlaybackReq(id,card);
      was_processed=true;
    }
  }
  if((f0.at(0)=="PP")&&(f0.size()==3)) {  // Play Position
    unsigned handle=f0.at(1).toUInt(&ok);
    if(ok) {
      unsigned pos=f0.at(2).toUInt(&ok);
      if(ok) {
	emit playPositionReq(id,handle,pos);
	was_processed=true;
      }
    }
  }
  if((f0.at(0)=="PY")&&(f0.size()==5)) {  // Play
    unsigned handle=f0.at(1).toUInt(&ok);
    if(ok) {
      unsigned len=f0.at(2).toUInt(&ok);
      if(ok) {
	unsigned speed=f0.at(3).toUInt(&ok);
	if(ok) {
	  unsigned pitch=f0.at(4).toUInt(&ok);
	  if(ok) {
	    emit playReq(id,handle,len,speed,pitch);
	    was_processed=true;
	  }
	}
      }
    }
  }
  if((f0.at(0)=="SP")&&(f0.size()==2)) {  // Stop Playback
    unsigned handle=f0.at(1).toUInt(&ok);
    if(ok) {
      emit stopPlaybackReq(id,handle);
      was_processed=true;
    }
  }
  if((f0.at(0)=="TS")&&(f0.size()==2)) {  // Timescaling Support
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      emit timescalingSupportReq(id,card);
      was_processed=true;
    }
  }
  if((f0.at(0)=="LR")&&(f0.size()==8)) {  // Load Recording
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned port=f0.at(2).toUInt(&ok);
      if(ok&&(port<RD_MAX_PORTS)) {
	unsigned coding=f0.at(3).toUInt(&ok);
	if(ok&&(coding<5)) {
	  unsigned chans=f0.at(4).toUInt(&ok);
	  if(ok&&(chans<=2)) {
	    unsigned samprate=f0.at(5).toUInt(&ok);
	    if(ok) {
	      unsigned bitrate=f0.at(6).toUInt(&ok);
	      if(ok) {
		emit loadRecordingReq(id,card,port,coding,chans,samprate,
				      bitrate,f0.at(7));
		was_processed=true;
	      }
	    }
	  }
	}
      }
    }
  }
  if((f0.at(0)=="UR")&&(f0.size()==3)) {  // Unload Recording
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned stream=f0.at(2).toUInt(&ok);
      if(ok&&(stream<RD_MAX_STREAMS)) {
	emit unloadRecordingReq(id,card,stream);
	was_processed=true;
      }
    }
  }
  if((f0.at(0)=="RD")&&(f0.size()==5)) {  // Record
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned stream=f0.at(2).toUInt(&ok);
      if(ok&&(stream<RD_MAX_STREAMS)) {
	if(ok) {
	  unsigned len=f0.at(3).toUInt(&ok);
	  if(ok) {
	    int thres=f0.at(4).toInt(&ok);
	    if(ok) {
	      emit recordReq(id,card,stream,len,thres);
	      was_processed=true;
	    }
	  }
	}
      }
    }
  }
  if((f0.at(0)=="SR")&&(f0.size()==3)) {  // Stop Recording
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned stream=f0.at(2).toUInt(&ok);
      if(ok&&(stream<RD_MAX_STREAMS)) {
	if(ok) {
	  emit stopRecordingReq(id,card,stream);
	  was_processed=true;
	}
      }
    }
  }
  if((f0.at(0)=="IV")&&(f0.size()==4)) {  // Set Input Volume
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned stream=f0.at(2).toUInt(&ok);
      if(ok&&(stream<RD_MAX_STREAMS)) {
	if(ok) {
	  int level=f0.at(3).toInt(&ok);
	  if(ok) {
	    emit setInputVolumeReq(id,card,stream,level);
	    was_processed=true;
	  }
	}
      }
    }
  }
  if((f0.at(0)=="OV")&&(f0.size()==5)) {  // Set Output Volume
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned stream=f0.at(2).toUInt(&ok);
      if(ok&&(stream<RD_MAX_STREAMS)) {
	if(ok) {
	  unsigned port=f0.at(3).toUInt(&ok);
	  if(ok&&(port<RD_MAX_PORTS)) {
	    int level=f0.at(4).toInt(&ok);
	    if(ok) {
	      emit setOutputVolumeReq(id,card,stream,port,level);
	      was_processed=true;
	    }
	  }
	}
      }
    }
  }
  if((f0.at(0)=="FV")&&(f0.size()==6)) {  // Fade Output Volume
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned stream=f0.at(2).toUInt(&ok);
      if(ok&&(stream<RD_MAX_STREAMS)) {
	if(ok) {
	  unsigned port=f0.at(3).toUInt(&ok);
	  if(ok&&(port<RD_MAX_PORTS)) {
	    int level=f0.at(4).toInt(&ok);
	    if(ok) {
	      int len=f0.at(5).toUInt(&ok);
	      if(ok) {
		emit fadeOutputVolumeReq(id,card,stream,port,level,len);
		was_processed=true;
	      }
	    }
	  }
	}
      }
    }
  }
  if((f0.at(0)=="IL")&&(f0.size()==4)) {  // Set Input Level
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned port=f0.at(2).toUInt(&ok);
      if(ok&&(port<RD_MAX_PORTS)) {
	int level=f0.at(3).toInt(&ok);
	if(ok) {
	  emit setInputLevelReq(id,card,port,level);
	  was_processed=true;
	}
      }
    }
  }
  if((f0.at(0)=="OL")&&(f0.size()==4)) {  // Set Output Level
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned port=f0.at(2).toUInt(&ok);
      if(ok&&(port<RD_MAX_PORTS)) {
	int level=f0.at(3).toInt(&ok);
	if(ok) {
	  emit setOutputLevelReq(id,card,port,level);
	  was_processed=true;
	}
      }
    }
  }
  if((f0.at(0)=="IM")&&(f0.size()==4)) {  // Set Input Mode
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned port=f0.at(2).toUInt(&ok);
      if(ok&&(port<RD_MAX_PORTS)) {
	unsigned mode=f0.at(3).toUInt(&ok);
	if(ok&&(mode<=3)) {
	  emit setInputModeReq(id,card,port,mode);
	  was_processed=true;
	}
      }
    }
  }
  if((f0.at(0)=="OM")&&(f0.size()==4)) {  // Set Output Mode
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned port=f0.at(2).toUInt(&ok);
      if(ok&&(port<RD_MAX_PORTS)) {
	unsigned mode=f0.at(3).toUInt(&ok);
	if(ok&&(mode<=3)) {
	  emit setOutputModeReq(id,card,port,mode);
	  was_processed=true;
	}
      }
    }
  }
  if((f0.at(0)=="IX")&&(f0.size()==4)) {  // Set Input Vox Level
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned stream=f0.at(2).toUInt(&ok);
      if(ok&&(stream<RD_MAX_STREAMS)) {
	int level=f0.at(3).toInt(&ok);
	if(ok) {
	  emit setInputVoxLevelReq(id,card,stream,level);
	  was_processed=true;
	}
      }
    }
  }
  if((f0.at(0)=="IT")&&(f0.size()==4)) {  // Set Input Type
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned port=f0.at(2).toUInt(&ok);
      if(ok&&(port<RD_MAX_PORTS)) {
	int type=f0.at(3).toInt(&ok);
	if(ok&&(type<=1)) {
	  emit setInputTypeReq(id,card,port,type);
	  was_processed=true;
	}
      }
    }
  }
  if((f0.at(0)=="IS")&&(f0.size()==3)) {  // Get Input Status
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned port=f0.at(2).toUInt(&ok);
      if(ok&&(port<RD_MAX_PORTS)) {
	emit getInputStatusReq(id,card,port);
	was_processed=true;
      }
    }
  }
  if((f0.at(0)=="AL")&&(f0.size()==5)) {  // Set Audio Passthrough Level
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned input=f0.at(2).toUInt(&ok);
      if(ok&&(input<RD_MAX_PORTS)) {
	unsigned output=f0.at(3).toUInt(&ok);
	if(ok&&(output<RD_MAX_PORTS)) {
	  int level=f0.at(4).toInt(&ok);
	  if(ok) {
	    emit setAudioPassthroughLevelReq(id,card,input,output,level);
	    was_processed=true;
	  }
	}
      }
    }
  }
  if((f0.at(0)=="CS")&&(f0.size()==3)) {  // Set Clock Source
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned input=f0.at(2).toUInt(&ok);
      if(ok&&(input<RD_MAX_PORTS)) {
	emit setClockSourceReq(id,card,input);
	was_processed=true;
      }
    }
  }
  if((f0.at(0)=="OS")&&(f0.size()==5)) {  // Set Output Status Flag
    unsigned card=f0.at(1).toUInt(&ok);
    if(ok&&(card<RD_MAX_CARDS)) {
      unsigned port=f0.at(2).toUInt(&ok);
      if(ok&&(port<RD_MAX_PORTS)) {
	unsigned stream=f0.at(3).toUInt(&ok);
	if(ok&&(stream<RD_MAX_STREAMS)) {
	  emit setOutputStatusFlagReq(id,card,port,stream,f0.at(4)=="1");
	  was_processed=true;
	}
      }
    }
  }

  if((f0.at(0)=="ME")&&(f0.size()>=3)) {  // Meter Enable
    uint16_t udp_port=0xFFFF&f0.at(1).toUInt(&ok);
    if(ok) {
      QList<unsigned> cards;
      for(int i=2;i<f0.size();i++) {
	cards.push_back(f0.at(i).toUInt());
      }
      emit meterEnableReq(id,udp_port,cards);
      was_processed=true;
    }
  }

  if(!was_processed) {  // Send generic error response
    sendCommand(id,f0.join(" ")+"-!");
    RDApplication::syslog(cae_config,LOG_WARNING,
			  "connection %d [%s:%u] sent unrecognized command \"%s\"",
			  id,
			  peerAddress(id).toString().toUtf8().constData(),
			  0xFFFF&peerPort(id),
			  cmdstr.toUtf8().constData());
  }

  return false;
}
