// =============================================================================
// Projet    : Plugin XMMS pour le format bonk
// Programme : bonk-xmms.cc
// Création  : 23.03.2002
// Révision  : 15.06.2002
// Objet     : fichier principal
// =============================================================================
// (c) 2002 - Fabrice Haberer-Proust <info@proustmedia.de>
// =============================================================================
// 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; either version 2 of the License, or (at your
// option) any later version.
//
// 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.
// =============================================================================

// TODO: neutraliser les noms de fichier commencant par http:// (cf vorbis.c)

// == En-têtes XMMS et plugin ==================================================
#include <pthread.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <glib.h>
#include <time.h>

#include <xmms/plugin.h>
#include <xmms/util.h>
//#include "xmms/i18n.h"

// == En-têtes bonk ============================================================
#include <math.h>
#include <time.h>
#include <vector>
#include <string>
#include <algorithm>

// == Déclarations plugin ======================================================

#define VERSION "0.12"

#define MAX_BONK_OFFSET 5000 // 1000000 dans le programme bonk, mais la
                             // recherche de la balise étant très lente, on se
                             // limite à 5000 caractères, ce qui dépasse de loin
                             // la taille raisonnable du champ d'informations

   char tag_artist[] = "Artist: ";
   char tag_title [] = "\nTitle: ";

// -- En-tête bonk --
   typedef struct {
      char    BONK[5];            /**< Balise, doit toujours être '\0BONK' */
      guint8  version;            /**< Version, doit être zéro */
      guint32 length;             /**< Nombre d'échantillons */
      guint32 rate;               /**< Fréquence d'échantillonnage ? */
      guint8  channels;           /**< Nombre de canaux */
      guint8  lossless;           /**< 1 si compression sans perte, 0 sinon */
      guint8  mid_side;           /**< ??? 0 ou 1 */
      guint16 n_taps;             /**< ??? */
      guint8  down_sampling;      /**< ??? */
      guint16 samples_per_packet; /**< Nombre d'échantillons par paquet */
   }
   BONKHEADER;

// -- Entrée dans le sommaire --
   struct toc_entry {
      long file_position;
      int byte;
      int bit_no;
      int length_remaining;
   // TODO: déduire length_remaining de file_position, byte et bit_no ?

   // -- Constructeur --
      toc_entry(long _file_position, int _byte, int _bit_no, int _length_remaining)
      {
         file_position    = _file_position   ;
         byte             = _byte            ;
         bit_no           = _bit_no          ;
         length_remaining = _length_remaining;
      }
   };

// == Variables globales =======================================================
   std::vector<toc_entry> toc; // Table of content
   unsigned int toc_entry_current;
   int packet_index = 0;

   static pthread_t decode_thread;
   static gboolean audio_error = FALSE;

   static short       bonk_file_playing = 0;
   static FILE       *bonk_file;
   static BONKHEADER  bonk_header;
   static int         bonk_file_seek_to = -1;

// == Structure InputPlugin ====================================================
   static void bonk_xmms__init         (void);
   static void bonk_xmms__about        (void);
   static int  bonk_xmms__is_our_file  (char *);
   static void bonk_xmms__play_file    (char *);
   static void bonk_xmms__stop         (void);
   static void bonk_xmms__pause        (short);
   static void bonk_xmms__seek         (int);
   static int  bonk_xmms__get_time     (void);
   static void bonk_xmms__get_song_info(char *, char **, int *);

   InputPlugin bonk_ip =
   {
      NULL,
      NULL,
      NULL,
      bonk_xmms__init,
      bonk_xmms__about,
      NULL,
      bonk_xmms__is_our_file,
      NULL,
      bonk_xmms__play_file,
      bonk_xmms__stop,
      bonk_xmms__pause,
      bonk_xmms__seek,
      NULL,
      bonk_xmms__get_time,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL,
      bonk_xmms__get_song_info,
      NULL,
      NULL
   };

// == Journal =================================================================

   /**
    * Écriture d'un message dans le journal
    * La longueur du message ne doit pas dépasser 500 caractères. Aucune vérification de longueur n'est effectuée.
    * @param line   int   Numéro de la ligne appellant cette fonction
    * @param func   char* Nom de la fonction appellante
    * @param format char* Chaîne de format
    * @param ...          Liste d'arguments variables
    */
   void bonk_xmms__log(int line, const char *func, char *format, ...)
   {
#ifdef DEBUG
      FILE *logfile = fopen("/tmp/plugin-bonk.log", "a");
      va_list ap;
      char msg[500];

      if (logfile == (FILE *) NULL) return;

      va_start(ap, format);
      (void) vsnprintf(msg, 500, format, ap);
      va_end(ap);

      (void) fprintf(logfile, "%ld:%4d:%s:%s\n", time(0), line, func, msg);
      fclose(logfile);
#endif
   }

// == Lecture d'entier dans le fichier (endian safe) ===========================

   /**
    * Lecture d'un entier non signé de 16 binons
    * @param file FILE* Fichier à lire, avec le pointeur de lecture correctement positionné
    * @return guint16 L'entier lu
    */
   guint16 read_guint16(FILE *file)
   {
      guint16 u16;
      guint8  u8;
      size_t count;

      count = fread(&u8, sizeof(guint8), 1, file);
      if (count != 1) bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread");

      u16  = guint16(u8);

      count = fread(&u8, sizeof(guint8), 1, file);
      if (count != 1) bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread");

      u16 += guint16(u8 << 8);

      return u16;
   }

   /**
    * Lecture d'un entier non signé de 32 binons
    * @param file FILE* Fichier à lire, préalablement ouvert, avec le pointeur de lecture correctement positionné
    * @return guint32 L'entier lu
    */
   guint32 read_guint32(FILE *file)
   {
      guint32 u32;
      guint8  u8;
      size_t count;

      count = fread(&u8, sizeof(guint8), 1, file);
      if (count != 1) bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread");

      u32  = guint32(u8);

      count = fread(&u8, sizeof(guint8), 1, file);
      if (count != 1) bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread");

      u32 += guint32(u8 << 8);

      count = fread(&u8, sizeof(guint8), 1, file);
      if (count != 1) bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread");

      u32 += guint32(u8 << 16);

      count = fread(&u8, sizeof(guint8), 1, file);
      if (count != 1) bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread");

      u32 += guint32(u8 << 24);

      return u32;
   }

// == Lecture d'un en-tête bonk ================================================

   /**
    * Lecture et vérification de l'en-tête d'un fichier bonk
    * @param bonkheader BONKHEADER * Description de l'en-tête bonk
    * @param bonk       FILE *       Descripteur du fichier bonk ouvert préalablement en lecture, avec le pointeur en début de fichier
    * @return long la position de la balise bonk si le format est correct, -1 sinon. Si le format est correcte, le pointeur est positionné sur le début des données
    */
   static long bonkheader_read(BONKHEADER *bonkheader, FILE *bonk)
   {
      char     bonk_tag[5];
      long     bonk_tag_offset;
      size_t   count;
      int      result;

   // -- Recherche de la balise BONK --
      count = fread(bonk_tag, sizeof(char), 5, bonk);

      if (count != 5) { bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread"); return -1; }

      bonk_tag_offset = -1;

      while ((feof(bonk) == 0) && (ftell(bonk) < MAX_BONK_OFFSET) && (bonk_tag_offset < 0)) {
         if ((bonk_tag[0] == '\0') &&
             (bonk_tag[1] ==  'B') &&
             (bonk_tag[2] ==  'O') &&
             (bonk_tag[3] ==  'N') &&
             (bonk_tag[4] ==  'K')) {
            bonk_tag_offset = ftell(bonk) - 5;
         }  else {
            bonk_tag[0] = bonk_tag[1];
            bonk_tag[1] = bonk_tag[2];
            bonk_tag[2] = bonk_tag[3];
            bonk_tag[3] = bonk_tag[4];

            count = fread(bonk_tag + 4, sizeof(char), 1, bonk);

            if (count != 1) { bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread"); return -1; }
         }
      }

      if (bonk_tag_offset < 0) { bonk_xmms__log(__LINE__, __FUNCTION__, "pas trouvé la balise bonk"); return -1; }

   // -- Lecture de l'en-tête bonk --
      result = fseek(bonk, -5L, SEEK_CUR); if (result != 0) { bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fseek"); return -1; }

      count = fread(&bonkheader->BONK              , sizeof(char   ), 5, bonk); if (count != 5) { bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread"); return -1; }
      count = fread(&bonkheader->version           , sizeof(guint8 ), 1, bonk); if (count != 1) { bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread"); return -1; }

//    count = fread(&bonkheader->length            , sizeof(guint32), 1, bonk); if (count != 1) { bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread"); return -1; }
      bonkheader->length = read_guint32(bonk);
//    count = fread(&bonkheader->rate              , sizeof(guint32), 1, bonk); if (count != 1) { bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread"); return -1; }
      bonkheader->rate = read_guint32(bonk);

      count = fread(&bonkheader->channels          , sizeof(guint8 ), 1, bonk); if (count != 1) { bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread"); return -1; }
      count = fread(&bonkheader->lossless          , sizeof(guint8 ), 1, bonk); if (count != 1) { bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread"); return -1; }
      count = fread(&bonkheader->mid_side          , sizeof(guint8 ), 1, bonk); if (count != 1) { bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread"); return -1; }

      bonkheader->n_taps = read_guint16(bonk);

      count = fread(&bonkheader->down_sampling     , sizeof(guint8 ), 1, bonk); if (count != 1) { bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread"); return -1; }

      bonkheader->samples_per_packet = read_guint16(bonk);

   // -- Vérifications --
      if (bonkheader->version != 0)                                   { bonk_xmms__log(__LINE__, __FUNCTION__, "le numéro de version doit être zéro"); return -1; }
      if ((bonkheader->channels != 1) && (bonkheader->channels != 2)) { bonk_xmms__log(__LINE__, __FUNCTION__, "le nombre de canaux doit être un ou deux"); return -1; }
      if ((bonkheader->lossless != 0) && (bonkheader->lossless != 1)) { bonk_xmms__log(__LINE__, __FUNCTION__, "lossless doit être zéro ou un"); return -1; }
      if ((bonkheader->mid_side != 0) && (bonkheader->mid_side != 1)) { bonk_xmms__log(__LINE__, __FUNCTION__, "mid_side doit être zéro ou un"); return -1; }
   // TODO: reprendre les vérifications du programme original --

   // -- Fin --
      return bonk_tag_offset;
   }

// =============================================================================
// Original bonk source code by Paul Harrison
// =============================================================================

// Number of bits required to store a value:

   int bits_to_store(guint32 i)
   {
      int result = 0;

      while (i) {
         result++;
         i >>= 1;
      }

      return result;
   }

// Bitstream reader:

   /**
    * Classe bitstream_in : fichier accessible en lecture binon par binon
    */
   struct bitstream_in
   {
      FILE *f_in;
      int byte;
      int bit_no;


      /**
       * Initialisation
       * @param _f_in FILE * Le fichier à parcourir
       */
      void setup(FILE *_f_in)
      {
         f_in = _f_in;
         bit_no  = 8;
      }

      /**
       * Lecture d'un seul binons
       * Cette méthode modifie les propriétés byte et bit_no, ainsi que la position du fichier f_in
       * @param
       * @return
       */
      int read()
      {
         if (bit_no == 8) {
            byte = fgetc(f_in);

            if (byte == EOF) {
               bonk_xmms__log(__LINE__, __FUNCTION__, "bitstream_in::read : unexpected end of file");
            }

            bit_no = 0;
         }

         return (byte & (1 << bit_no++) ? 1 : 0);
      }

      /**
       * Lecture d'un groupe de binons
       * @param bits int nombre de binons à lire
       * @return guint32 La valeur lue
       */
      guint32 read_uint(int bits)
      {
         guint32 value = 0;

         for (int i = 0 ; i < bits ; i++)
            value += read() << i;

         return value;
      }

      guint32 read_uint_max(int max)
      {
         if (!max) return 0;
         int bits = bits_to_store(max);

         guint32 value = 0;

         for (int i = 0 ; i < bits - 1 ; i++)
            if (read())
               value += 1<<i;

         if ( (value | (1<<(bits-1))) <= guint32(max) )
            if (read())
               value += 1<<(bits-1);

         return value;
      }
   };

// Coder/decoder for lists of small signed ints:

   const int adapt_level = 8;

   /**
    * Read a list of ints as encoded by "write_list"
    * @param
    * @param
    * @param
    */
   void read_list(std::vector<int> &list, bool base_2_part, bitstream_in &in)
   {
   // Read base 2 part:

      int low_bits = (base_2_part ? in.read_uint(4) : 0);

      for (unsigned int i = 0 ; i < list.size() ; i++) {
         list[i] = in.read_uint(low_bits);
      }

   // Decode bitstream:

      int n_zeros = 0;
      int step = 256;
      bool dominant = false;
      std::vector<guint8> bits;

      while ((unsigned int) n_zeros < list.size()) {
         int steplet = step >> 8;

         if (!in.read()) {
            for (int i = 0 ; i < steplet ; i++) {
               bits.push_back(dominant);
            }

            if (!dominant) {
               n_zeros += steplet;
            }

            step += step / adapt_level;
         }  else {
            int actual_run = in.read_uint_max(steplet - 1);

            for (int i = 0 ; i < actual_run ; i++) {
               bits.push_back(dominant);
            }

            bits.push_back(!dominant);

            if (!dominant) {
               n_zeros += actual_run;
            }

            if (dominant) {
               n_zeros++;
            }

            step -= step / adapt_level;
         }

         if (step < 256) {
            step = 65536 / step;
            dominant = !dominant;
         }
      }

   // Reconstruct (unsigned) values:

      n_zeros = 0;
      int pos   = 0;
      int level = 0;

      for(int i = 0 ; (unsigned int) n_zeros < list.size() ; i++) {
         for (;;) {
            if ((unsigned int) pos >= list.size()) {
               pos = 0;
               level += 1 << low_bits;
            }

            if (list[pos] >= level)
               break;

            pos++;
         }

         if (bits[i])
            list[pos] += 1 << low_bits;
         else
            n_zeros++;

         pos++;
      }

  // Read signs:

     for (unsigned int i = 0 ; i < list.size() ; i++) {
        if (list[i] && in.read()) {
           list[i] = -list[i];
        }
     }
   }

// Accuracy of fixed point calculations
   const int    lattice_shift  = 10,
                sample_shift   = 4,
                lattice_factor = 1 << lattice_shift,
                sample_factor  = 1 << sample_shift,

// Maximum allowable taps
                max_tap        = 2048;

// Default quantization level
   const double base_quant     = 0.6,

// Amount of bit rate variation
                rate_variation = 3.0;


   const int tap_quant[max_tap] = { // int(sqrt(i+1))
      1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4,
      4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6,
      6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
      7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,
     10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
     11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
     11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,
     12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,
     13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,
     14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
     14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
     15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,16,16,
     16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
     16,16,16,16,16,16,16,16,17,17,17,17,17,17,17,17,17,17,17,17,
     17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
     17,17,17,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,
     18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,
     19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,
     19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,20,
     20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
     20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
     21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,
     21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,
     21,21,21,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,
     22,22,22,22,22,22,22,22,22,22,22,22
   };

   inline int shift(int a, int b)
   {
      return a + (1 << b - 1) >> b;
   }

   inline int shift_down(int a, int b)
   {
      return (a >> b) + (a < 0 ? 1 : 0);
   }

   struct lattice
   {
      int order;
      std::vector<int> k, state;

      void init(int _order)
      {
         order = _order;
         k.resize(order);
         state.resize(order);

         for (int i = 0 ; i < order ; i++) {
            state[i] = 0;
            k[i] = 0;
         }
      }

      void dequantize()
      {
         for (int i = 0 ; i < order ; i++) {
            k[i] *= tap_quant[i];
         }
      }

      void init_state()
      {
         for (int i = order - 2 ; i >= 0 ; i--) {
            int x = state[i], new_x;

            for(int j = 0 , p = i + 1 ; p < order ; j++, p++) {
               new_x = x + shift_down(k[j] * state[p], lattice_shift);
               state[p] += shift_down(k[j] * x, lattice_shift);
               x = new_x;
            }
         }
      }

      int advance_by_error(int error)
      {
      // returns value
         int x = error;
         x -= shift_down(k[order-1] * state[order-1], lattice_shift);

         int *k_ptr     = &(k[order-2]),
             *state_ptr = &(state[order-2]);

         for (int i = order - 2 ; i >= 0 ; i--, k_ptr--, state_ptr--) {
           int k_value     = *k_ptr,
               state_value = *state_ptr;
           x -= shift_down(k_value * state_value, lattice_shift);
           state_ptr[1] = state_value + shift_down(k_value * x, lattice_shift);
         }

      // Don't drift too far, to avoid overflows
         if (x >  (sample_factor << 16)) x =  (sample_factor << 16);
         if (x < -(sample_factor << 16)) x = -(sample_factor << 16);

         state[0] = x;

         return x;
      }
   };

   struct decoder
   {
      FILE *f_in;
      bitstream_in bit_in;
      int length, length_remaining, rate;
      int channels;
      bool lossless;
      bool mid_side;
      int n_taps;
      int down_sampling, samples_per_packet;

      lattice predictor;
      std::vector< std::vector<int> > predictor_initer;

      void begin(FILE *_f_in)
      {
         f_in = bonk_file;

         length             = bonk_header.length            ;
         rate               = bonk_header.rate              ;
         channels           = bonk_header.channels          ;
         lossless           = bonk_header.lossless          ;
         mid_side           = bonk_header.mid_side          ;
         n_taps             = bonk_header.n_taps            ;
         down_sampling      = bonk_header.down_sampling     ;
         samples_per_packet = bonk_header.samples_per_packet;

         if (channels == 0 || (channels < 2 && mid_side) || n_taps > max_tap || n_taps == 0 || down_sampling == 0 || samples_per_packet == 0) {
            bonk_xmms__log(__LINE__, __FUNCTION__, "Bonk file has strange settings");
            return;
         }

         predictor.init(n_taps);
         predictor_initer.resize(channels);

         for (int i = 0 ; i < channels ; i++) {
            predictor_initer[i].resize(n_taps);

            for (int j = 0 ; j < n_taps ; j++) {
               predictor_initer[i][j] = 0;
            }
         }

         length_remaining = length;

         bit_in.setup(f_in);
      }

      void read_packet(std::vector<int> &samples)
      {
//       bonk_xmms__log(__LINE__, __FUNCTION__, "decoder::read_packet : ftell(bit_in.f_in) == %10ld : length_remaining == %10d", ftell(bit_in.f_in), length_remaining); // DEVEL
//       bonk_xmms__log(__LINE__, __FUNCTION__, "decoder::read_packet : bit_in.byte == %d : bit_in.bit_no == %d", bit_in.byte, bit_in.bit_no); // DEVEL

         samples.resize(samples_per_packet * down_sampling * channels);

         std::vector<int> input_samples(samples_per_packet);

         read_list(predictor.k, false, bit_in);

         predictor.dequantize();

         int quant = (lossless ? 1 : bit_in.read_uint(16) * sample_factor);

         for (int channel = 0 ; channel < channels ; channel++) {
            int *sample = &(samples[channel]);

            predictor.state = predictor_initer[channel];
            predictor.init_state();

            read_list(input_samples, true, bit_in);

            for (int i = 0 ; i < samples_per_packet ; i++) {
               for (int j = 0 ; j < down_sampling - 1 ; j++) {
                  *sample = predictor.advance_by_error(0);
                  sample += channels;
               }

               *sample = predictor.advance_by_error(input_samples[i] * quant);
               sample += channels;
            }

            for (int i = 0 ; i < n_taps ; i++) {
               predictor_initer[channel][i] = samples[samples.size() - channels + channel - i * channels];
            }
         }


         if (mid_side) {
            for(unsigned int i = 0 ; i < samples.size() ; i += channels) {
               samples[i + 1] += shift(samples[i], 1);
               samples[i] -= samples[i + 1];
            }
         }

         if (!lossless) {
            for(unsigned int i = 0 ; i < samples.size() ; i++) {
               samples[i] = shift(samples[i], sample_shift);
            }
         }

         if ((unsigned int) length_remaining < samples.size()) {
            samples.resize(length_remaining);
            length_remaining = 0;
         }  else {
            length_remaining -= samples.size();
         }
      }

   };

// =============================================================================
// End of original bonk source code
// =============================================================================

   extern "C"
   {
      InputPlugin *get_iplugin_info(void)
      {
         bonk_ip.description = g_strdup_printf("Bonk player "
#ifdef DEBUG
            "(%s %s)", __DATE__, __TIME__
#else
            VERSION
#endif
         );

         return &bonk_ip;
      }
   }

   /**
    * Indique si un fichier est au format bonk
    * @param  char* Nom du fichier
    * @return int 1 si le fichier est au format bonk, 0 sinon
    */
   int bonk_xmms__is_our_file(char *filename)
   {
      BONKHEADER bonkheader;
      FILE *bonk = fopen(filename, "r");
      char *extension = strrchr(filename, '.');
      int is_our_file;

      if ((extension == NULL) || (strcasecmp(extension, ".bonk") != 0)) {
         return 0;
      }

      if (bonk == (FILE *) NULL) {
         bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fopen");
         return 0;
      }

      if (bonkheader_read(&bonkheader, bonk) > -1) {
         is_our_file = 1;
      }  else {
         bonk_xmms__log(__LINE__, __FUNCTION__, "bonkheader_read a renvoyé -1");
         is_our_file = 0;
      }

      fclose(bonk);

      return is_our_file;
   }

   /**
    * Lecture des informations du fichier
    * @param in  filename char *  Nom du fichier
    * @param out title    char ** Informations sur la plage
    * @param out length   int *   Longueur en millisecondes de la plage
    */
   void bonk_xmms__get_song_info(char *filename, char **title, int *length)
   {
      BONKHEADER bonkheader;
      long bonk_tag_offset;
      char *temp;
      FILE *bonk;
      int result;
      size_t count;

      bonk = fopen(filename, "r"); if (bonk == (FILE *) NULL) return;

      bonk_tag_offset = bonkheader_read(&bonkheader, bonk); if (bonk_tag_offset < 0) return;

      if (title != NULL) {
         if (bonk_tag_offset > 0) {
         // décodage des informations contenues dans le fichier
            *title = (char *) g_malloc(bonk_tag_offset + 1);

            result = fseek(bonk, 0L, SEEK_SET); if (result != 0) { bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fseek"); return; }
            count  = fread(*title, sizeof(char), bonk_tag_offset, bonk); if ((long) count != bonk_tag_offset) { bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread"); return; }

            if (strncmp(*title, tag_artist, strlen(tag_artist)) == 0) {
               (void) memmove(*title, *title + strlen(tag_artist), bonk_tag_offset - strlen(tag_artist));
            }

            temp = strstr(*title, tag_title);

            if (temp != (char *) NULL) {
               temp[0] = ' '; temp[1] = '-'; temp[2] = ' ';
               (void) memmove(temp+ 3, temp + strlen(tag_artist), strlen(temp) - strlen(tag_artist));
            }

            temp = strchr(*title, '\n'); if (temp != (char *) NULL) *temp = '\0';

         }  else {
         // pas d'informations, nous utilisons donc le nom du fichier
            temp = strrchr(filename, '/');

            if (temp == (char *) NULL) {
               temp = filename;
            }  else {
               temp++;
            }

            *title = (char *) g_malloc(strlen(temp) + 1);
            (void) strcpy(*title, temp);
            temp = strrchr(*title, '.'); if (temp != (char *) NULL) *temp = '\0';
         }
      }

   // -- Longueur en millisecondes --
      if (length != NULL) {
         *length = (int) (1000.0 * (float) bonkheader.length / (float) bonkheader.rate / (float) bonkheader.channels);
      }

   // -- Fermeture du fichier
      fclose(bonk);
   }


   /**
    * Initialisation
    */
   static void bonk_xmms__init(void)
   {
   }

   /**
    * Informations sur ce plugin
    */
   static void bonk_xmms__about(void)
   {
      static GtkWidget *box;

      box = xmms_show_message("xmms-bonk " VERSION, "bonk format by\nPaul Harrison <pfh@csse.monash.edu.au>\n\nxmms interface by\nFabrice Haberer-Proust <info@proustmedia.de>", "OK", FALSE, NULL, NULL);

//    gtk_signal_connect(GTK_OBJECT(box), "destroy", gtk_widget_destroyed, &box);
   }

   /**
    * Lecture du fichier bonk
    * @param *arg void Paramètre inutilisé, mais nécessaire pour respecter le prototype de la fonction pthread_create
    */
   static void *play_loop(void *arg)
   {
      AFormat fmt = (G_BYTE_ORDER == G_BIG_ENDIAN) ? FMT_S16_BE : FMT_S16_LE;
      decoder deco;

      if (bonk_file == (FILE *) NULL) {
         bonk_xmms__log(__LINE__, __FUNCTION__, "bonk_file == NULL");
         return NULL;
      }

      srand(time(0));
      deco.begin(bonk_file);

   // -- Découpage des paquets --
      int slice_index = 0;
      int slice_count = 8;

      packet_index = 0;

   // -- Initialisation du sommaire --
      toc_entry_current = 0;
      toc.clear();
      toc.push_back(toc_entry(ftell(deco.bit_in.f_in), deco.bit_in.byte, deco.bit_in.bit_no, deco.length_remaining));

   // -- Code bonk --
      std::vector<int> samples;
		std::vector<gint16> little_samples;

      while (bonk_file_playing == 1) {
         if (deco.length_remaining) {
         // -- Lecture du paquet --
            if (slice_index == 0) {
               deco.read_packet(samples);
               unsigned int sec = (int) (((float)packet_index * (float)bonk_header.samples_per_packet) / (float)bonk_header.rate); // conversion n° de paquet -> seconde
               toc_entry_current = sec;
               if (sec > toc.size() - 1) {
                  bonk_xmms__log(__LINE__, __FUNCTION__, "toc_entry_current == %3d - packet_index == %5d", toc_entry_current, packet_index);

                  toc.push_back(toc_entry(ftell(deco.bit_in.f_in), deco.bit_in.byte, deco.bit_in.bit_no, deco.length_remaining));
               }
               packet_index++;

               little_samples.resize(samples.size());

               for (unsigned int i = 0 ; i < samples.size() ; i++) {
                  if (samples[i] > 32767) {
                     little_samples[i] = 32767;
                  }  else if (samples[i] < -32768) {
                     little_samples[i] = -32768;
                  }  else {
                     little_samples[i] = samples[i];
                  }
               }
            }

         // -- Temporisation --
            while (((unsigned int) bonk_ip.output->buffer_free() < 2 * little_samples.size() * slice_count) && (bonk_file_playing == 1) && (bonk_file_seek_to == -1)) {
               xmms_usleep(10000);
            }

         // -- Données de visualisation --
            bonk_ip.add_vis_pcm(bonk_ip.output->written_time(), fmt, samples.size() * sizeof(gint16) / slice_count, samples.size() * sizeof(gint16) * 2048 / slice_count, &little_samples[samples.size() * slice_index / slice_count]);

         // -- Écriture sur le phériphérique audio --
            for (unsigned int i = samples.size() * slice_index / slice_count ; i < samples.size() * (slice_index + 1) / slice_count ; i++) {
               if ((bonk_file_playing == 1) && (bonk_file_seek_to == -1)) {
                  bonk_ip.output->write_audio(&(little_samples[i]), sizeof(little_samples[i]));
               }  else {
                  bonk_xmms__log(__LINE__, __FUNCTION__, "break");
                  break;
               }
            }

         // -- Tranche suivante du paquet --
            slice_index = (slice_index + 1) % slice_count;

         // -- Changement de position --
            if (bonk_file_seek_to != -1) {
               unsigned int pos = bonk_file_seek_to;
               pos = (pos < toc.size()) ? pos : toc.size() - 1;
               toc_entry_current = pos;

               packet_index = pos * bonk_header.rate / bonk_header.samples_per_packet; // TODO: attention au débordement d'entiers

               bonk_xmms__log(__LINE__, __FUNCTION__, "CHANGEMENT DE bonk_file_seek_to");
               bonk_xmms__log(__LINE__, __FUNCTION__, "toc_entry_current == %3d - packet_index == %5d", toc_entry_current, packet_index);

               fseek(bonk_file, toc[pos].file_position, SEEK_SET);
               deco.length_remaining = toc[pos].length_remaining;
               deco.bit_in.byte   = toc[pos].byte  ;
               deco.bit_in.bit_no = toc[pos].bit_no;
               slice_index = 0;

               bonk_file_seek_to = -1;
            }
         }  else {
            bonk_xmms__log(__LINE__, __FUNCTION__, "fin de la lecture des paquets");
            while (bonk_ip.output->buffer_playing()) xmms_usleep(20000);
            break;
         }
      }

      bonk_xmms__log(__LINE__, __FUNCTION__, "toc.size() : %d", toc.size());

      bonk_ip.output->close_audio();
      fclose(bonk_file);
      bonk_file = (FILE *) NULL;
      bonk_file_playing = 0;
      pthread_exit(NULL);
   }

   /**
    * Lecture d'un fichier
    * @param filename char * Nom du fichier à jouer
    */
   static void bonk_xmms__play_file(char *filename)
   {
   // -- Déclarations --
      long    bonk_tag_offset;
      long    bonk_data_offset;
      char   *bonk_artist_title;
      char   *temp;
      int     result;
      size_t  count;
      AFormat fmt = (G_BYTE_ORDER == G_BIG_ENDIAN) ? FMT_S16_BE : FMT_S16_LE;
   // -- Ouverture du fichier et lecture de son en-tête --
      bonk_file = fopen(filename, "r");

      bonk_tag_offset = bonkheader_read(&bonk_header, bonk_file);

      if (bonk_tag_offset < 0) {
         bonk_xmms__log(__LINE__, __FUNCTION__, "le fichier n'est pas au format bonk");
         return;
      }

      bonk_data_offset = ftell(bonk_file);

      if (bonk_data_offset < 0) {
         bonk_xmms__log(__LINE__, __FUNCTION__, "échec de ftell");
         fclose(bonk_file);
         bonk_file = (FILE *) NULL;
         return;
      }

   // -- Informations sur le fichier --
   // TODO: regrouper ce code avec celui de bonk_xmms__get_song_info

      if (bonk_tag_offset > 0) {
      // décodage des informations contenues dans le fichier
         bonk_artist_title = new char[bonk_tag_offset + 1];

         result = fseek(bonk_file, 0L, SEEK_SET); if (result != 0) {
            bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fseek");
            fclose(bonk_file);
            bonk_file = (FILE *) NULL;
            return;
         }

         count  = fread(bonk_artist_title, sizeof(char), bonk_tag_offset, bonk_file);

         if ((long) count != bonk_tag_offset) {
            bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fread");
            fclose(bonk_file);
            bonk_file = (FILE *) NULL;
            return;
         }

         if (strncmp(bonk_artist_title, tag_artist, strlen(tag_artist)) == 0) {
            (void) memmove(bonk_artist_title, bonk_artist_title + strlen(tag_artist), bonk_tag_offset - strlen(tag_artist));
         }

         temp = strstr(bonk_artist_title, tag_title);

         if (temp != (char *) NULL) {
            temp[0] = ' '; temp[1] = '-'; temp[2] = ' ';
            (void) memmove(temp+ 3, temp + strlen(tag_artist), strlen(temp) - strlen(tag_artist));
         }

         temp = strchr(bonk_artist_title, '\n'); if (temp != (char *) NULL) *temp = '\0';

         bonk_xmms__log(__LINE__, __FUNCTION__, bonk_artist_title); // DEVEL
      }  else {
      // pas d'informations, nous utilisons donc le nom du fichier
         temp = strrchr(filename, '/');

         if (temp == (char *) NULL) {
            temp = filename;
         }  else {
            temp++;
         }

         bonk_artist_title = new char[strlen(temp) + 1];
         (void) strcpy(bonk_artist_title, temp);
         temp = strrchr(bonk_artist_title, '.'); if (temp != (char *) NULL) *temp = '\0';
      }

   // -- Positionnement au début des données --
      result = fseek(bonk_file, bonk_data_offset, SEEK_SET); // TODO: vérifier le résultat

      if (result != 0) {
         bonk_xmms__log(__LINE__, __FUNCTION__, "échec de fseek");
         fclose(bonk_file);
         bonk_file = (FILE *) NULL;
         return;
      }

   // -- Ouverture du périphérique audio --
      if (bonk_ip.output->open_audio(fmt, bonk_header.rate, bonk_header.channels) == 0) {
         bonk_xmms__log(__LINE__, __FUNCTION__, "impossible d'ouvrir le périphérique audio");
         audio_error = TRUE;
         fclose(bonk_file);
         bonk_file = (FILE *) NULL;
         return;
      }

   // -- Affichage des informations du fichier --
      bonk_ip.set_info(bonk_artist_title, (int) (1000.0 * (float) bonk_header.length / (float) bonk_header.rate / (float) bonk_header.channels), 8, bonk_header.rate, bonk_header.channels);
      delete[] bonk_artist_title;

   // -- --
      bonk_file_playing = 1;
      bonk_file_seek_to = -1; // DEVEL

   // -- Lecture dans un nouveau thread --
      bonk_xmms__log(__LINE__, __FUNCTION__, "appel de play_loop dans un nouveau thread");
      pthread_create(&decode_thread, NULL, play_loop, NULL);
   }

   /**
    * Arrêt de la lecture
    */
   static void bonk_xmms__stop(void)
   {
      if (bonk_file_playing == 1) {
         bonk_file_playing = 0;
         bonk_xmms__log(__LINE__, __FUNCTION__, "Arrêt par l'utilisateur");
         pthread_join(decode_thread, NULL);
         bonk_ip.output->close_audio();
      }
   }

   /**
    * Pause
    * @param paused short État de pause
    */
   static void bonk_xmms__pause(short paused)
   {
      if (bonk_file_playing) {
         bonk_ip.output->pause(paused);
      }
   }

   /**
    * Déplacement dans la plage
    * @param time int Position en secondes à atteindre
    */
   static void bonk_xmms__seek(int time)
   {
      bonk_xmms__log(__LINE__, __FUNCTION__, "time == %d  -  toc.size() = %d", time, toc.size());
      bonk_xmms__log(__LINE__, __FUNCTION__, "((time <= toc.size()) ? time : toc.size()) == %d", (((unsigned int) time <= toc.size()) ? time : toc.size()));

      bonk_file_seek_to = time;

      bonk_ip.output->flush(1000 * (((unsigned int) time <= toc.size()) ? time : toc.size()));

//    while (bonk_file_seek_to != -1) {
//       bonk_xmms__log(__LINE__, __FUNCTION__, "bonk_file_seek_to : %d", bonk_file_seek_to); // DEVEL
//       xmms_usleep(10000);
//    }

      bonk_xmms__log(__LINE__, __FUNCTION__, "après while..."); // DEVEL
      bonk_xmms__log(__LINE__, __FUNCTION__, "toc_entry_current == %d", toc_entry_current);
   }

   /**
    * Renvoi de la position de lecture en cours
    * @return int La position en secondes, -1 ou -2 en cas d'erreur
    */
   static int bonk_xmms__get_time(void)
   {
      if (audio_error) {
         return -2;
      }

      if ((bonk_file_playing == 1) || (bonk_ip.output->buffer_playing())) {
         return bonk_ip.output->output_time();
      }

      return -1;
   }
