//+------------------------------------------------------------------ß
//|                                                            JAson |
//|    This software is licensed under the MIT https://goo.gl/eyJgHe |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2006-2017"
#property version "1.12"
#property strict

//------------------------------------------------------------------ enum enJAType
enum enJAType { jtUNDEF, jtNULL, jtBOOL, jtINT, jtDBL, jtSTR, jtARRAY, jtOBJ };

//------------------------------------------------------------------ class CJAVal
class CJAVal
  {
public:
   virtual void      Clear(enJAType jt=jtUNDEF, bool savekey=false) { m_parent=NULL; if(!savekey) m_key=""; m_type=jt; m_bv=false; m_iv=0; m_dv=0; m_prec=8; m_sv=""; ArrayResize(m_e, 0, 100); }
   virtual bool      Copy(const CJAVal &a) { m_key=a.m_key; CopyData(a); return true; }
   virtual void      CopyData(const CJAVal& a) { m_type=a.m_type; m_bv=a.m_bv; m_iv=a.m_iv; m_dv=a.m_dv; m_prec=a.m_prec; m_sv=a.m_sv; CopyArr(a); }
   virtual void      CopyArr(const CJAVal& a) { int n=ArrayResize(m_e, ArraySize(a.m_e)); for(int i=0; i<n; i++) { m_e[i]=a.m_e[i]; m_e[i].m_parent=GetPointer(this); } }

public:
   CJAVal            m_e[];
   string            m_key;
   string            m_lkey;
   CJAVal*           m_parent;
   enJAType          m_type;
   bool              m_bv;
   long              m_iv;
   double            m_dv;
   int               m_prec;
   string            m_sv;
   static int        code_page;

public:
                     CJAVal() { Clear(); }
                     CJAVal(CJAVal* aparent, enJAType atype) { Clear(); m_type=atype; m_parent=aparent; }
                     CJAVal(enJAType t, string a) { Clear(); FromStr(t, a); }
                     CJAVal(const int a) { Clear(); m_type=jtINT; m_iv=a; m_dv=(double)m_iv; m_sv=IntegerToString(m_iv); m_bv=m_iv!=0; }
                     CJAVal(const long a) { Clear(); m_type=jtINT; m_iv=a; m_dv=(double)m_iv; m_sv=IntegerToString(m_iv); m_bv=m_iv!=0; }
                     CJAVal(const double a, int aprec=-100) { Clear(); m_type=jtDBL; m_dv=a; if(aprec>-100) m_prec=aprec; m_iv=(long)m_dv; m_sv=DoubleToString(m_dv, m_prec); m_bv=m_iv!=0; }
                     CJAVal(const bool a) { Clear(); m_type=jtBOOL; m_bv=a; m_iv=m_bv; m_dv=m_bv; m_sv=IntegerToString(m_iv); }
                     CJAVal(const CJAVal& a) { Clear(); Copy(a); }
                    ~CJAVal() { Clear(); }

public:
   int               Size() { return ArraySize(m_e); }
   virtual bool      IsNumeric() { return m_type==jtDBL || m_type==jtINT; }
   virtual CJAVal*   FindKey(string akey) { for(int i=Size()-1; i>=0; --i) if(m_e[i].m_key==akey) return GetPointer(m_e[i]); return NULL; }
   virtual CJAVal*   HasKey(string akey, enJAType atype=jtUNDEF) { CJAVal* e=FindKey(akey); if(CheckPointer(e)!=POINTER_INVALID) { if(atype==jtUNDEF || atype==e.m_type) return GetPointer(e); } return NULL; }
   virtual CJAVal*   operator[](string akey);
   virtual CJAVal*   operator[](int i);
   void              operator=(const CJAVal &a) { Copy(a); }
   void              operator=(const int a) { m_type=jtINT; m_iv=a; m_dv=(double)m_iv; m_bv=m_iv!=0; }
   void              operator=(const long a) { m_type=jtINT; m_iv=a; m_dv=(double)m_iv; m_bv=m_iv!=0; }
   void              operator=(const double a) { m_type=jtDBL; m_dv=a; m_iv=(long)m_dv; m_bv=m_iv!=0; }
   void              operator=(const bool a) { m_type=jtBOOL; m_bv=a; m_iv=(long)m_bv; m_dv=(double)m_bv; }
   void              operator=(string a) { m_type=(a!=NULL)?jtSTR:jtNULL; m_sv=a; m_iv=StringToInteger(m_sv); m_dv=StringToDouble(m_sv); m_bv=a!=NULL; }

   bool              operator==(const int a) { return m_iv==a; }
   bool              operator==(const long a) { return m_iv==a; }
   bool              operator==(const double a) { return m_dv==a; }
   bool              operator==(const bool a) { return m_bv==a; }
   bool              operator==(string a) { return m_sv==a; }

   bool              operator!=(const int a) { return m_iv!=a; }
   bool              operator!=(const long a) { return m_iv!=a; }
   bool              operator!=(const double a) { return m_dv!=a; }
   bool              operator!=(const bool a) { return m_bv!=a; }
   bool              operator!=(string a) { return m_sv!=a; }

   long              ToInt() const { return m_iv; }
   double            ToDbl() const { return m_dv; }
   bool              ToBool() const { return m_bv; }
   string            ToStr() { return m_sv; }

   virtual void      FromStr(enJAType t, string a)
     {
      m_type=t;
      switch(m_type)
        {
         case jtBOOL:
            m_bv=(StringToInteger(a)!=0);
            m_iv=(long)m_bv;
            m_dv=(double)m_bv;
            m_sv=a;
            break;
         case jtINT:
            m_iv=StringToInteger(a);
            m_dv=(double)m_iv;
            m_sv=a;
            m_bv=m_iv!=0;
            break;
         case jtDBL:
            m_dv=StringToDouble(a);
            m_iv=(long)m_dv;
            m_sv=a;
            m_bv=m_iv!=0;
            break;
         case jtSTR:
            m_sv=Unescape(a);
            m_type=(m_sv!=NULL)?jtSTR:jtNULL;
            m_iv=StringToInteger(m_sv);
            m_dv=StringToDouble(m_sv);
            m_bv=m_sv!=NULL;
            break;
        }
     }
   virtual string    GetStr(char& js[], int i, int slen) { if(slen==0) return ""; char cc[]; ArrayCopy(cc, js, 0, i, slen); return CharArrayToString(cc, 0, WHOLE_ARRAY, CJAVal::code_page); }

   virtual void      Set(const CJAVal& a) { if(m_type==jtUNDEF) m_type=jtOBJ; CopyData(a); }
   virtual void      Set(const CJAVal& list[]);
   virtual CJAVal*   Add(const CJAVal& item) { if(m_type==jtUNDEF) m_type=jtARRAY; /*ASSERT(m_type==jtOBJ || m_type==jtARRAY);*/ return AddBase(item); }  // добавление
   virtual CJAVal*   Add(const int a) { CJAVal item(a); return Add(item); }
   virtual CJAVal*   Add(const long a) { CJAVal item(a); return Add(item); }
   virtual CJAVal*   Add(const double a, int aprec=-2) { CJAVal item(a, aprec); return Add(item); }
   virtual CJAVal*   Add(const bool a) { CJAVal item(a); return Add(item); }
   virtual CJAVal*   Add(string a) { CJAVal item(jtSTR, a); return Add(item); }
   virtual CJAVal*   AddBase(const CJAVal &item) { int c=Size(); ArrayResize(m_e, c+1, 100); m_e[c]=item; m_e[c].m_parent=GetPointer(this); return GetPointer(m_e[c]); } // добавление
   virtual CJAVal*   New() { if(m_type==jtUNDEF) m_type=jtARRAY; /*ASSERT(m_type==jtOBJ || m_type==jtARRAY);*/ return NewBase(); }  // добавление
   virtual CJAVal*   NewBase() { int c=Size(); ArrayResize(m_e, c+1, 100); return GetPointer(m_e[c]); } // добавление

   virtual string    Escape(string a);
   virtual string    Unescape(string a);
public:
   virtual void      Serialize(string &js, bool bf=false, bool bcoma=false);
   virtual string    Serialize() { string js; Serialize(js); return js; }
   virtual bool      Deserialize(char& js[], int slen, int &i);
   virtual bool      ExtrStr(char& js[], int slen, int &i);
   virtual bool      Deserialize(string js, int acp=CP_ACP) { int i=0; Clear(); CJAVal::code_page=acp; char arr[]; int slen=StringToCharArray(js, arr, 0, WHOLE_ARRAY, CJAVal::code_page); return Deserialize(arr, slen, i); }
   virtual bool      Deserialize(char& js[], int acp=CP_ACP) { int i=0; Clear(); CJAVal::code_page=acp; return Deserialize(js, ArraySize(js), i); }
  };

int CJAVal::code_page=CP_ACP;

//------------------------------------------------------------------ operator[]
CJAVal* CJAVal::operator[](string akey) { if(m_type==jtUNDEF) m_type=jtOBJ; CJAVal* v=FindKey(akey); if(v) return v; CJAVal b(GetPointer(this), jtUNDEF); b.m_key=akey; v=Add(b); return v; }
//------------------------------------------------------------------ operator[]
CJAVal* CJAVal::operator[](int i)
  {
   if(m_type==jtUNDEF)
      m_type=jtARRAY;
   while(i>=Size())
     {
      CJAVal b(GetPointer(this), jtUNDEF);
      if(CheckPointer(Add(b))==POINTER_INVALID)
         return NULL;
     }
   return GetPointer(m_e[i]);
  }
//------------------------------------------------------------------ Set
void CJAVal::Set(const CJAVal& list[])
  {
   if(m_type==jtUNDEF)
      m_type=jtARRAY;
   int n=ArrayResize(m_e, ArraySize(list), 100);
   for(int i=0; i<n; ++i)
     {
      m_e[i]=list[i];
      m_e[i].m_parent=GetPointer(this);
     }
  }

//------------------------------------------------------------------ Serialize
void CJAVal::Serialize(string& js, bool bkey/*=false*/, bool coma/*=false*/)
  {
   if(m_type==jtUNDEF)
      return;
   if(coma)
      js+=",";
   if(bkey)
      js+=StringFormat("\"%s\":", m_key);
   int _n=Size();
   switch(m_type)
     {
      case jtNULL:
         js+="null";
         break;
      case jtBOOL:
         js+=(m_bv?"true":"false");
         break;
      case jtINT:
         js+=IntegerToString(m_iv);
         break;
      case jtDBL:
         js+=DoubleToString(m_dv, m_prec);
         break;
      case jtSTR:
        { string ss=Escape(m_sv); if(StringLen(ss)>0) js+=StringFormat("\"%s\"", ss); else js+="null"; }
      break;
      case jtARRAY:
         js+="[";
         for(int i=0; i<_n; i++)
            m_e[i].Serialize(js, false, i>0);
         js+="]";
         break;
      case jtOBJ:
         js+="{";
         for(int i=0; i<_n; i++)
            m_e[i].Serialize(js, true, i>0);
         js+="}";
         break;
     }
  }

//------------------------------------------------------------------ Deserialize
bool CJAVal::Deserialize(char& js[], int slen, int &i)
  {
   string num="0123456789+-.eE";
   int i0=i;
   for(; i<slen; i++)
     {
      char c=js[i];
      if(c==0)
         break;
      switch(c)
        {
         case '\t':
         case '\r':
         case '\n':
         case ' ': // пропускаем из имени пробелы
            i0=i+1;
            break;

         case '[': // начало массива. создаём объекты и забираем из js
           {
            i0=i+1;
            if(m_type!=jtUNDEF)
              {
               Print(m_key+" "+string(__LINE__));   // если значение уже имеет тип, то это ошибка
               return false;
              }
            m_type=jtARRAY; // задали тип значения
            i++;
            CJAVal val(GetPointer(this), jtUNDEF);
            while(val.Deserialize(js, slen, i))
              {
               if(val.m_type!=jtUNDEF)
                  Add(val);
               if(val.m_type==jtINT || val.m_type==jtDBL || val.m_type==jtARRAY)
                  i++;
               val.Clear();
               val.m_parent=GetPointer(this);
               if(js[i]==']')
                  break;
               i++;
               if(i>=slen)
                 {
                  Print(m_key+" "+string(__LINE__));
                  return false;
                 }
              }
            return js[i]==']' || js[i]==0;
           }
         break;
         case ']':
            if(!m_parent)
               return false;
            return m_parent.m_type==jtARRAY; // конец массива, текущее значение должны быть массивом

         case ':':
           {
            if(m_lkey=="")
              {
               Print(m_key+" "+string(__LINE__));
               return false;
              }
            CJAVal val(GetPointer(this), jtUNDEF);
            CJAVal *oc=Add(val); // тип объекта пока не определён
            oc.m_key=m_lkey;
            m_lkey=""; // задали имя ключа
            i++;
            if(!oc.Deserialize(js, slen, i))
              {
               Print(m_key+" "+string(__LINE__));
               return false;
              }
            break;
           }
         case ',': // разделитель значений // тип значения уже должен быть определён
            i0=i+1;
            if(!m_parent && m_type!=jtOBJ)
              {
               Print(m_key+" "+string(__LINE__));
               return false;
              }
            else
               if(m_parent)
                 {
                  if(m_parent.m_type!=jtARRAY && m_parent.m_type!=jtOBJ)
                    {
                     Print(m_key+" "+string(__LINE__));
                     return false;
                    }
                  if(m_parent.m_type==jtARRAY && m_type==jtUNDEF)
                     return true;
                 }
            break;

         // примитивы могут быть ТОЛЬКО в массиве / либо самостоятельно
         case '{': // начало объекта. создаем объект и забираем его из js
            i0=i+1;
            if(m_type!=jtUNDEF)
              {
               Print(m_key+" "+string(__LINE__));   // ошибка типа
               return false;
              }
            m_type=jtOBJ; // задали тип значения
            i++;
            if(!Deserialize(js, slen, i))
              {
               Print(m_key+" "+string(__LINE__));   // вытягиваем его
               return false;
              }
            return js[i]=='}' || js[i]==0;
            break;
         case '}':
            return m_type==jtOBJ; // конец объекта, текущее значение должно быть объектом

         case 't':
         case 'T': // начало true
         case 'f':
         case 'F': // начало false
            if(m_type!=jtUNDEF)
              {
               Print(m_key+" "+string(__LINE__));   // ошибка типа
               return false;
              }
            m_type=jtBOOL; // задали тип значения
            if(i+3<slen)
              {
               if(StringCompare(GetStr(js, i, 4), "true", false)==0)
                 {
                  m_bv=true;
                  i+=3;
                  return true;
                 }
              }
            if(i+4<slen)
              {
               if(StringCompare(GetStr(js, i, 5), "false", false)==0)
                 {
                  m_bv=false;
                  i+=4;
                  return true;
                 }
              }
            Print(m_key+" "+string(__LINE__));
            return false; // не тот тип или конец строки
            break;
         case 'n':
         case 'N': // начало null
            if(m_type!=jtUNDEF)
              {
               Print(m_key+" "+string(__LINE__));   // ошибка типа
               return false;
              }
            m_type=jtNULL; // задали тип значения
            if(i+3<slen)
               if(StringCompare(GetStr(js, i, 4), "null", false)==0)
                 {
                  i+=3;
                  return true;
                 }
            Print(m_key+" "+string(__LINE__));
            return false; // не NULL или конец строки
            break;

         case '0':
         case '1':
         case '2':
         case '3':
         case '4':
         case '5':
         case '6':
         case '7':
         case '8':
         case '9':
         case '-':
         case '+':
         case '.': // начало числа
           {
            if(m_type!=jtUNDEF)
              {
               Print(m_key+" "+string(__LINE__));   // ошибка типа
               return false;
              }
            bool dbl=false;// задали тип значения
            int is=i;
            while(js[i]!=0 && i<slen)
              {
               i++;
               if(StringFind(num, GetStr(js, i, 1))<0)
                  break;
               if(!dbl)
                  dbl=(js[i]=='.' || js[i]=='e' || js[i]=='E');
              }
            m_sv=GetStr(js, is, i-is);
            if(dbl)
              {
               m_type=jtDBL;
               m_dv=StringToDouble(m_sv);
               m_iv=(long)m_dv;
               m_bv=m_iv!=0;
              }
            else
              {
               m_type=jtINT;   // уточнии тип значения
               m_iv=StringToInteger(m_sv);
               m_dv=(double)m_iv;
               m_bv=m_iv!=0;
              }
            i--;
            return true; // отодвинулись на 1 символ назад и вышли
            break;
           }
         case '\"': // начало или конец строки
            if(m_type==jtOBJ)  // если тип еще неопределён и ключ не задан
              {
               i++;
               int is=i;
               if(!ExtrStr(js, slen, i))
                 {
                  Print(m_key+" "+string(__LINE__));   // это ключ, идём до конца строки
                  return false;
                 }
               m_lkey=GetStr(js, is, i-is);
              }
            else
              {
               if(m_type!=jtUNDEF)
                 {
                  Print(m_key+" "+string(__LINE__));   // ошибка типа
                  return false;
                 }
               m_type=jtSTR; // задали тип значения
               i++;
               int is=i;
               if(!ExtrStr(js, slen, i))
                 {
                  Print(m_key+" "+string(__LINE__));
                  return false;
                 }
               FromStr(jtSTR, GetStr(js, is, i-is));
               return true;
              }
            break;
        }
     }
   return true;
  }

//------------------------------------------------------------------ ExtrStr
bool CJAVal::ExtrStr(char& js[], int slen, int &i)
  {
   for(; js[i]!=0 && i<slen; i++)
     {
      char c=js[i];
      if(c=='\"')
         break; // конец строки
      if(c=='\\' && i+1<slen)
        {
         i++;
         c=js[i];
         switch(c)
           {
            case '/':
            case '\\':
            case '\"':
            case 'b':
            case 'f':
            case 'r':
            case 'n':
            case 't':
               break; // это разрешенные
            case 'u': // \uXXXX
              {
               i++;
               for(int j=0; j<4 && i<slen && js[i]!=0; j++, i++)
                 {
                  if(!((js[i]>='0' && js[i]<='9') || (js[i]>='A' && js[i]<='F') || (js[i]>='a' && js[i]<='f')))
                    {
                     Print(m_key+" "+CharToString(js[i])+" "+string(__LINE__));   // не hex
                     return false;
                    }
                 }
               i--;
               break;
              }
            default:
               break; /*{ return false; } // неразрешенный символ с экранированием */
           }
        }
     }
   return true;
  }
//------------------------------------------------------------------ Escape
string CJAVal::Escape(string a)
  {
   ushort as[], s[];
   int n=StringToShortArray(a, as);
   if(ArrayResize(s, 2*n)!=2*n)
      return NULL;
   int j=0;
   for(int i=0; i<n; i++)
     {
      switch(as[i])
        {
         case '\\':
            s[j]='\\';
            j++;
            s[j]='\\';
            j++;
            break;
         case '"':
            s[j]='\\';
            j++;
            s[j]='"';
            j++;
            break;
         case '/':
            s[j]='\\';
            j++;
            s[j]='/';
            j++;
            break;
         case 8:
            s[j]='\\';
            j++;
            s[j]='b';
            j++;
            break;
         case 12:
            s[j]='\\';
            j++;
            s[j]='f';
            j++;
            break;
         case '\n':
            s[j]='\\';
            j++;
            s[j]='n';
            j++;
            break;
         case '\r':
            s[j]='\\';
            j++;
            s[j]='r';
            j++;
            break;
         case '\t':
            s[j]='\\';
            j++;
            s[j]='t';
            j++;
            break;
         default:
            s[j]=as[i];
            j++;
            break;
        }
     }
   a=ShortArrayToString(s, 0, j);
   return a;
  }
//------------------------------------------------------------------ Unescape
string CJAVal::Unescape(string a)
  {
   ushort as[], s[];
   int n=StringToShortArray(a, as);
   if(ArrayResize(s, n)!=n)
      return NULL;
   int j=0, i=0;
   while(i<n)
     {
      ushort c=as[i];
      if(c=='\\' && i<n-1)
        {
         switch(as[i+1])
           {
            case '\\':
               c='\\';
               i++;
               break;
            case '"':
               c='"';
               i++;
               break;
            case '/':
               c='/';
               i++;
               break;
            case 'b':
               c=8; /*08='\b'*/;
               i++;
               break;
            case 'f':
               c=12;/*0c=\f*/ i++;
               break;
            case 'n':
               c='\n';
               i++;
               break;
            case 'r':
               c='\r';
               i++;
               break;
            case 't':
               c='\t';
               i++;
               break;
            case 'u': // \uXXXX
              {
               i+=2;
               ushort k=0;
               for(int jj=0; jj<4 && i<n; jj++, i++)
                 {
                  c=as[i];
                  ushort h=0;
                  if(c>='0' && c<='9')
                     h=c-'0';
                  else
                     if(c>='A' && c<='F')
                        h=c-'A'+10;
                     else
                        if(c>='a' && c<='f')
                           h=c-'a'+10;
                        else
                           break; // не hex
                  k+=h*(ushort)pow(16, (3-jj));
                 }
               i--;
               c=k;
               break;
              }
           }
        }
      s[j]=c;
      j++;
      i++;
     }
   a=ShortArrayToString(s, 0, j);
   return a;
  }
//+------------------------------------------------------------------+