#ifndef __SOM_H__
#define __SOM_H__

#include "dbtmpfeature.h"
#include <math.h>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <vector>
#include <map>
#include <expat.h>
#include <string.h>
#include <pthread.h>
#include "common.h"
#include <algorithm>
#include "timer.h"

#ifndef PI
#define PI 3.14159265358979323846
#endif

struct somupdate_td;
struct somclassify_td
{
  pthread_t tid;
  double **f;  ///< feature vector
  pthread_barrier_t *bar;
  pthread_mutex_t *lock;

  std::map<int, double*> todo;
  
  int bid;
  double bs;
  int vectorsz;

  int run;
};

class som
{
 public:
  som();
  ~som();
  int init( dbtmpfeature *db, 
            int sz,
            std::vector< int > &dims );
  int init( dbtmpfeature *db, 
            std::string state);
  int run( std::string file );
  int classify( double *f,
                std::vector< int > &coor );
  int clear();
  int xmlout( std::ofstream &of );
  int savestate( int time, int maxtime, std::string file );

  inline int encodecoor( std::vector< int > &coor )
  {
    int ret;
    ret = 0;
    for(int i = 0; i < somdims_size; i++ )
      ret = ret + dimmult[i]*coor[i];
    return ret;
  }

  inline int decodecoor( int e, std::vector< int > &coor )
  {
    coor.resize( somdims_size );
    for(int i = 0; i < somdims_size; i++)
      {
        coor[i] = e / dimmult[i];
        e = e % dimmult[i];
      }
    return 0;
  }

  inline double distance( std::vector< int > &a,
                          std::vector< int > &b )
  {
    double res;
    
#ifdef __SOMDEBUG__
    if( a.size() != b.size() )
      {
        std::cout << "SOM ERROR dist size error\n" << std::flush;
        exit(0);
      }
#endif
    
    res = 0;
    unsigned int mi;
    mi = a.size();
    for( unsigned i = 0; i < mi; i++ )
      res = res + pow( a[i] - b[i], 2);
    res = sqrt( res );
    return res;
  }

  inline int getsomsize( std::vector< int > &size )
  {
    size = somdims;
    return 0;
  }

  int checkspecial( std::vector< int > &node );

  int clearsomgridvec();
  int initsomgridvec();

 private:
  int clearcache();

  int classifyt( double *f, 
                 std::vector< int > &coor );

  int endthreads();
  int startthreads( int numthreads );

  int loadcache( unsigned int low, unsigned int high );
  int update( double time, double *f );
  double maxdis;
  dbtmpfeature *dbf;
  std::map< int, double* > somgrid;

  double **somgridvec;
  int numbernodes;

  //std::map< unsigned int, double* > fcache;
  std::vector< double* > fcache;

  int curtime;
  int maxtime;

  int vectorsz;
  std::vector< int > somdims;
  std::vector< int > dimmult;

  int somdims_size;

  double *curfeature;
  double curdis;

  std::vector< std::vector<int> > exclude;  ///< list of nodes to be excluded
  std::vector< std::vector<int> > special;  ///< list of special nodes to be counted

  pthread_barrier_t *mybar, *myupdatebar;
  pthread_mutex_t *mylock;
  std::vector< somclassify_td* > threads;
  std::vector< somupdate_td* > updatethread;
};

struct somupdate_td
{
  pthread_t tid;
  double **f;  ///< feature vector
  pthread_barrier_t *bar;

  int gcoorid;
  std::vector< int > *gcoor;
  std::map<int, double*> todo;
  
  double a, n;

  int vectorsz;
  int run;

  som *mysom;
};

void *classify_threaded_start( void *p );
int classify_threaded( somclassify_td *d );

int somupdate_threaded( somupdate_td *data );
void *somupdate_start( void *data );
#endif
