/**
 *  Copyright (C) 2007 Jakob Eriksson (c)
 *
 *  This program 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; version 2.
 *
 *  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.
 */

#define _GNU_SOURCE
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <sys/un.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define STATE_BEGIN             0
#define STATE_PHONE_LIFTED      1
#define STATE_PHONE_HANGUP      2
#define STATE_KEY_PRESSED       3
#define STATE_CALL_IN_PROGRESS  4
#define STATE_PHONE_RINGING     5
#define STATE_LINPHONEC_HANGUP  6

static int state = STATE_BEGIN;
static char phone_number[100];
static long ticks_with_no_key_press = 0;
static const long msecs = 100;

static int write_to_fd (char *str, int fd)
{
   int len;

   len = strlen (str);

   system ("date");
   fprintf (stderr, "Writing '%s' to %d, length %d\n", str, fd, len);

   return write (fd, str, len) != len;
}

static int phone_hardware_read_socket;

static void write_to_phone (char *str)
{
   assert (!write_to_fd (str, phone_hardware_read_socket));
}

static int ringing = 0;
static void start_ringing (void)
{
   if (ringing)
   {
      return;
   }

   ringing = 1;

   write_to_phone ("RING 1\r\n");
}

static void stop_ringing (void)
{
   if (!ringing)
   {
      return;
   }

   ringing = 0;

   write_to_phone ("RING 0\r\n");
}

static void setup_fd_tv (int fdb, fd_set * fdset, struct timeval *tv)
{
   FD_ZERO (fdset);
   FD_SET (fdb, fdset);
   tv->tv_sec = 0;
   tv->tv_usec = msecs * 1000;
}

static void interpret (fd_set * fdset, int read_socket, void (*interpreter) (char *))
{
   int ret;
   char str[200];

   if (!FD_ISSET (read_socket, fdset))
   {
      return;
   }

   str[0] = '\0';
   ret = read (read_socket, str, sizeof (str) - 1);
   if (ret <= 0)
   {
      return;
   }

   str[ret] = '\0';

   fprintf (stderr, "Read from fd %d: '%s'\n", read_socket, str);

   interpreter (str);
}

static void answer_call (void)
{
   system ("twinkle --cmd \"answer\"");
}

static void change_state (int new_state)
{
   fprintf (stderr, "   ::: changed state to %d :::\n", new_state);

   state = new_state;
}

static void phone_lifted (void)
{
   phone_number[0] = '\0';

   if (STATE_PHONE_RINGING)
   {
      answer_call ();
   }

   change_state (STATE_PHONE_LIFTED);
   ticks_with_no_key_press = 0;
}

static void phone_hangup (void)
{
   phone_number[0] = '\0';
   change_state (STATE_PHONE_HANGUP);

   system ("twinkle --cmd \"bye\"");
}

static void key_pressed (int key)
{
   char keystr[2];

   if (state != STATE_PHONE_LIFTED && state != STATE_KEY_PRESSED)
   {
      return;
   }

   if (strlen (phone_number) >= sizeof (phone_number) - 1)
   {
      return;
   }

   keystr[1] = '\0';
   keystr[0] = '0' + key;
   strcat (phone_number, keystr);
   ticks_with_no_key_press = 0;
   change_state (STATE_KEY_PRESSED);
}

static void place_call (void)
{
   char buf[200];

   if (state == STATE_CALL_IN_PROGRESS)
   {
      return;
   }

   change_state (STATE_CALL_IN_PROGRESS);

   buf[sizeof (buf) - 1] = '\0';
   snprintf (buf, sizeof (buf) - 1, "twinkle --immediate --call %s", phone_number);

   system (buf);
}

static void timetick (void)
{
   if (STATE_KEY_PRESSED != state)
   {
      return;
   }

   ticks_with_no_key_press++;

   /* If more than 3 seconds has passed since last key press, dial it! */
   if (msecs * ticks_with_no_key_press > 3000)
   {
      ticks_with_no_key_press = 0;
      place_call ();
   }
}

static void phone_hardware (char *in)
{
   if (strstr (in, "KEY"))
   {
      key_pressed (atoi (in + 3));

      return;
   }

   if (strstr (in, "HANDSET ON"))
   {
      phone_lifted ();

      return;
   }

   if (strstr (in, "HANDSET OFF"))
   {
      phone_hangup ();
   }

   if (strstr (in, "USB DEVICE INIT ERROR"))
   {
      exit (1);
   }
}

static void check_for_incoming_call (void)
{
   struct stat buf;

   if (stat ("/tmp/ring_phone_0", &buf))
   {
      stop_ringing ();

      return;
   }

   if (STATE_PHONE_HANGUP == state)
   {
      start_ringing ();
   }
}

int main (int argc, char *argv[])
{
   int len;
   struct sockaddr_un remote;
   char *socket_name;
   fd_set fdset;
   struct timeval tv;

   if (argc != 2)
   {
      fprintf (stderr, "Usage: %s <socket name> <linphonec config file>\n\n"
               "socket name is the UNIX socket used by usbb2k_api\n\n", basename (argv[0]));

      return 1;
   }

   socket_name = argv[1];

   phone_hardware_read_socket = socket (AF_UNIX, SOCK_STREAM, 0);
   if (-1 == phone_hardware_read_socket)
   {
      perror ("socket");

      return 1;
   }

   sleep (1);

   remote.sun_family = AF_UNIX;
   strcpy (remote.sun_path, argv[1]);
   len = strlen (remote.sun_path) + sizeof (remote.sun_family);
   if (-1 == connect (phone_hardware_read_socket, (struct sockaddr *)&remote, len))
   {
      perror ("connect");

      return 1;
   }

   fcntl (phone_hardware_read_socket, F_SETFL, O_NONBLOCK);


   write_to_phone ("SWITCH USB\r\n");

   sleep (1);

   while (1)
   {
      fflush (NULL);
      setup_fd_tv (phone_hardware_read_socket, &fdset, &tv);

      if (select (phone_hardware_read_socket + 1, &fdset, NULL, NULL, &tv) > 0)
      {
         interpret (&fdset, phone_hardware_read_socket, phone_hardware);
      }

      usleep (100000);

      check_for_incoming_call ();

      timetick ();
   }

   return 0;
}
