/*
 * iaxphone.c: A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id$
 */

#include <vdr/plugin.h>
#include <math.h>
#include "iaxclient.h"
#include "i18n.h"
#include "statusmenu.h"


static const char *VERSION        = "0.0.4";
static const char *DESCRIPTION    = "Place voip phone calls";
static const char *MAINMENUENTRY  = "Iax Phone";

//------------------------------------------------------------------------------

static const char *CodecNames[] = {
                                  "U-Law",
                                  "A-Law",
                                  "GSM",
                                  "Speex",
                                  "iLBC"
                                  };
                                
static const int Formats[] =      {
                                  IAXC_FORMAT_ULAW,
                                  IAXC_FORMAT_ALAW,
                                  IAXC_FORMAT_GSM,
                                  IAXC_FORMAT_SPEEX,
                                  IAXC_FORMAT_ILBC
                                  };      
                             
#ifdef CODEC_ILBC
#define FORMATS 5
#else
#define FORMATS 4
#endif
                                                                    
//------------------------------------------------------------------------------
//plugin parameters
static char iaxserver[256]={0};
static char username[256]={0};
static char password[256]={0};
static char defprefix[256]={0};
static char callerid[256]={0};
static char callername[256]={0};
static char *indev=NULL;
static char *outdev=NULL;
static char *ringdev=NULL;
static int preferred_codec=2; //IAXC_FORMAT_GSM;
static int doregister=0;
static int accepted_codec[FORMATS];
//echo suppression currently doesn't work causing an immediate crash
static int filters=IAXC_FILTER_DENOISE|IAXC_FILTER_AGC| /*IAXC_FILTER_ECHO| */ IAXC_FILTER_AAGC|IAXC_FILTER_CN;
static int showlevels=0;

//------------------------------------------------------------------------------
//iaxclient state
static struct {
  bool free;
  bool outgoing;
  bool ringing;
  bool complete;
  bool selected;
  } call;
static char remote_number[256];
static char remote_name[256];
static int format;
static int registered=0;
static char states[256];	/* buffer to hold ascii states */
static int regid=-1;
static time_t callstart;
static bool started=false;

static double outlevel;
static double miclevel;
static double spkrlevel;

class cMainMenuIaxphone;
static cMainMenuIaxphone *mainmenu=NULL;

class cRinger;
static cRinger *ringer=NULL;

static cMutex mutex;

//--- cRinger --------------------------------------------------------------

class cRinger : public cThread {
protected:
  void Action(void);
public:
  cRinger(void);  
  ~cRinger(void);
  void Cancel(void) { cThread::Cancel();}
};

cRinger::cRinger(void):cThread("iaxphone ringer")
{
}

cRinger::~cRinger(void)
{
  Cancel();
}

void cRinger::Action(void)
{
  while(true) {
    SystemExec("iaxphone-ring.sh");
    cCondWait::SleepMs(1000);
  }
}
//--------------- cTone ------------------------------------------------------------

class cTone
{
private:
  struct iaxc_sound tone;
public:
  cTone(int f1, int f2, int len, int duration, int repeat);
  ~cTone();
  void Start(void);
  void Stop(void);
};  

cTone::cTone(int f1, int f2, int len, int duration, int repeat)
{
  
#define M_PI 3.14159265358979323846  

   memset(&tone, 0, sizeof(tone));

   tone.len  = len;
   tone.data = (short *)calloc(tone.len , sizeof(short));

   for( int i=0;i < duration; i++ )
   {
       tone.data[i] = (short)(0x7fff*0.4*sin((double)i*f1*M_PI/8000))
                    + (short)(0x7fff*0.4*sin((double)i*f2*M_PI/8000));
   }

   tone.repeat = repeat;
}

cTone::~cTone()
{
  if(tone.data != NULL) free(tone.data);
}

void cTone::Start(void)
{
  iaxc_play_sound(&tone, 0);
}

void cTone::Stop()
{
  iaxc_stop_sound(tone.id);
}

static cTone RingBack(440,480,48000,16000,-1);
static cTone DtmfFeedback(1000,1000,400,400,1);

//-------------------------------------------------------------------------------------- 
// called at plugin initialization and each time the parameters are changed

void iaxc_settings(void)
{

  int codec=IAXC_FORMAT_GSM;
  if (preferred_codec<FORMATS) codec=Formats[preferred_codec];
  int accepted=codec;
  for (int i=0; i<FORMATS; i++) if (accepted_codec[i]) accepted |= Formats[i];
  //printf("*** preferred %x  accepted %x\n",codec,accepted);
  iaxc_set_formats(codec, accepted);

  iaxc_set_filters(filters);
  
  //audio device setting ripped from iaxcomm
  struct iaxc_audio_device *devices;
  int nDevs;
  int i;
  int input=0;
  int output=0;
  int ring=0;
  
  // Note that if we're relying on an invalid devicename, the deviceID
  // stays 0, which equals default.

  iaxc_audio_devices_get(&devices, &nDevs, &input, &output, &ring);

  for(i=0; i<nDevs; i++) {
      printf("iaxphone available device %d: %s, capabilities: %s%s%s\n",devices->devID, devices->name,
                (devices->capabilities & IAXC_AD_INPUT) != 0 ? "input " : "", 
                (devices->capabilities & IAXC_AD_OUTPUT) != 0 ? "output " : "", 
                (devices->capabilities & IAXC_AD_RING) != 0 ? "ring " : ""); 
  
      if(indev && (devices->capabilities & IAXC_AD_INPUT)) {
          if(strcmp(indev,devices->name) == 0)
              input = devices->devID;
      }

      if(outdev && (devices->capabilities & IAXC_AD_OUTPUT)) {
          if(strcmp(outdev,devices->name) == 0)
              output = devices->devID;
      }

      if(ringdev && (devices->capabilities & IAXC_AD_RING)) {
          if(strcmp(ringdev,devices->name) == 0)
              ring = devices->devID;
      }
      devices++;
  }
  iaxc_audio_devices_set(input,output,ring);
  printf("setting audio devices in %d out %d ring %d\n",input,output,ring);
  
  iaxc_set_callerid(callerid,callername);
  
  if (regid>=0) {
    printf("unregistering id %d\n",regid);
    iaxc_unregister(regid);
    regid=-1;
    registered=0;
  };
  if (doregister) {
    regid=iaxc_register(username, password, iaxserver);
    printf("registering new id %d\n",regid);
  }
  
}

//----------------------------------------------------------------------------------

cOsdItem *Category(const char *Title)
{
  char *buffer = NULL;
  asprintf(&buffer, "--- %s -------------------------------------------------"
                    "---------------", Title );
  cOsdItem *item = new cOsdItem(buffer);
  free(buffer);
  item->SetSelectable(false);
  return item;
}
  
// --- cMenuSetupIaxphone ---------------------------------------------------------

class cMenuSetupIaxphone : public cMenuSetupPage {
private:
  char allowed[256]; 
  char newiaxserver[256];
  char newusername[256];
  char newpassword[256];
  char newdefprefix[256];
  char newcallerid[256];
  char newcallername[256];
  char *InputDevices[256];
  char *OutputDevices[256];
  char *RingDevices[256];
  int selindev;
  int seloutdev;
  int selringdev;
  int newpreferred_codec;
  int newdoregister;
  int filter_denoise;
  int filter_agc;
  int filter_echo;
  int filter_aagc;
  int filter_cn;
  int newshowlevels;
  int newaccepted_codec[FORMATS];
  
protected:
  virtual void Store(void);
public:
  cMenuSetupIaxphone(void);
  ~cMenuSetupIaxphone();  
};

cMenuSetupIaxphone::cMenuSetupIaxphone(void)
{
  int i;
  for(i=0; i<255 ; i++) allowed[i]=i+1;
  allowed[255]=0;
  strn0cpy(newiaxserver,iaxserver,sizeof(newiaxserver));
  strn0cpy(newusername,username,sizeof(newusername));
  strn0cpy(newpassword,password,sizeof(newpassword));
  strn0cpy(newdefprefix,defprefix,sizeof(newdefprefix));
  strn0cpy(newcallerid,callerid,sizeof(newcallerid));
  strn0cpy(newcallername,callername,sizeof(newcallername));
  newpreferred_codec=preferred_codec;
  for(i=0; i<FORMATS; i++) newaccepted_codec[i]=accepted_codec[i];
  newdoregister=doregister;
  filter_denoise= ((filters & IAXC_FILTER_DENOISE) != 0);
  filter_agc=((filters & IAXC_FILTER_AGC) != 0);
  filter_echo=((filters & IAXC_FILTER_ECHO) != 0);
  filter_aagc=((filters & IAXC_FILTER_AAGC) != 0);
  filter_cn=((filters & IAXC_FILTER_CN) != 0);
  newshowlevels=showlevels;
  
  struct iaxc_audio_device *devices;
  int nDevs;
  int input=0;
  int output=0;
  int ring=0;
  
  for (i=0; i<256; i++) {
    InputDevices[i]=NULL;
    OutputDevices[i]=NULL;
    RingDevices[i]=NULL;
  }
  int numoutdevs=0;
  int numindevs=0;
  int numringdevs=0;
  selindev=0;
  seloutdev=0;
  selringdev=0;
  
  iaxc_audio_devices_get(&devices, &nDevs, &input, &output, &ring);

  for(i=0; i<nDevs; i++) {
      if(devices->capabilities & IAXC_AD_INPUT) {
          if (numindevs<256) {
            InputDevices[numindevs]=strdup(devices->name);
            if(indev) if (strcmp(indev,devices->name) == 0) selindev=numindevs;
            numindevs++;
          }      
      }

      if(devices->capabilities & IAXC_AD_OUTPUT) {
          if (numoutdevs<256) {
            OutputDevices[numoutdevs]=strdup(devices->name);
            if(outdev) if (strcmp(outdev,devices->name) == 0) seloutdev=numoutdevs;
            numoutdevs++;
          }      
      }

      if(devices->capabilities & IAXC_AD_RING) {
          if (numringdevs<256) {
            RingDevices[numringdevs]=strdup(devices->name);
            if(ringdev) if (strcmp(ringdev,devices->name) == 0) selringdev=numringdevs;
            numringdevs++;
          }      
      }
      devices++;
  }

  Add(Category(tr("Server settings")));
  Add(new cMenuEditStrItem(tr("iax server"),newiaxserver,sizeof(newiaxserver),allowed));
  Add(new cMenuEditStrItem(tr("username"),newusername,sizeof(newusername),allowed));
  Add(new cMenuEditStrItem(tr("password"),newpassword,sizeof(newpassword),allowed));
  Add(new cMenuEditBoolItem(tr("register"),&newdoregister));
  Add(new cMenuEditNumItem(tr("caller id"),newcallerid,sizeof(newcallerid)));
  Add(new cMenuEditStrItem(tr("caller name"),newcallername,sizeof(newcallername),allowed));
  Add(Category(tr("Audio settings")));
  Add(new cMenuEditStraItem(tr("input device"),&selindev,numindevs,InputDevices));
  Add(new cMenuEditStraItem(tr("output device"),&seloutdev,numoutdevs,OutputDevices));
  Add(new cMenuEditStraItem(tr("ring device"),&selringdev,numringdevs,RingDevices));
  for (int i=0; i<FORMATS; i++)
    Add(new cMenuEditBoolItem(CodecNames[i],&newaccepted_codec[i]));
  Add(new cMenuEditStraItem(tr("preferred codec"),&newpreferred_codec, FORMATS, CodecNames));
  Add(Category(tr("Filters")));
  Add(new cMenuEditBoolItem(tr("denoise"), &filter_denoise));
  Add(new cMenuEditBoolItem(tr("agc"), &filter_agc));
  Add(new cMenuEditBoolItem(tr("analog agc"), &filter_aagc));
  Add(new cMenuEditBoolItem(tr("echo suppression"), &filter_echo));
  Add(new cMenuEditBoolItem(tr("add comfort noise"), &filter_cn));
  Add(Category(tr("Plugin")));
  Add(new cMenuEditBoolItem(tr("show levels"), &newshowlevels));
  Add(new cMenuEditNumItem(tr("default prefix"), newdefprefix,sizeof(newdefprefix)));
  SetCurrent(Get(1));
}  

cMenuSetupIaxphone::~cMenuSetupIaxphone()
{
  for (int i=0; i<256; i++) {
    free(InputDevices[i]);
    free(OutputDevices[i]);
    free(RingDevices[i]);
  }
}

void cMenuSetupIaxphone::Store(void)
{
  int i;
  strn0cpy(iaxserver,newiaxserver,sizeof(iaxserver));
  strn0cpy(username,newusername,sizeof(username));
  strn0cpy(password,newpassword,sizeof(password));
  strn0cpy(defprefix,newdefprefix,sizeof(defprefix));
  strn0cpy(callerid,newcallerid,sizeof(callerid));
  strn0cpy(callername,newcallername,sizeof(callername));
  preferred_codec=newpreferred_codec;
  doregister=newdoregister;
  filters=0;
  if(filter_denoise) filters|=IAXC_FILTER_DENOISE;
  if(filter_agc)     filters|=IAXC_FILTER_AGC;
  if(filter_aagc)    filters|=IAXC_FILTER_AAGC;
  if(filter_echo)    filters|=IAXC_FILTER_ECHO;
  if(filter_cn)      filters|=IAXC_FILTER_CN;
  showlevels=newshowlevels;
  free(indev); indev=strdup(InputDevices[selindev]);
  free(outdev); outdev=strdup(OutputDevices[seloutdev]);
  free(ringdev); ringdev=strdup(RingDevices[selringdev]);
  SetupStore("IaxServer",iaxserver);
  SetupStore("Username",username);
  SetupStore("Password",password);
  SetupStore("CallerId",callerid);
  SetupStore("CallerName",callername);
  SetupStore("DefaultPrefix",defprefix);
  SetupStore("PreferredCodec",preferred_codec);
  SetupStore("DoRegister",doregister);
  SetupStore("Filters",filters);
  SetupStore("ShowLevels",showlevels);
  SetupStore("InputDevice",indev);
  SetupStore("OutputDevice",outdev);
  SetupStore("RingDevice",ringdev);
  for (i=0; i<FORMATS; i++) {
    accepted_codec[i]=newaccepted_codec[i];
    SetupStore(CodecNames[i],accepted_codec[i]);
  }    
  iaxc_settings();
  
}

// --- cPhoneBookEntry ----------------------------------------------------------
class cPhoneBookEntry : public cListObject {
private:
  char *name;
  char *number;
public:
  cPhoneBookEntry(void) { name=NULL; number=NULL; };
  cPhoneBookEntry(const char *Name, const char *Number);
  ~cPhoneBookEntry();
  const char *GetName(void) { return name; };
  const char *GetNumber(void) { return number; };
  bool Parse(const char *s);
};

cPhoneBookEntry::cPhoneBookEntry(const char* Name, const char *Number)
{
  name=strdup(Name);
  number=strdup(Number);
}  

cPhoneBookEntry::~cPhoneBookEntry()
{
  free(name);
  free(number);
}

bool cPhoneBookEntry::Parse(const char *s)
{
  bool result = false;
  char *namebuf = NULL;
  char *numbuf = NULL;
  if (2 == sscanf(s, "%a[^:]:%as", &namebuf, &numbuf)) {
    free(name);
    name=strdup(namebuf);
    free(number);
    number=strdup(numbuf);
    result=true;
  }
  free(namebuf);
  free(numbuf);
  return result;
}    

// --- cPhoneBook ----------------------------------------------------------------

class cPhoneBook : public cConfig<cPhoneBookEntry> {};

                          
// --- cMainMenuIaxphone ---------------------------------------------------------

#define VOLUME_ENTRY 0
#define NUMBER_ENTRY 1

class cMainMenuIaxphone : public cStatusMenu {
private:
  char number[256];
  const char *hanguphelp;
  const char *callhelp;
  const char *answerhelp;
  const char *rejecthelp;
  cOsdItem *volumeline;
  cOsdItem *numberline;
  cPhoneBook *PhoneBook;
  time_t lastmessage;
  void ReplaceNumber(void);
  void ShowVolume(void);
public:
  cMainMenuIaxphone(void);
  ~cMainMenuIaxphone();
  void ShowStatus(void);
  virtual void Show(void);
  virtual eOSState ProcessKey(eKeys Key);
  void ShowTextEvent(const char *s) { SetStatus(s); lastmessage=time(NULL);}
};

cMainMenuIaxphone::cMainMenuIaxphone(void):cStatusMenu(tr(MAINMENUENTRY),20)
{
  hanguphelp=tr("Hang-up");
  callhelp=tr("Call");
  answerhelp=tr("Answer");
  rejecthelp=tr("Reject");
  outlevel=iaxc_output_level_get();
  SetReserved(showlevels ? 6 : 4);
  Add(volumeline=new cOsdItem(""));
  ShowVolume();
  strn0cpy(number,defprefix,sizeof(number));
  Add(numberline=new cMenuEditNumItem(tr("Enter number"),number,sizeof(number)));
  PhoneBook=new cPhoneBook;
  PhoneBook->Load(AddDirectory(cPlugin::ConfigDirectory(), "iaxphone.conf"));
  for (cPhoneBookEntry *p=PhoneBook->First(); p ; p=PhoneBook->Next(p)) Add(new cOsdItem(p->GetName()));
  mutex.Lock();
  SetCurrent(call.complete || call.outgoing ? volumeline : numberline);
  mutex.Unlock();
  lastmessage=0;
}

cMainMenuIaxphone::~cMainMenuIaxphone()
{
mutex.Lock();
mainmenu=NULL;
mutex.Unlock();
delete PhoneBook;
}

#define HISTLEN 50
static char histstring[HISTLEN+1];
const char *hist(double value, double min, double max)
{
   int v=int((value-min)/(max-min)*HISTLEN);
   if (v<0) v=0;
   for (int i=0; i<v; i++) histstring[i]='|';
   for (int i=v; i<HISTLEN; i++) histstring[i]=' ';
   histstring[HISTLEN]=0;
   return histstring;
}

void cMainMenuIaxphone::ShowStatus(void)
{
  char buf[100];
  // First status line: registration state
  switch(registered) {
    case 0: DisplayMenu()->SetItem(tr("Not registered"),0,false,false); break;
    case IAXC_REGISTRATION_REPLY_ACK: DisplayMenu()->SetItem(tr("Registered"),0,false,false); break;
    case IAXC_REGISTRATION_REPLY_REJ: DisplayMenu()->SetItem(tr("Registration failed"),0,false,false); break;
    case IAXC_REGISTRATION_REPLY_TIMEOUT: DisplayMenu()->SetItem(tr("Registration timeout"),0,false,false); break;
    default: DisplayMenu()->SetItem(tr("Unknown registration code"),0,false,false); break;
  }
  
  // Second status line: call state
  buf[0]=0;
  if (call.free) { 
    snprintf(buf,sizeof(buf),tr("Free"));
    if (strcmp(number,defprefix)) SetHelp(" ",callhelp,"*","#"); else SetHelp(" "," ","*","#");
  }  
  else if (call.complete) {
    snprintf(buf,sizeof(buf),"%s %s <%s>",tr("Connected with"),remote_number, remote_name);
    SetHelp(hanguphelp," ","*","#");
  }  
  else if (call.outgoing) {
    snprintf(buf,sizeof(buf),"%s %s <%s>",tr("Calling"), remote_number, remote_name);
    SetHelp(hanguphelp," ","*","#");
  }  
  else if (call.ringing) {
    snprintf(buf,sizeof(buf),"%s %s <%s>",tr("Incoming call from"),remote_number,remote_name);
    SetHelp(rejecthelp,answerhelp,"*","#");
  }  
  DisplayMenu()->SetItem(buf,1,false,false);
  
  // Third status line: call format and duration
  if (call.complete) {
    int t=time(NULL)-callstart;
    char *s_format;
    switch(format) {
      case(IAXC_FORMAT_ULAW):   s_format="U-Law"; break;
      case(IAXC_FORMAT_ALAW):   s_format="A-Law"; break;
      case(IAXC_FORMAT_GSM):    s_format="GSM";   break;
      case(IAXC_FORMAT_SPEEX):  s_format="Speex"; break;
      case(IAXC_FORMAT_ILBC):   s_format="iLBC";  break;
      default:                  s_format="";  
    }
    snprintf(buf,sizeof(buf),"[%s]\t%d:%2.2d:%2.2d", s_format, t/3600, t/ 60 % 60, t % 60);
  } else buf[0]=0;
  DisplayMenu()->SetItem(buf,2,false,false);
  
  //Mic and audio levels  
  if (showlevels) {
    snprintf(buf, sizeof(buf), "%s\t[%s]", tr("Microphone"), hist(miclevel,-99,0));
    DisplayMenu()->SetItem(buf,3,false,false);
    snprintf(buf, sizeof(buf), "%s\t[%s]", tr("Audio"), hist(spkrlevel,-99,0));
    DisplayMenu()->SetItem(buf,4,false,false);
   
  }
  
  //Separator
  DisplayMenu()->SetItem("--------------------------------------------------------------------------------------------------",
     showlevels ? 5 : 3,false,false);
     
  Skins.Flush();
}

void cMainMenuIaxphone::ShowVolume(void)
{
  char buf[100];
  snprintf(buf, sizeof(buf), "%s\t[%s]", tr("Volume"), hist(outlevel,0,1));
  volumeline->SetText(buf);
}

void cMainMenuIaxphone::ReplaceNumber(void)
{
  int oldcurrent=Current(); //to keep it the same after Del+Add if it's the last entry
  Del(NUMBER_ENTRY);
  Add(new cMenuEditNumItem(tr("Enter number"),number,sizeof(number)),false,volumeline);
  SetCurrent(Get(oldcurrent));
}

void cMainMenuIaxphone::Show(void)
{
  mutex.Lock();
  cStatusMenu::Show();
  mainmenu=this;
  Display();
  ShowStatus();
  mutex.Unlock();
}

eOSState cMainMenuIaxphone::ProcessKey(eKeys Key)
{
  mutex.Lock();
  if (lastmessage) if (time(NULL)-lastmessage>=3) {
    SetStatus(NULL);
    lastmessage=0;
  }
  mutex.Unlock();
  eOSState state = osUnknown;
  if (Current() != VOLUME_ENTRY || (NORMALKEY(Key)!=kLeft && NORMALKEY(Key)!=kRight)) state=cStatusMenu::ProcessKey(Key);
  Key=NORMALKEY(Key);
  if (state==osUnknown || (Current() == NUMBER_ENTRY && Key>=k0 && Key<=k9))
  {
    state=osContinue;
    switch(Key) {
      case kRed :
        if (call.ringing && !call.outgoing) iaxc_reject_call();
        else if (!call.free) iaxc_dump_call();
        break;
         
      case kGreen :
        if (call.free && strcmp(number,defprefix)) {
          char *LocNumber=strdup(number);
          char *Extension=LocNumber;
          char *AccountInfo=NULL;
          char *Username=NULL;
          char *Password=NULL;
          char *Host=NULL;
          char *pos=strrchr(LocNumber,'/');
          if (pos) {
            AccountInfo=LocNumber;
            *pos=0;
            Extension=pos+1;
            if(pos==LocNumber) AccountInfo=NULL; //empty AccountInfo
          }
          if(AccountInfo) {
            Host=AccountInfo;
            pos=strrchr(Host,'@');
            if (pos) {
              *pos=0;
              Username=Host;
              Host=pos+1;
              pos=strrchr(Username,':');
              if (pos) {
                *pos=0;
                Password=pos+1;
              }
            } else {
              Host=AccountInfo;
            }
          } else { //just the extension, use the defaults
            Username=username;
            Password=password;
            Host=iaxserver;
          }
          char *buf=NULL;
          asprintf(&buf,"%s:%s@%s/%s", Username ? Username : "", Password ? Password : "", Host, Extension[0] ? Extension : "s");
          printf("buf: %s\n",buf);
          printf("calling %s:XXX@%s/%s\n", Username ? Username : "", Host, Extension[0] ? Extension: "s");
          iaxc_call(buf);
          iaxc_select_call(0);
          SetCurrent(volumeline);
          free(buf);
          free(LocNumber);
        }
        else if (call.ringing && !call.outgoing) {   
          iaxc_answer_call(0);
          iaxc_select_call(0);
          SetCurrent(volumeline);
        }
        break;
     case kLeft: 
     case kRight: if (Current()!=VOLUME_ENTRY) break;
     case kVolUp:
     case kVolDn:
      if (Key==kVolDn || Key==kLeft) { 
        outlevel=outlevel-0.05;
        if (outlevel<0) outlevel=0;
      } else {
       outlevel=outlevel+0.05;
       if (outlevel>1) outlevel=1;
      }   
      iaxc_output_level_set(outlevel);
      ShowVolume();
      break;
     case k0: 
     case k1:
     case k2:
     case k3:
     case k4:
     case k5:
     case k6:
     case k7:
     case k8:
     case k9:
       if (call.complete) { 
         DtmfFeedback.Start();
         iaxc_send_dtmf(Key-k0+'0');
       }
       break;
     case kYellow:
     case kBlue:
       {
         char c=(Key==kYellow) ? '*' : '#';
         if (Current()==NUMBER_ENTRY) {
           unsigned int l=strlen(number);
           if (l<sizeof(number)) {
             number[l]= c;
             number[l+1]=0;
             ReplaceNumber();
           }
         }
         if (call.complete) { 
           DtmfFeedback.Start();
           iaxc_send_dtmf(c);
         }
       }    
       break;
     default:
       ; //just falls through
    }
  }
 if (Key!=kNone && Current()>NUMBER_ENTRY) {
    strn0cpy(number,PhoneBook->Get(Current()-NUMBER_ENTRY-1)->GetNumber(), sizeof(number));
    ReplaceNumber();
  }
  mutex.Lock();
  Display();
  ShowStatus();
  mutex.Unlock();  
  return state;
}

//--------- iaxc_callback -----------------------------------------------------------
/*
 * State events
 */

static char *map[] = {
    "unknown", "active", "outgoing", "ringing", "complete", "selected", NULL
};

char *map_state(int state) {
    int i, j;
    int next=0;
    *states = '\0';

    if (state == 0) {
	return "free";
    }
    for(i=0, j=1; map[i] != NULL; i++, j<<=1) {
	if (state&j) {
	    if (next) strcat(states, ",");
	    strcat(states, map[i]);
	    next = 1;
	}
    }
    return states;
}


/*
 * all iax callbacks come here
 */
int iaxc_callback(iaxc_event e) {
    bool dostatus=false;
    int last_state;
    mutex.Lock();
    switch(e.type) {
        case IAXC_EVENT_LEVELS:
            miclevel=e.ev.levels.input;
            spkrlevel=e.ev.levels.output;
            dostatus=showlevels;
	    break;
        case IAXC_EVENT_TEXT:
            printf("iaxphone text event: %d, %s\n", e.ev.text.type, e.ev.text.message);
            if (mainmenu) mainmenu->ShowTextEvent(e.ev.text.message);
	    break;
        case IAXC_EVENT_STATE:
            last_state=e.ev.call.state;
            call.free= (last_state == IAXC_CALL_STATE_FREE);
            call.ringing=last_state & IAXC_CALL_STATE_RINGING;
            call.outgoing=last_state & IAXC_CALL_STATE_OUTGOING;
            call.complete=last_state & IAXC_CALL_STATE_COMPLETE;
            call.selected=last_state & IAXC_CALL_STATE_SELECTED;
            format=e.ev.call.format;
            if (call.complete && !started) callstart=time(NULL);
            started=call.complete;
            strn0cpy(remote_number,e.ev.call.remote,sizeof(remote_number));
            //remove account information
            {
              char *remaddr=strrchr(e.ev.call.remote_name,'@');
              if (remaddr) remaddr++;
                else remaddr=e.ev.call.remote_name; 
              strn0cpy(remote_name,remaddr,sizeof(remote_name));
            }  
            //remove extension
            if (char *ext=strrchr(remote_name,'/')) *ext=0;
            printf("iaxphone state event: %d %s, remote %s, remote_name %s, local %s, local_context %s\n", 
                     last_state, map_state(last_state), e.ev.call.remote, remote_name, e.ev.call.local, e.ev.call.local_context);
            dostatus=true;         
            if (call.ringing && !call.outgoing) {
              if(!ringer->Active()) ringer->Start();
            } else {
              if(ringer->Active()) ringer->Cancel();
            }
            if (call.ringing && call.outgoing) RingBack.Start();
            else RingBack.Stop();
	    break;
	case IAXC_EVENT_NETSTAT:
	    printf("iaxphone net event\n");
	    /*
"iaxphone net event: local jitter %d, losspct %d, losscnt %d, packets %d, delay %d, dropped %d, ooo %d\n    remote jitter %d, losspct %d, losscnt %d, packets %d, delay %d, dropped %d, ooo %d\n",
	             e.ev.netstats.local.jitter, e.ev.netstats.local.losspct, e.ev.netstats.local.losscnt, e.ev.netstats.local.packets, e.ev.netstats.local.delay, e.ev.netstats.local.dropped, e.ev.netstats.local.ooo,
	             e.ev.netstats.remote.jitter, e.ev.netstats.remote.losspct, e.ev.netstats.remote.losscnt, e.ev.netstats.remote.packets, e.ev.netstats.remote.delay, e.ev.netstats.remote.dropped, e.ev.netstats.remote.ooo
	             );
	    */         
	    break;
	case IAXC_EVENT_URL:
	    printf("iaxphone url event: %d %s\n", e.ev.url.type, e.ev.url.url);
	    break;
	case IAXC_EVENT_VIDEO:
	    printf("iaxphone Video event\n");
	    break;
	case IAXC_EVENT_REGISTRATION:
	    printf("iaxphone reg event: id %d, reply %d\n", e.ev.reg.id, e.ev.reg.reply);
	    registered=e.ev.reg.reply;
	    dostatus=true;
	    break;        
        default:
            printf("iaxphone unknown event %d\n",e.type);
	    break;
    }
    if (mainmenu && dostatus) mainmenu->ShowStatus();
    mutex.Unlock();
    return 1;
}

// --- cPluginIaxphone ---------------------------------------------------------

class cPluginIaxphone : public cPlugin {
private:
  // Add any member variables or functions you may need here.
public:
  cPluginIaxphone(void);
  virtual ~cPluginIaxphone();
  virtual const char *Version(void) { return VERSION; }
  virtual const char *Description(void) { return tr(DESCRIPTION); }
  virtual const char *CommandLineHelp(void);
  virtual bool ProcessArgs(int argc, char *argv[]);
  virtual bool Initialize(void);
  virtual bool Start(void);
  virtual void Stop(void);
  virtual void Housekeeping(void);
  virtual const char *MainMenuEntry(void) { return tr(MAINMENUENTRY); }
  virtual cOsdObject *MainMenuAction(void);
  virtual cMenuSetupPage *SetupMenu(void);
  virtual bool SetupParse(const char *Name, const char *Value);
  };

cPluginIaxphone::cPluginIaxphone(void)
{
  // Initialize any member variables here.
  // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
  // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
  call.free=true;
  call.outgoing=false;
  call.ringing=false;
  call.complete=false;
  call.selected=false;
  for (int i=0; i<FORMATS; i++) accepted_codec[i]=1;
}

cPluginIaxphone::~cPluginIaxphone()
{
  // Clean up after yourself!
}

const char *cPluginIaxphone::CommandLineHelp(void)
{
  // Return a string that describes all known command line options.
  return NULL;
}

bool cPluginIaxphone::ProcessArgs(int argc, char *argv[])
{
  // Implement command line argument processing here if applicable.
  return true;
}



bool cPluginIaxphone::Initialize(void)
{
  // Initialize any background activities the plugin shall perform.
  if (iaxc_initialize(AUDIO_INTERNAL_PA,1)) {
    esyslog("cannot initialize iaxclient!");
    return false;
  }
  ringer=new cRinger;
  iaxc_set_silence_threshold(-99.0); // the default
  iaxc_set_audio_output(0); //the default
  iaxc_set_event_callback(iaxc_callback);
  iaxc_start_processing_thread();
  iaxc_settings();
  return true;
}

bool cPluginIaxphone::Start(void)
{
  // Start any background activities the plugin shall perform.
  RegisterI18n(Phrases);
  return true;
}

void cPluginIaxphone::Stop(void)
{
  // Stop any background activities the plugin shall perform.
  iaxc_dump_call();
  iaxc_unregister(regid);
  iaxc_millisleep(1000);
  iaxc_stop_processing_thread();
  iaxc_shutdown();
  delete ringer;
}

void cPluginIaxphone::Housekeeping(void)
{
  // Perform any cleanup or other regular tasks.
}

cOsdObject *cPluginIaxphone::MainMenuAction(void)
{
  // Perform the action when selected from the main VDR menu.
  return new cMainMenuIaxphone;
}

cMenuSetupPage *cPluginIaxphone::SetupMenu(void)
{
  // Return a setup menu in case the plugin supports one.
  return new cMenuSetupIaxphone;
}

bool cPluginIaxphone::SetupParse(const char *Name, const char *Value)
{
  // Parse your own setup parameters and store their values.
  if      (!strcasecmp(Name, "IaxServer"))       strn0cpy(iaxserver,Value,sizeof(iaxserver));
  else if (!strcasecmp(Name, "Username"))        strn0cpy(username,Value,sizeof(username));
  else if (!strcasecmp(Name, "Password"))        strn0cpy(password,Value,sizeof(password));
  else if (!strcasecmp(Name, "CallerId"))        strn0cpy(callerid,Value,sizeof(callerid));
  else if (!strcasecmp(Name, "CallerName"))      strn0cpy(callername,Value,sizeof(callername));
  else if (!strcasecmp(Name, "DefaultPrefix"))   strn0cpy(defprefix,Value,sizeof(defprefix));
  else if (!strcasecmp(Name, "PreferredCodec"))  preferred_codec=atoi(Value);
  else if (!strcasecmp(Name, "DoRegister"))      doregister=atoi(Value);
  else if (!strcasecmp(Name, "Filters"))         filters=atoi(Value);
  else if (!strcasecmp(Name, "ShowLevels"))      showlevels=atoi(Value);
  else if (!strcasecmp(Name, "InputDevice"))     { free(indev); indev=strdup(Value); }
  else if (!strcasecmp(Name, "OutputDevice"))    { free(outdev); outdev=strdup(Value); }
  else if (!strcasecmp(Name, "RingDevice"))      { free(ringdev); ringdev=strdup(Value); }
  else {   
          for (int i=0; i<FORMATS; i++) {
           if(!strcasecmp(Name, CodecNames[i])) {
              accepted_codec[i]=atoi(Value);
              return true;
           }   
         }
       }        
  return false;
}

VDRPLUGINCREATOR(cPluginIaxphone); // Don't touch this!
