/*

                                   _JNJ`                        
                               .JNMH`                           
                              JMMF`       `;.                   
                            .NMM)          `MN.                 
                            MMM)            (MML                
                           (MMM`             MMML               
                     M  I  D  N  I  G  H  T    C  o  D  E       
                           (NMMF             MHNH               
                            NMML            .MMM                
                             NMML          .NMH                 
                              4MMNL       .#F                   
                               `4HNNL_   `                      
                                   `"""`                        

                          Copyright (C) 2004-2007
       "Ian (Larry) Latter" <ian dot latter at midnightcode dot org>

    Midnight Code is a registered trademark of Ian Latter.

    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, and mirrored at the Midnight Code web
    site; as at version 2 of the License only.
  
    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
    (version 2) along with this program; if not, write to the Free
    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA, or see http://midnightcode.org/gplv2.txt

*/

/* midnightcode-gps.c :: Midnight Code Global Position System library	*/


#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <time.h>

#include "preferences.h"
#include "midnightcode-list.h"
#include "midnightcode-parse.h"
#include "midnightcode-ip.h"
#include "midnightcode-config.h"
#include "midnightcode-socket.h"
#include "midnightcode-random.h"
#include "midnightcode-gps.h"



struct midnightcode_gps_head_t * midnightcode_gps_fixes = NULL;
pthread_mutex_t midnightcode_gps_mutex_t = PTHREAD_MUTEX_INITIALIZER;
pthread_t midnightcode_gps_pthread_t;



int
midnightcode_gps_open(struct midnightcode_gps_element_t ** fix_p,
                     char * ipv4_address_s, int port) {
  struct midnightcode_gps_head_t * fixes;
  struct midnightcode_gps_element_t * fix;
  int ret_i;


  if(midnightcode_gps_fixes == NULL)
    return -1;
  if(!fix_p)
    return -1;
  if(!ipv4_address_s)
    return -1;
  if(strlen(ipv4_address_s) < 7 || strlen(ipv4_address_s) > 15)
    return -1;
  if(port < 0 || port > 65535)
    return -1;

  ret_i = 0;
  fixes = midnightcode_gps_fixes;

  fix = malloc(sizeof(struct midnightcode_gps_element_t));
  if(fix == NULL)
    return -1;
  memset(fix, 0, sizeof(struct midnightcode_gps_element_t));

  strncpy(fix->ipv4_address_s, ipv4_address_s,
    sizeof(fix->ipv4_address_s) - 1);
  fix->port = port;

  if(midnightcode_socket_open(&(fix->socket),
    MIDNIGHTCODE_SOCKET_CONTROL_RAW,
    MIDNIGHTCODE_SOCKET_DPD_DISABLE, 0,
    MIDNIGHTCODE_SOCKET_DIR_INITIATE,
    MIDNIGHTCODE_SOCKET_PROTOCOL_TCP,
    fix->ipv4_address_s, fix->port) < 0) {
    free(fix);
    return -1;
  }
  fix->state = MIDNIGHTCODE_GPS_STATE_ACTIVE;

  if(midnightcode_socket_write_block(fix->socket,
    MIDNIGHTCODE_GPS_DAEMON_WATCHER, strlen(MIDNIGHTCODE_GPS_DAEMON_WATCHER), 
    MIDNIGHTCODE_GPS_SOCKET_HANG_MAX,
    MIDNIGHTCODE_SOCKET_IO_BLOCKING) <= 0) {
    midnightcode_socket_close(fix->socket);
    fix->socket = NULL;
    free(fix);
    return -1;
  }

  pthread_mutex_lock(&midnightcode_gps_mutex_t);
  midnightcode_list_append(fixes, fix);
  *fix_p = fix;
  pthread_mutex_unlock(&midnightcode_gps_mutex_t);

  return ret_i;
}



int
midnightcode_gps_close(struct midnightcode_gps_element_t * fix) {
  struct midnightcode_gps_head_t * fixes;
  int ret_i;


  if(midnightcode_gps_fixes == NULL)
    return -1;
  if(!fix)
    return -1;

  ret_i = 0;
  fixes = midnightcode_gps_fixes;


  pthread_mutex_lock(&midnightcode_gps_mutex_t);
  if(fix->state != MIDNIGHTCODE_GPS_STATE_INACTIVE) {
    if(fix->socket != NULL) {
      midnightcode_socket_close(fix->socket);
      fix->socket = NULL;
    }
  }
  midnightcode_list_remove(fixes, fix);
  ret_i++;
  if(fix != NULL) {
    free(fix);
  }
  pthread_mutex_unlock(&midnightcode_gps_mutex_t);

  return ret_i;
}



int
midnightcode_gps_receive(
                     struct midnightcode_gps_element_t * fix,
                     struct midnightcode_socket_element_t * socket) {
  char * buffer;
  int buffer_len;
  double gps_time;
  double gps_ept;
  double gps_latitude;
  double gps_longitude;
  double gps_altitude;
  double gps_eph;
  double gps_epv;
  double gps_course;
  double gps_speed;
  double gps_climb;
  int gps_fix;
  int ret_i;


  if(!fix)
    return -1;
  if(!socket)
    return -1;
  if(midnightcode_gps_fixes == NULL)
    return -1;

  buffer = NULL;
  buffer_len = 0;
  ret_i = 0;

  while(socket->state == MIDNIGHTCODE_SOCKET_STATE_OPERATIONAL) {

    if(midnightcode_socket_read_block(socket, &buffer, &buffer_len, 1,
      0, MIDNIGHTCODE_SOCKET_IO_NONBLOCKING) < 0) {
      return -1;
    }

    if(buffer_len) {
      if(strncmp(buffer, MIDNIGHTCODE_GPS_DAEMON_NOFIX,
        strlen(MIDNIGHTCODE_GPS_DAEMON_NOFIX)) == 0) {
        fix->state = MIDNIGHTCODE_GPS_STATE_ACTIVE;
        free(buffer);
        buffer = NULL;
        buffer_len = 0;
        continue;
      }
      if(strncmp(buffer, MIDNIGHTCODE_GPS_DAEMON_FIXED,
        strlen(MIDNIGHTCODE_GPS_DAEMON_FIXED)) == 0) {
        fix->state = MIDNIGHTCODE_GPS_STATE_ACTIVE;
        if(sscanf(buffer, MIDNIGHTCODE_GPS_DAEMON_FIXED 
          " %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %d",
          &gps_fix)) {
          if(gps_fix == 3) {
            if(sscanf(buffer, MIDNIGHTCODE_GPS_DAEMON_FIXED 
              " %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %*s %*s %*s %d",
              &gps_time, &gps_ept,
              &gps_latitude, &gps_longitude, &gps_altitude,
              &gps_eph, &gps_epv,
              &gps_course, &gps_speed, &gps_climb, &gps_fix) == 11) {
              fix->state |= MIDNIGHTCODE_GPS_STATE_3DFIX;
              /* TODO: Incorporate local timezone offset */
              fix->time = gps_time;
              fix->latitude = gps_latitude;
              fix->longitude = gps_longitude;
              fix->altitude = gps_altitude;
              fix->course = gps_course;
              fix->speed = gps_speed;
              fix->climb = gps_climb;
            }
          } else {
            if(sscanf(buffer, MIDNIGHTCODE_GPS_DAEMON_FIXED 
              " %lf %lf %lf %lf %*s %lf %*s %lf %lf %*s %*s %*s %*s %d",
              &gps_time, &gps_ept,
              &gps_latitude, &gps_longitude,
              &gps_eph,
              &gps_course, &gps_speed, &gps_fix) == 8) {
              fix->state |= MIDNIGHTCODE_GPS_STATE_2DFIX;
              /* TODO: Incorporate local timezone offset */
              fix->time = gps_time;
              fix->latitude = gps_latitude;
              fix->longitude = gps_longitude;
              fix->course = gps_course;
              fix->speed = gps_speed;
              /* TODO: Consider setting void 3D values to 0 */
            }
          }
        }
        ret_i++;
      }

      if(buffer != NULL)
        free(buffer);
      buffer = NULL;
      buffer_len = 0;

    } else {
      break;
    }

    /* TODO: Consider a max packet processing count for this loop */
  }

  if(socket->state != MIDNIGHTCODE_SOCKET_STATE_OPERATIONAL)
    return -1;


  return ret_i;
}



int
midnightcode_gps_unfix(struct midnightcode_gps_head_t * fixes) {
  struct midnightcode_gps_element_t * fix;
  int ret_i;


  if(!fixes)
    return -1;
  if(midnightcode_gps_fixes == NULL)
    return -1;

  ret_i = 0;
  midnightcode_list_for_each(fix, fixes) {
    if(fix->state == MIDNIGHTCODE_GPS_STATE_INACTIVE)
      continue;

    if(fix->socket != NULL) {
      midnightcode_socket_close(fix->socket);
      fix->socket = NULL;
    }
    fix->state = MIDNIGHTCODE_GPS_STATE_INACTIVE;
    ret_i++;
  }


  return ret_i;
}



void *
midnightcode_gps_handler(void *arg) {
  struct midnightcode_gps_head_t * fixes;
  struct midnightcode_gps_element_t * fix;


  arg = NULL;
  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
  pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);

  while(1) {

    pthread_mutex_lock(&midnightcode_gps_mutex_t);

    if(!midnightcode_gps_fixes) {
      pthread_mutex_unlock(&midnightcode_gps_mutex_t);
      pthread_exit(NULL);
    }

    fixes = midnightcode_gps_fixes;

    if(midnightcode_list_count(fixes) == 0) {
      pthread_mutex_unlock(&midnightcode_gps_mutex_t);
      pthread_testcancel();
      usleep(MIDNIGHTCODE_GPS_POLL_TIME_HANDLER);
      continue;
    }

    midnightcode_list_for_each(fix, fixes) {

      /* TODO: Retrieve socket realtionship from "fix" record */

      if(fix->state == MIDNIGHTCODE_GPS_STATE_INACTIVE)
        continue;

      if(fix->socket->state != MIDNIGHTCODE_SOCKET_STATE_OPERATIONAL) {
        if(fix->socket != NULL) {
          midnightcode_socket_close(fix->socket);
          fix->socket = NULL;
        }
        fix->state = MIDNIGHTCODE_GPS_STATE_INACTIVE;
        continue;
      }

      if(midnightcode_gps_receive(fix, fix->socket) < 0) {
        if(fix->socket != NULL) {
          midnightcode_socket_close(fix->socket);
          fix->socket = NULL;
        }
        fix->state = MIDNIGHTCODE_GPS_STATE_INACTIVE;
        continue;
      }

    }

    pthread_mutex_unlock(&midnightcode_gps_mutex_t);
    pthread_testcancel();
    usleep(MIDNIGHTCODE_GPS_POLL_TIME_HANDLER);

  }

}



int
midnightcode_gps_initialise(struct midnightcode_gps_head_t * fixes) {
  int ret_i;


  if(!fixes)
    return -1;
  if(midnightcode_gps_fixes != NULL)
    return -1;

  midnightcode_gps_fixes = fixes;

  ret_i = 0;
  if(pthread_create(&midnightcode_gps_pthread_t, NULL,
    midnightcode_gps_handler, NULL)) {
    if(midnightcode_gps_shutdown(fixes) < 0)
      midnightcode_gps_unfix(fixes);
    midnightcode_gps_fixes = NULL;
    return -1;
  }


  return ret_i;
}



int
midnightcode_gps_shutdown(struct midnightcode_gps_head_t * fixes) {
  int ret_i;


  if(!fixes)
    return -1;
  if(fixes != midnightcode_gps_fixes)
    return -1;
  if(midnightcode_gps_fixes == NULL)
    return -1;

  if(pthread_cancel(midnightcode_gps_pthread_t))
    return -1;

  ret_i = midnightcode_gps_unfix(fixes);
  midnightcode_gps_fixes = NULL;


  return ret_i;
}

/* The life I breathe into thee is a power I source from a dark place
   beyond even my own understanding - yet, it is by wielding this 
   power that I am able to create at all */

