/*
   $Id: protocol.c,v 1.1 2011-01-18 12:48:45 moellmer Exp $
   Copyright (C) 2003 Pascal Brisset, Antoine Drouin

  This file is part of paparazzi.

  paparazzi 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; either version 2, or (at your option)
  any later version.

  paparazzi 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 paparazzi; see the file COPYING.  If not, write to
  the Free Software Foundation, 59 Temple Place - Suite 330,
  Boston, MA 02111-1307, USA.
*/

#include "protocol.h"
#include <stdlib.h>
#include <string.h>

struct PprzTypInfo {
  gchar *xml_name;
  guint  size;
  gchar *default_format;
};

const struct PprzTypInfo type_info[ TYP_NB ] = {
  {"uint8",  1, "%hhu"},
  {"uint16", 2, "%hu" },
  {"uint32", 4, "%u"  },
  {"int8",   1, "%hhd"},
  {"int16",  2, "%hd" },
  {"int32",  4, "%d"  },
  {"float",  4, "%f"  },
  {"uint8_array",  1, "%0x"}
};

static gboolean parse_xml_msg( struct PprzProtocol *this, xmlNodePtr ptr );
static gboolean parse_xml_field( struct PprzMsgClass *msg_class,
                                 xmlNodePtr ptr );
static void ascii_of_fields( struct PprzMsg *msg, GList *fields_class,
                             GString *buf );
static gboolean field_of_ascii( GList *fields_class, gchar *line,
                                struct PprzMsg *msg );

static enum PprzFieldType type_of_string( const gchar *type_str );

static struct PprzMsgClass *msg_class_of_name( struct PprzProtocol *this,
    const gchar *name );
static struct PprzMsgClass *msg_class_of_id( struct PprzProtocol *this,
    guchar id );

static struct PprzFieldClass *field_class_of_name( struct PprzMsg *msg,
    const gchar *name );
/* static struct PprzFieldClass* field_class_of_id(struct PprzMsg* msg, guchar id); */

struct PprzProtocol *pprz_protocol_new_from_xml( xmlDocPtr doc,
    xmlNodePtr cur )
{
  struct PprzProtocol *this;
  if ( !cur ) return NULL;                                /* empty document */
  if ( xmlStrcmp( cur->name,
                  ( const xmlChar * ) "protocol" ) ) /* not protocol   */
    return cur->next ? pprz_protocol_new_from_xml( doc, cur->next ) : NULL;
  this = g_new( struct PprzProtocol, 1 );
  this->msgs_classes = NULL;
  this->msgs_classes_by_name = g_hash_table_new( g_str_hash, g_str_equal );
  if ( !parse_xml_msg( this, cur->xmlChildrenNode ) ) {
    g_free( this );
    return NULL;
  }
  this->nb_msgs_classes = g_list_length( this->msgs_classes );
  this->msgs_classes_by_id = g_new( struct PprzMsgClass *,
                                    this->nb_msgs_classes );
  {
    GList *cur = this->msgs_classes;
    guint i = 0;
    while ( cur ) {
      this->msgs_classes_by_id[ i ] = cur->data;
      i++;
      cur = cur->next;
    }
  }
  return this;
}

const gchar *pprz_protocol_str_of_field_type( enum PprzFieldType type )
{
  return type_info[ type ].xml_name;
}

const guint pprz_protocol_size_of_field_type( enum PprzFieldType type )
{
  return type_info[ type ].size;
}

static enum PprzFieldType type_of_string( const gchar *type_str )
{
  guint ret = 0;
  while ( ret < TYP_NB && strcmp( type_info[ ret ].xml_name, type_str ) )
    ret++;
  return ret;
}

struct PprzMsg *pprz_message_new( struct PprzMsgClass *msg_class,
                                  GTimeVal date )
{
  struct PprzMsg *this = g_new( struct PprzMsg, 1 );
  this->date = date;
  this->class = msg_class;
  this->bytes = g_new( guchar, this->class->size - 1 );
  return this;
}

struct PprzMsg *pprz_protocol_msg_new_by_id( struct PprzProtocol *this,
    guchar id, GTimeVal date )
{
  struct PprzMsgClass *msg_class = msg_class_of_id( this, id );
  if ( msg_class ) return pprz_message_new( msg_class, date );
  return NULL;
}

struct PprzMsg *pprz_protocol_msg_new_by_name( struct PprzProtocol *this,
    const gchar *name, GTimeVal date )
{
  struct PprzMsgClass *msg_class = msg_class_of_name( this, name );
  if ( msg_class ) return pprz_message_new( msg_class, date );
  return NULL;
}

struct PprzMsg *pprz_protocol_msg_new_of_bin( struct PprzProtocol *this,
    const guchar *buf, GTimeVal date )
{
  struct PprzMsg *msg = pprz_protocol_msg_new_by_id( this, buf[ 0 ], date );
  if ( msg )
    memcpy( msg->bytes, buf + 1, msg->class->size - 1 );
  return msg;
}


struct PprzMsg *pprz_protocol_msg_new_of_ascii( struct PprzProtocol *this,
    const gchar *line )
{
  gchar *kw;
  GTimeVal date;
  struct PprzMsg *msg;
  if ( sscanf( line, "%ld.%06ld %as", &date.tv_sec, &date.tv_usec, &kw ) != 3 )
    return NULL;
  if ( !( msg = pprz_protocol_msg_new_by_name( this, kw, date ) ) )
    return NULL;
  if ( msg->class->fields_classes ) {   // message has arguments
    gchar *space = index( line, ' ' );
    g_assert( space );
    g_assert( space + 1 ); // checked before by sscanf
    space = index( space + 1, ' ' );
    g_assert( space );                  // checked before by sscanf
    if ( !field_of_ascii( msg->class->fields_classes, space, msg ) ) {
      pprz_msg_free( msg );
      return NULL;
    }
  }
  return msg;
}

void pprz_msg_free( struct PprzMsg *this )
{
  g_free( this->bytes );
  g_free( this );
}

gboolean pprz_msg_set_field( struct PprzMsg *msg,
                             struct PprzFieldClass *field_class, gconstpointer value )
{
  /*#ifdef WITH_SWITCH
    switch (field_class->type) {
    case TYP_UINT_8:
       (guint8*)(msg->bytes+field_class->offset) = *(guint8*)value;
      break;
    case TYP_UINT_16:
       (guint16*)(msg->bytes+field_class->offset) = *(guint16*)value;
      break;
    case  TYP_UINT_32:
       (guint32*)(msg->bytes+field_class->offset) = *(guint32*)value;
      break;
    case  TYP_INT_8:
       (gint8*)(msg->bytes+field_class->offset) = *(gint8*)value;
      break;
    case  TYP_INT_16:
       (gint16*)(msg->bytes+field_class->offset) = *(gint16*)value;
      break;
    case  TYP_INT_32:
       (gint32*)(msg->bytes+field_class->offset) = *(gint32*)value;
      break;
    case  TYP_FLOAT:
       (gfloat*)(msg->bytes+field_class->offset) = *(gfloat*)value;
      break;
    case  TYP_ARRAY_UINT_8:
      //    g_string_append_printf(buf, format, *((gfloat*)data));
      break;
    default:
      g_warning("in ascii of field : unknown type");
    }
    #else*/
  if ( field_class->type == TYP_UINT_8 ) {
    *( guint8 * )( msg->bytes + field_class->offset ) = *( guint8 * )value;
    break;
  } else
    if ( field_class->type == TYP_UINT_16 ) {
      *( guint16 * )( msg->bytes + field_class->offset ) = *( guint16 * )value;
      break;
    } else
      if ( field_class->type == TYP_UINT_32 ) {
        *( guint32 * )( msg->bytes + field_class->offset ) = *( guint32 * )value;
        break;
      } else
        if ( field_class->type == TYP_INT_8 ) {
          *( gint8 * )( msg->bytes + field_class->offset ) = *( gint8 * )value;
          break;
        } else
          if ( field_class->type == TYP_INT_16 ) {
            *( gint16 * )( msg->bytes + field_class->offset ) = *( gint16 * )value;
            break;
          } else
            if ( field_class->type == TYP_INT_32 ) {
              *( gint32 * )( msg->bytes + field_class->offset ) = *( gint32 * )value;
              break;
            } else
              if ( field_class->type == TYP_FLOAT ) {
                *( gfloat * )( msg->bytes + field_class->offset ) = *( gfloat * )value;
                break;
              } else
                if ( field_class->type == TYP_ARRAY_UINT_8 ) {
                  //    g_string_append_printf(buf, format, *((gfloat*)data));
                  break;
                } else
                  g_warning( "in ascii of field : unknown type" );
//#endif
  return TRUE;
}

gboolean pprz_msg_set_field_by_name( struct PprzMsg *msg, const gchar *name,
                                     gconstpointer value )
{
  struct PprzFieldClass *field_class = field_class_of_name( msg, name );
  if ( field_class )
    return pprz_msg_set_field( msg, field_class, value );
  return FALSE;
}

gboolean pprz_msg_set_field_by_id( struct PprzMsg *msg, guint id,
                                   gconstpointer value )
{
  GList *cell = g_list_nth( msg->class->fields_classes, id );
  if ( cell )
    return pprz_msg_set_field( msg, cell->data, value );
  return FALSE;
}


gpointer pprz_msg_get_field( struct PprzMsg *msg,
                             struct PprzFieldClass *field_class )
{
  return msg->bytes + field_class->offset;
}

gpointer pprz_msg_get_field_by_name( struct PprzMsg *msg, const gchar *name )
{
  struct PprzFieldClass *field_class = field_class_of_name( msg, name );
  if ( field_class )
    return pprz_msg_get_field( msg, field_class );
  g_message( "pprz_msg_get_field_by_name : unknown field [ %s ] in msg [ %s ]", name,
             msg->class->name );
  return NULL;
}



#define DEFAULT_LINE_LEN 256
void pprz_protocol_ascii_of_msg( struct PprzMsg *msg, GString **buf )
{
  *buf = g_string_sized_new( DEFAULT_LINE_LEN );
  g_string_printf( *buf, "%ld.%06ld %s  ", msg->date.tv_sec, msg->date.tv_usec,
                   msg->class->name );
  if ( msg->class->fields_classes ) ascii_of_fields( msg,
        msg->class->fields_classes, *buf );
  g_string_append_printf( *buf, "\n" );
}

void pprz_msg_ascii_of_field( struct PprzMsg *msg,
                              struct PprzFieldClass *field_class, GString *buf )
{
  guchar *format;
  guchar *data;
  format = field_class->format;
  if ( !format ) format = type_info[ field_class->type ].default_format;
  data = msg->bytes + field_class->offset;
  /*#ifdef WITH_SWITCH
    switch (field_class->type) {
    case TYP_UINT_8:
      g_string_append_printf(buf, format, *((guint8*)data));
      break;
    case TYP_UINT_16:
      g_string_append_printf(buf, format, *((guint16*)data));
      break;
    case  TYP_UINT_32:
      g_string_append_printf(buf, format, *((guint32*)data));
      break;
    case  TYP_INT_8:
      g_string_append_printf(buf, format, *((gint8*)data));
      break;
    case  TYP_INT_16:
      g_string_append_printf(buf, format, *((gint16*)data));
      break;
    case  TYP_INT_32:
      g_string_append_printf(buf, format, *((gint32*)data));
      break;
    case  TYP_FLOAT:
      g_string_append_printf(buf, format, *((gfloat*)data));
      break;
    case  TYP_ARRAY_UINT_8:
      //    g_string_append_printf(buf, format, *((gfloat*)data));
      break;
    default:
      g_warning("in ascii of field : unknown type");
    }
    #else*/
  if ( field_class->type == TYP_UINT_8 ) {
    g_string_append_printf( buf, format, *( ( guint8 * )data ) );
    break;
  } else
    if ( field_class->type == TYP_UINT_16 ) {
      g_string_append_printf( buf, format, *( ( guint16 * )data ) );
      break;
    } else
      if ( field_class->type == TYP_UINT_32 ) {
        g_string_append_printf( buf, format, *( ( guint32 * )data ) );
        break;
      } else
        if ( field_class->type == TYP_INT_8 ) {
          g_string_append_printf( buf, format, *( ( gint8 * )data ) );
          break;
        } else
          if ( field_class->type == TYP_INT_16 ) {
            g_string_append_printf( buf, format, *( ( gint16 * )data ) );
            break;
          } else
            if ( field_class->type == TYP_INT_32 ) {
              g_string_append_printf( buf, format, *( ( gint32 * )data ) );
              break;
            } else
              if ( field_class->type == TYP_FLOAT ) {
                g_string_append_printf( buf, format, *( ( gfloat * )data ) );
                break;
              } else
                if ( field_class->type == TYP_ARRAY_UINT_8 ) {
                  //    g_string_append_printf(buf, format, *((gfloat*)data));
                  break;
                } else
                  g_warning( "in ascii of field : unknown type" );
//#endif
}


static gboolean field_of_ascii( GList *fields_classes, gchar *line,
                                struct PprzMsg *msg )
{
  struct PprzFieldClass *field_class = ( struct PprzFieldClass * )
                                       fields_classes->data;
  gchar *end_ptr;
  gchar *ptr_field = msg->bytes + field_class->offset;
  /*#ifdef WITH_SWITCH
    switch (field_class->type) {
    case TYP_UINT_8:
       ((guint8*)ptr_field) = (guint8)strtoul(line, &end_ptr, 10);
      break;
    case TYP_UINT_16:
       ((guint16*)ptr_field) = (guint16)strtoul(line, &end_ptr, 10);
      break;
    case TYP_UINT_32:
       ((guint32*)ptr_field) = (guint32)strtoul(line, &end_ptr, 10);
      break;
    case TYP_INT_8:
       ((gint8*)ptr_field) = (gint8)strtol(line, &end_ptr, 10);
      break;
    case TYP_INT_16:
       ((gint16*)ptr_field) = (gint16)strtol(line, &end_ptr, 10);
      break;
    case TYP_INT_32:
       ((gint32*)ptr_field) = (gint32)strtol(line, &end_ptr, 10);
      break;
    case TYP_FLOAT:
       ((gfloat*)ptr_field) = (gfloat)strtod(line, &end_ptr);
      break;
    case  TYP_ARRAY_UINT_8:
      break;
    default:
      printf("in field of ascii: unknown type\n");
    }
    #else*/
  if ( field_class->type == TYP_UINT_8 ) {
    *( ( guint8 * )ptr_field ) = ( guint8 )strtoul( line, &end_ptr, 10 );
    break;
  } else
    if ( field_class->type == TYP_UINT_16 ) {
      *( ( guint16 * )ptr_field ) = ( guint16 )strtoul( line, &end_ptr, 10 );
      break;
    } else
      if ( field_class->type == TYP_UINT_32 ) {
        *( ( guint32 * )ptr_field ) = ( guint32 )strtoul( line, &end_ptr, 10 );
        break;
      } else
        if ( field_class->type == TYP_INT_8 ) {
          *( ( gint8 * )ptr_field ) = ( gint8 )strtol( line, &end_ptr, 10 );
          break;
        } else
          if ( field_class->type == TYP_INT_16 ) {
            *( ( gint16 * )ptr_field ) = ( gint16 )strtol( line, &end_ptr, 10 );
            break;
          } else
            if ( field_class->type == TYP_INT_32 ) {
              *( ( gint32 * )ptr_field ) = ( gint32 )strtol( line, &end_ptr, 10 );
              break;
            } else
              if ( field_class->type == TYP_FLOAT ) {
                *( ( gfloat * )ptr_field ) = ( gfloat )strtod( line, &end_ptr );
                break;
              } else
                if ( field_class->type == TYP_ARRAY_UINT_8 )
                  break;
                else
                  printf( "in field of ascii: unknown type\n" );
//#endif
  if ( fields_classes->next )
    return field_of_ascii( fields_classes->next, end_ptr, msg );
  return TRUE;
}

static void ascii_of_fields( struct PprzMsg *msg, GList *fields_class,
                             GString *buf )
{
  struct PprzFieldClass *field_class = ( struct PprzFieldClass * )
                                       fields_class->data;
  pprz_msg_ascii_of_field( msg, field_class, buf );
  if ( fields_class->next ) {
    g_string_append_printf( buf, " " );
    ascii_of_fields( msg, fields_class->next, buf );
  }
}

static gboolean parse_xml_msg( struct PprzProtocol *this, xmlNodePtr ptr )
{
  struct PprzMsgClass *new_msg_class;
  if ( !xmlNodeIsText ( ptr ) ) {
    if ( xmlStrcmp( ptr->name, ( const xmlChar * )"message" ) ) /* not a message */
      return FALSE;
    new_msg_class = g_new( struct PprzMsgClass, 1 );
    new_msg_class->id =  g_list_length( this->msgs_classes );
    new_msg_class->name = xmlGetProp( ptr, "id" );
    new_msg_class->fields_classes = NULL;
    new_msg_class->fields_classes_by_name = g_hash_table_new( g_str_hash,
                                            g_str_equal );
    new_msg_class->size = 1;
    this->msgs_classes = g_list_append( this->msgs_classes, new_msg_class );
    g_hash_table_insert( this->msgs_classes_by_name, new_msg_class->name,
                         new_msg_class );
    if ( ptr->xmlChildrenNode &&
         !parse_xml_field( new_msg_class, ptr->xmlChildrenNode ) )
      /* fixme : free space */
      return FALSE;
    new_msg_class->nb_fields_classes = g_list_length(
                                         new_msg_class->fields_classes );
    new_msg_class->fields_classes_by_id = g_new( struct PprzFieldClass *,
                                          new_msg_class->nb_fields_classes );
    {
      GList *cur = new_msg_class->fields_classes;
      guint i = 0;
      while ( cur ) {
        new_msg_class->fields_classes_by_id[ i ] = cur->data;
        i++;
        cur = cur->next;
      }
    }
  }
  if ( ptr->next )
    return parse_xml_msg( this, ptr->next );
  return TRUE;
}

static gboolean parse_xml_field( struct PprzMsgClass *msg_class,
                                 xmlNodePtr ptr )
{
  struct PprzFieldClass *new_field_class;
  xmlChar *type_str;
  if ( !xmlNodeIsText ( ptr ) ) {
    if ( xmlStrcmp( ptr->name, ( const xmlChar * )"field" ) ) /* not a field    */
      return FALSE;
    new_field_class = g_new( struct PprzFieldClass, 1 );
    new_field_class->name = xmlGetProp( ptr, "id" );
    new_field_class->format = xmlGetProp( ptr, "format" );
    new_field_class->unit = xmlGetProp( ptr, "unit" );
    new_field_class->description = xmlGetProp( ptr, "description" );
    type_str = xmlGetProp( ptr, "type" );
    if ( ( new_field_class->type = type_of_string( type_str ) ) >= TYP_NB ) {
      /* fixme : free space */
      return FALSE;
    }
    xmlFree( type_str );
    msg_class->fields_classes = g_list_append( msg_class->fields_classes,
                                new_field_class );
    g_hash_table_insert( msg_class->fields_classes_by_name, new_field_class->name,
                         new_field_class );
    new_field_class->offset = msg_class->size - 1;
    msg_class->size += type_info[ new_field_class->type ].size;
  }
  if ( ptr->next )
    /* check return and free space */
    return parse_xml_field( msg_class, ptr->next );
  return TRUE;
}


static struct PprzMsgClass *msg_class_of_name( struct PprzProtocol *this,
    const gchar *name )
{
  return g_hash_table_lookup( this->msgs_classes_by_name, name );
}

static struct PprzMsgClass *msg_class_of_id( struct PprzProtocol *this,
    guchar id )
{
  if ( id >= this->nb_msgs_classes )
    return NULL;
  return this->msgs_classes_by_id[ id ];
}

static struct PprzFieldClass *field_class_of_name( struct PprzMsg *msg,
    const gchar *name )
{
  return g_hash_table_lookup( msg->class->fields_classes_by_name, name );
}

/* static struct PprzFieldClass* field_class_of_id(struct PprzMsg* msg, guchar id) { */
/*   GList* cell = g_list_nth(msg->class->fields_classes, id); */
/*   return cell?cell->data:NULL; */
/* } */
