//+------------------------------------------------------------------+
//|                                                  CDictionary.mqh |
//|                                 Copyright 2015, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, Vasiliy Sokolov."
#property link      "http://www.mql5.com"

#include <Object.mqh>
#include <Arrays\List.mqh>
//+------------------------------------------------------------------+
//| Контейнер для хранения элементов CObject                         |
//+------------------------------------------------------------------+
class KeyValuePair : public CObject
  {
private:
   string            m_string_key;    // Хранит строковый ключ.
   double            m_double_key;    // Хранит ключ с плавающей запятой.
   ulong             m_ulong_key;     // Хранит безнаковый целочисленный ключ.
   ulong             m_hash;
   bool              m_free_mode;     // Режим освобождения памяти объекта
public:
   CObject          *object;
   KeyValuePair     *next_kvp;
   KeyValuePair     *prev_kvp;
   template<typename T>
                     KeyValuePair(T key,ulong hash,CObject *obj);
                    ~KeyValuePair();
   template<typename T>
   bool              EqualKey(T key);
   template<typename T>
   void              GetKey(T &gkey);
   ulong             GetHash(){return m_hash;}
   void              FreeMode(bool free_mode){m_free_mode=free_mode;}
   bool              FreeMode(void){return m_free_mode;}
  };
//+------------------------------------------------------------------+
//| Конструктор по умолчанию.                                        |
//+------------------------------------------------------------------+
template<typename T>
void KeyValuePair::KeyValuePair(T key,ulong hash,CObject *obj)
  {
   m_hash=hash;
   string name=typename(key);
   if(name=="string")
      m_string_key=(string)key;
   else if(name=="double" || name=="float")
      m_double_key=(double)key;
   else
      m_ulong_key=(ulong)key;
   object=obj;
   m_free_mode=true;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
template<typename T>
KeyValuePair::GetKey(T &gkey)
  {
   string name=typename(gkey);
   if(name=="string")
      gkey=(T)m_string_key;
   else if(name=="double" || name=="float")
      gkey=(T)m_double_key;
   else
      gkey=(T)m_ulong_key;
  }
//+------------------------------------------------------------------+
//| Диструктор.                                                      |
//+------------------------------------------------------------------+
KeyValuePair::~KeyValuePair()
  {
   if(m_free_mode)
      delete object;
  }
//+------------------------------------------------------------------+
//| Возвращает истину, если ключи равны.                             |
//+------------------------------------------------------------------+
template<typename T>
bool KeyValuePair::EqualKey(T key)
  {
   string name=typename(key);
   if(name=="string")
      return m_string_key == (string)key;
   if(name=="double" || name=="float")
      return m_double_key == (double)key;
   else
      return m_ulong_key == (ulong)key;
  }
//+------------------------------------------------------------------+
//| Ассоциативный массив или словарь, хранящий элементы в виде       |
//| <ключ - значение>. Где ключом может являться любой базовый тип,  |
//| а значением - объект типа CObject.                               |
//+------------------------------------------------------------------+
class CDictionary : public CObject
  {
private:
   int               m_array_size;
   int               m_total;
   bool              m_free_mode;
   bool              m_auto_free;
   int               m_index;
   ulong             m_hash;
   CList            *m_array[];
   union casting_struct
     {
      double            d_value;
      ulong             l_value;
     }
   casting;
   KeyValuePair     *m_first_kvp;
   KeyValuePair     *m_current_kvp;
   KeyValuePair     *m_last_kvp;

   ulong             Adler32(string line);
   int               GetIndexByHash(ulong hash);
   template<typename T>
   ulong             GetHashByKey(T key);
   void              Resize();
   int               FindNextSimpleNumber(int number);
   int               FindNextLevel();
   void              Init(int capacity);

public:
                     CDictionary();
                     CDictionary(int capacity);
                    ~CDictionary();
   void              Compress(void);
   ///
   /// Returns the total number of items.
   ///
   int Total(void){return m_total;}
   /// Returns the element at key    
   template<typename T>
   CObject          *GetObjectByKey(T key);
   template<typename T>
   bool              AddObject(T key,CObject *value);
   template<typename T>
   bool              DeleteObjectByKey(T key);
   template<typename T>
   bool              ContainsKey(T key);
   template<typename T>
   void              GetCurrentKey(T &key);
   bool              DeleteCurrentNode(void);
   bool              FreeMode(void) { return(m_free_mode); }
   void              FreeMode(bool free_mode);
   void              AutoFreeMemory(bool autoFree){m_auto_free=autoFree;}
   void              Clear();

   CObject          *GetNextNode(void);
   CObject          *GetPrevNode(void);
   CObject          *GetCurrentNode(void);
   CObject          *GetFirstNode(void);
   CObject          *GetLastNode(void);

  };
//+------------------------------------------------------------------+
//| Конструктор по умолчанию.                                        |
//+------------------------------------------------------------------+
CDictionary::CDictionary()
  {
   Init(3);
   m_free_mode= true;
   m_auto_free=true;
  }
//+------------------------------------------------------------------+
//| Создает словарь, с заранее определенной емкостью capacity.       |
//+------------------------------------------------------------------+
CDictionary::CDictionary(int capacity)
  {
   if(capacity<3)
      Init(3);
   else
      Init(capacity);
   m_free_mode= true;
   m_auto_free=true;
  }
//+------------------------------------------------------------------+
//| Диструктор.                                                      |
//+------------------------------------------------------------------+
CDictionary::~CDictionary()
  {
   Clear();
  }
//+------------------------------------------------------------------+
//| Режим устанавливает режим памяти для всех подузлов               |
//+------------------------------------------------------------------+  
void CDictionary::FreeMode(bool free_mode)
  {
   if(free_mode==m_free_mode)
      return;
   m_free_mode=free_mode;
   for(int i=0; i<ArraySize(m_array); i++)
     {
      CList *list=m_array[i];
      if(CheckPointer(list)==POINTER_INVALID)
         continue;
      for(KeyValuePair *kvp=list.GetFirstNode(); kvp!=NULL; kvp=list.GetNextNode())
         kvp.FreeMode(m_free_mode);
     }
  }
//+------------------------------------------------------------------+
//| Выполняет инициализацию словаря.                                 |
//+------------------------------------------------------------------+
void CDictionary::Init(int capacity)
  {
   m_array_size=ArrayResize(m_array,capacity);
   m_index= 0;
   m_hash = 0;
   m_total=0;
  }
//+------------------------------------------------------------------+
//| Находит следующий размер для словаря.                            |
//+------------------------------------------------------------------+
int CDictionary::FindNextLevel()
  {
   double value=4;
   for(int i=2; i<=31; i++)
     {
      value=MathPow(2.0,(double)i);
      if(value > m_total)return (int)value;
     }
   return (int)value;
  }
//+------------------------------------------------------------------+
//| Принимает строку и возвращает хэширующее 32 битное число,        |
//| характеризующее эту строку.                                      |
//+------------------------------------------------------------------+
ulong CDictionary::Adler32(string line)
  {
   ulong s1 = 1;
   ulong s2 = 0;
   uint buflength=StringLen(line);
   uchar char_array[];
   ArrayResize(char_array,buflength,0);
   StringToCharArray(line,char_array,0,-1,CP_ACP);
   for(uint n=0; n<buflength; n++)
     {
      s1 = (s1 + char_array[n]) % 65521;
      s2 = (s2 + s1)     % 65521;
     }
   return ((s2 << 16) + s1);
  }
//+------------------------------------------------------------------+
//| Рассчитывает хэш на основе переданного ключа. Ключом может быть  |
//| любой базовый MQL тип.
//+------------------------------------------------------------------+
template<typename T>
ulong CDictionary::GetHashByKey(T key)
  {
   ulong ukey = 0;
   string name=typename(key);
   if(name=="string")
      return Adler32((string)key);
   if(name=="double" || name=="float")
     {
      casting.d_value=(double)key;
      ukey=casting.l_value;
     }
   else
      ukey=(ulong)key;
   return ukey;
  }
//+------------------------------------------------------------------+
//| Возвращает ключ текущего элемента.                               |
//+------------------------------------------------------------------+
template<typename T>
void CDictionary::GetCurrentKey(T &key)
  {
   m_current_kvp.GetKey(key);
  }
//+------------------------------------------------------------------+
//| Возвращает индекс по ключу.                                      |
//+------------------------------------------------------------------+
int CDictionary::GetIndexByHash(ulong key)
  {
   return (int)(key%m_array_size);
  }
//+------------------------------------------------------------------+
//| Очищает словарь от всех значений.                                |
//+------------------------------------------------------------------+
void CDictionary::Clear(void)
  {
   int size=ArraySize(m_array);
   for(int i=0; i<size; i++)
     {
      if(CheckPointer(m_array[i])!=POINTER_INVALID)
        {
         m_array[i].FreeMode(true); // Элементы типа KeyValuePair удаляются всегда
         delete m_array[i];
        }
     }
   ArrayFree(m_array);
   if(m_auto_free)
      Init(3);
   else
      Init(size);
   m_first_kvp=m_last_kvp=m_current_kvp=NULL;
  }
//+------------------------------------------------------------------+
//| Переразмечает контейнер хранения данных.                         |
//+------------------------------------------------------------------+
void CDictionary::Resize(void)
  {
   int level=FindNextLevel();
   int n=level;
   CList *temp_array[];
   ArrayCopy(temp_array,m_array);
   ArrayFree(m_array);
   m_array_size=ArrayResize(m_array,n);
   int total=ArraySize(temp_array);
   KeyValuePair *kv=NULL;
   for(int i=0; i<total; i++)
     {
      if(temp_array[i]==NULL)continue;
      CList *list=temp_array[i];
      int count=list.Total();
      list.FreeMode(false);
      kv=list.GetFirstNode();
      while(kv!=NULL)
        {
         int index=GetIndexByHash(kv.GetHash());
         if(CheckPointer(m_array[index])==POINTER_INVALID)
           {
            m_array[index]=new CList();
            m_array[index].FreeMode(true);   // Элементы KeyValuePair удаляются всегда
           }
         list.DetachCurrent();
         m_array[index].Add(kv);
         kv=list.GetCurrentNode();
        }
      delete list;
     }
   int size=ArraySize(temp_array);
   ArrayFree(temp_array);
  }
//+------------------------------------------------------------------+
//| Сжимает словарь.                                                 |
//+------------------------------------------------------------------+
CDictionary::Compress(void)
  {
   if(!m_auto_free)return;
   double koeff=m_array_size/(double)(m_total+1);
   if(koeff < 2.0 || m_total <= 4)return;
   Resize();
  }
//+------------------------------------------------------------------+
//| Возвращет объект по ключу.                                       |
//+------------------------------------------------------------------+
template<typename T>
CObject *CDictionary::GetObjectByKey(T key)
  {
   if(!ContainsKey(key))
      return NULL;
   CObject *obj=m_current_kvp.object;
   return obj;
  }
//+------------------------------------------------------------------+
//| Проверяет содержит ли словарь ключ произвольного типа T.         |
//| RETURNS:                                                         |
//|   Возвращает истину, если объект с таким ключом уже существует   |
//|   и ложь в противном случае.                                     |
//+------------------------------------------------------------------+
template<typename T>
bool CDictionary::ContainsKey(T key)
  {
   m_hash=GetHashByKey(key);
   m_index=GetIndexByHash(m_hash);
   if(CheckPointer(m_array[m_index])==POINTER_INVALID)
      return false;
   CList *list=m_array[m_index];
   KeyValuePair *current_kvp=list.GetCurrentNode();
   if(current_kvp == NULL)return false;
   if(current_kvp.EqualKey(key))
     {
      m_current_kvp=current_kvp;
      return true;
     }
   current_kvp=list.GetFirstNode();
   while(true)
     {
      if(current_kvp.EqualKey(key))
        {
         m_current_kvp=current_kvp;
         return true;
        }
      current_kvp=list.GetNextNode();
      if(current_kvp==NULL)
         return false;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Добавляет в словарь элемент типа CObject с ключом T key.         |
//| INPUT PARAMETRS:                                                 |
//|   T key - любой базовый тип, например int, double или string.    |
//|   value - класс, производный от CObject.                         |
//| RETURNS:                                                         |
//|   Истина, если элемент был добавлен и ложь в противном случае.   |
//+------------------------------------------------------------------+
template<typename T>
bool CDictionary::AddObject(T key,CObject *value)
  {
   if(ContainsKey(key))
      return false;
   if(m_total==m_array_size)
     {
      Resize();
      ContainsKey(key);
     }
   if(CheckPointer(m_array[m_index])==POINTER_INVALID)
     {
      m_array[m_index]=new CList();
      m_array[m_index].FreeMode(true);   // Элементы KeyValuePair удаляются всегда
     }
   KeyValuePair *kv=new KeyValuePair(key,m_hash,value);
   kv.FreeMode(m_free_mode);
   if(m_array[m_index].Add(kv)!=-1)
      m_total++;
   if(CheckPointer(m_current_kvp)==POINTER_INVALID)
     {
      m_first_kvp=kv;
      m_current_kvp=kv;
      m_last_kvp=kv;
     }
   else
     {
      //добавляем в самый конец, т.к. текущий узел может быть где угодно
      while(m_current_kvp.next_kvp!=NULL)
         m_current_kvp=m_current_kvp.next_kvp;
      m_current_kvp.next_kvp=kv;
      kv.prev_kvp=m_current_kvp;
      m_current_kvp=kv;
      m_last_kvp=kv;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Возвращает текущий объект. Если объект не выбран возвращает      |
//| NULL.                                                            |
//+------------------------------------------------------------------+
CObject *CDictionary::GetCurrentNode(void)
  {
   if(m_current_kvp==NULL)
      return NULL;
   return m_current_kvp.object;
  }
//+------------------------------------------------------------------+
//| Возвращает предыдущий объект. После вызова метода текущий        |
//| объект становиться предыдущим. Если объект не выбран, возвращает |
//| NULL.                                                            |
//+------------------------------------------------------------------+
CObject *CDictionary:: GetPrevNode(void)
  {
   if(m_current_kvp==NULL)
      return NULL;
   if(m_current_kvp.prev_kvp==NULL)
      return NULL;
   KeyValuePair *kvp=m_current_kvp.prev_kvp;
   m_current_kvp=kvp;
   return kvp.object;
  }
//+------------------------------------------------------------------+
//| Возвращает следующий объект. После вызова метода текущий         |
//| объект становиться следующим. Если объект не выбран, возвращает  |
//| NULL.                                                            |
//+------------------------------------------------------------------+
CObject *CDictionary::GetNextNode(void)
  {
   if(m_current_kvp==NULL)
      return NULL;
   if(m_current_kvp.next_kvp==NULL)
      return NULL;
   m_current_kvp=m_current_kvp.next_kvp;
   return m_current_kvp.object;
  }
//+------------------------------------------------------------------+
//| Возвращает первый узел в списке узлов. Если в словаре узлов нет, |
//| возвращает NULL.                                                 |
//+------------------------------------------------------------------+
CObject *CDictionary::GetFirstNode(void)
  {
   if(m_first_kvp==NULL)
      return NULL;
   m_current_kvp=m_first_kvp;
   return m_first_kvp.object;
  }
//+------------------------------------------------------------------+
//| Возвращает последний узел в списке узлов. Если в словаре узлов   |
//| нет возвращает NULL.                                             |
//+------------------------------------------------------------------+
CObject *CDictionary::GetLastNode(void)
  {
   if(m_last_kvp==NULL)
      return NULL;
   m_current_kvp=m_last_kvp;
   return m_last_kvp.object;
  }
//+------------------------------------------------------------------+
//| Удаляет текущий узел                                             |
//+------------------------------------------------------------------+
bool CDictionary::DeleteCurrentNode(void)
  {
   if(m_current_kvp==NULL)
      return false;
   KeyValuePair* p_kvp = m_current_kvp.prev_kvp;
   KeyValuePair* n_kvp = m_current_kvp.next_kvp;
   if(CheckPointer(p_kvp)!=POINTER_INVALID)
      p_kvp.next_kvp=n_kvp;
   if(CheckPointer(n_kvp)!=POINTER_INVALID)
      n_kvp.prev_kvp=p_kvp;
   m_array[m_index].FreeMode(m_free_mode);
   bool res=m_array[m_index].DeleteCurrent();
   if(res)
     {
      m_total--;
      Compress();
     }
   return res;
  }
//+------------------------------------------------------------------+
//| Удаляет объект с ключом key из словаря.                          |
//+------------------------------------------------------------------+
template<typename T>
bool CDictionary::DeleteObjectByKey(T key)
  {
   if(!ContainsKey(key))
      return false;
   return DeleteCurrentNode();
  }

#define FOREACH_DICT(dict) for(CObject* node = (dict).GetFirstNode(); node != NULL; node = (dict).GetNextNode())
//+------------------------------------------------------------------+
