#include <Arduino.h>
#include "itemCmd.h"
#include "main.h"
#include "Streaming.h"
#include "item.h"
#include "bright.h"
#include <utility/stringbuffer.h>>

#ifndef HSV_DISABLE
#ifdef ADAFRUIT_LED
#include <Adafruit_NeoPixel.h>
#else
#include "FastLED.h"
#endif
#endif

//#include "hsv2rgb.h"

int txt2cmd(char *payload) {
  int cmd = CMD_UNKNOWN;
  if (!payload || !payload[0]) return cmd;

  // Check for command
  if (*payload == '-' || (*payload >= '0' && *payload <= '9')) cmd = CMD_VOID;
  else if (*payload == '%') cmd = CMD_UP;
  else if (*payload == '{') cmd = CMD_JSON;
  else if (*payload == '#') cmd = CMD_RGB;
  else
  {
    for(uint8_t i=1; i<commandsNum ;i++)
        if (strncmp_P(payload, commands_P[i], strlen_P(commands_P[i])) == 0)
             {
//             debugSerial<< i << F(" ") << pgm_read_word_near(&serialModes_P[i].mode)<< endl;
             return i;
           }
  }


  return cmd;
}


int type2num(char *payload) {
  if (!payload || !payload[0]) return -1;

    for(uint8_t i=0; i<ch_typeNum ;i++)
        if (strncmp_P(payload, ch_type_P[i], strlen_P(ch_type_P[i])) == 0)
             {
             return i;
           }
  return -1;
}



/*!
     \brief Constructor with definition of type and command
     \param type - type of value (ST_???, ST_VOID by default) 
     \param code - code of command (CMD_VOID by default)
*/
itemCmd::itemCmd(uint8_t _type, uint8_t _code)
{
  cmd.aslong=0;
  param.aslong=0;

  cmd.itemArgType=_type;
  cmd.cmdCode=_code;
}


/*!
     \brief Constructor with definition of FLOAT value in storage
     \param float
     \param type - type of value (ST_FLOAT or ST_FLOAT_CELSIUS or ST_FLOAT_FARENHEIT) - optional
*/
itemCmd::itemCmd(float val)
{
  cmd.aslong=0;
  param.aslong=0;

  cmd.itemArgType=ST_FLOAT;
  param.asfloat=val;
}

/*!
     \brief Constructor with loading value from Item
     \param Item
*/
itemCmd::itemCmd(Item *item)
{
  cmd.aslong=0;
  param.aslong=0;

  loadItem(item);
}

/*!
     \brief Constructor with textual definition of command
     \param cmd - name of cmd 
todo - extend to values
*/
itemCmd::itemCmd(char * _cmd)
{
  //debugSerial<<"ITEMCMD:" <<_cmd<<endl;
  cmd.aslong=0;
  param.aslong=0;

  short i=0;
  int Par[4];

  while (_cmd[i]) {_cmd[i]=toupper(_cmd[i]);i++;};

  int cmdN = txt2cmd(_cmd);
  if (cmdN>=0) cmd.cmdCode = cmdN;

  bool hsvflag=false;

  switch (cmd.cmdCode) {
      case CMD_HSV: 
        hsvflag=true;
      case CMD_RGB:

        cmd.cmdCode=CMD_VOID;
        //Parsing integers from payload
        i = 0;

         while (_cmd && i < 4)
                Par[i++] = getIntFromStr((char **) &_cmd);

         if (hsvflag)
         {
         setSuffix(S_HSV); 
         cmd.itemArgType=ST_HSV255;
         switch (i) //Number of params
                {
                  case 4:
                      setColorTemp(Par[3]);
                  case 3:
                      param.v=Par[2]; 
                  case 2:
                      setH(Par[0]);
                      setS(Par[1]);
                } 
         }
         else 
         { 
         setSuffix(S_RGB); 
         switch (i) //Number of params
                  {
                    case 3: RGB(Par[0],Par[1],Par[2]);
                    break;
                    case 4: RGBW(Par[0],Par[1],Par[2],Par[3]);
                    default:;
                  }
         }

      break;

  //    case CMD_UNKNOWN: //Not known command
  //    case CMD_JSON: //JSON input (not implemented yet
  //    cmd.cmdCode=CMD_VOID;
  //    break;

      default: //some known command
      //case CMD_VOID:
      //case CMD_UP:
      //case CMD_DN:    
     {    
     itemCmd num =getNumber((char **) &_cmd);  
     cmd.itemArgType=num.getArgType();
     param.aslong=num.param.aslong;
     if (cmd.cmdCode) setSuffix(S_CMD);
     }
  } //switch
  //debugOut();
}

itemCmd itemCmd::setChanType(short chanType)
{
  cmd.itemArgType=getStoragetypeByChanType(chanType);
  debugSerial<<F("Chan type:")<<chanType<<F(" -> AT:")<<cmd.itemArgType<<endl;
  return *this;
}


uint8_t itemCmd::getStoragetypeByChanType(short chanType)
{
  switch (chanType)
  {
    case CH_RGB:
    case CH_RGBW:
    case CH_RGBWW:
    case CH_SPILED:
    return ST_HSV255;
    break;
    case CH_AC:
    case CH_THERMO:
    case CH_VCTEMP:
    return ST_FLOAT_CELSIUS;
    break;
    case CH_DIMMER:
    case CH_MOTOR:
    case CH_PWM:
    case CH_VC:
    case CH_MODBUS:
    //case CH_RELAY:
    //case CH_GROUP:
    return ST_PERCENTS255;
    break;
    case CH_RELAY:
    return ST_VOID;
    //case CH_COUNTER:
    //return ST_TENS;
    default:
    return ST_VOID;
  }

}


itemCmd itemCmd::setDefault()
{
  switch (cmd.itemArgType){
    case ST_FLOAT_CELSIUS: param.asfloat=20.;
    break;
    case ST_FLOAT_FARENHEIT: param.asfloat=75.;
    break;
  
    case ST_HS:
    case ST_HSV255: param.h=100; param.s=0; param.v=255;
    break;
    case ST_PERCENTS255: param.v=255;
    break;
    default:
    param.asInt32=0;
  }
  return *this;
}

bool itemCmd::setH(uint16_t h)
{
  int par=h;
  switch (cmd.itemArgType)
  {
    case ST_VOID:
      cmd.itemArgType=ST_HSV255;
    case ST_HSV255:
      if (par>365) par=365;
      if (par<0) par=0;
      param.h=par;
      break;

    default:
  //  debugSerial<<F("Can't assign HUE to type ")<<cmd.itemArgType<<endl;
    return false;
  }
  return true;
}

bool itemCmd::setS(uint8_t s)
{
  int par=s;
  switch (cmd.itemArgType)
  {
    case ST_VOID:
      cmd.itemArgType=ST_HSV255;
     case ST_HSV255:
      if (par>100) par=100;
      param.s=par;
      break;
    default:
//    debugSerial<<F("Can't assign saturation to type ")<<cmd.itemArgType<<endl;
    return false;
  }
  return true;
}



//! Setup color tempetature parameter for HSV or HSV255 types. It must be 153..500 (mireds) value.
//! Internally  1 - cold, 101 - warm light
bool itemCmd::setColorTemp(int t)
{
  if (!t)
  {
  param.colorTemp=0;  
  return true;  
  }
  int par=map(t,153,500,0,100);
  switch (cmd.itemArgType)
  {
    case ST_VOID:
      cmd.itemArgType=ST_HSV255;
    case ST_HS:
    case ST_PERCENTS255:
    case ST_HSV255:
      if (par>100) par=100;
      if (par<0) par=0;
      //debugSerial<<F("Assign color temp:")<<par<<endl;
      // value 0 is reserved for default/uninitialized value. Internally data stored in 1..101 range (7 bits)
      param.colorTemp=par+1;
      break;
    default:
    return false;
  }
  return true;
}

//! Return color tempetature parameter from HSV or HSV255 types. return 153..500 value in success.
//! -1 - if no value stored
int itemCmd::getColorTemp()
{
  if (!param.colorTemp) return -1;

  switch (cmd.itemArgType)
  {
    case ST_HS:
    case ST_PERCENTS255:
    case ST_HSV255:
  
  return map(param.colorTemp-1,0,100,153,500);
    break;
  }
  return -1;  
}

uint16_t itemCmd::getH()
{
  return param.h;
}

uint16_t itemCmd::getS()
{
  return param.s;
}


bool itemCmd::incrementPercents(long int dif, long int limit )
{ long par=param.v;
  switch (cmd.itemArgType)
  {
    case ST_PERCENTS255:
    case ST_HSV255:
     par+=dif/TENS_BASE;
     if (par>255) par=255;
     if (par<0) par=0;
    param.v=par;
    break;

    case ST_INT32:
    case ST_UINT32:
    if (dif<TENS_BASE) // Step < 1 - convert to tens
      {
        par=param.asInt32*TENS_BASE;
        par+=dif;

        if (limit && par>limit*TENS_BASE) par=limit*TENS_BASE;
        if (limit && par<0) par=0;

        param.asInt32=par;
        cmd.itemArgType=ST_TENS;
      }
     else
     {
     par=param.asInt32;
     par+=dif/TENS_BASE;
     if (limit && par>limit) par=limit;
     if (limit && par<0) par=0;
     param.asInt32=par;
     }
    break;  

    case ST_FLOAT:
    case ST_FLOAT_CELSIUS:
    case ST_FLOAT_FARENHEIT:
     par=param.asfloat;
     par+=dif/TENS_BASE;
    if (limit && par>limit) par=limit;
    if (limit && par<0) par=0;
     param.asfloat=par;
    break;  
 
    case ST_TENS:
    
     par=param.asInt32;
     par+=dif;
        if (limit && par>limit*TENS_BASE) par=limit*TENS_BASE;
        if (limit && par<0) par=0;
     param.asInt32=par;
    break;  

   default: return false;
  }
  
  return true;
}

bool itemCmd::incrementH(long int dif)
{ int par=param.h;
  switch (cmd.itemArgType)
  {
  //case ST_HSV:
  case ST_HSV255:
   par+=dif/TENS_BASE;
   if (par>365) par=0;
   if (par<0) par=365;
   break;
  default: return false;
  break;
}
param.h=par;
return true;
}

bool itemCmd::incrementS(long int dif)
{int par=param.s;
  switch (cmd.itemArgType)
  {
    case ST_HSV255:
     par+=dif/TENS_BASE;
     if (par>100) par=100;
     if (par<0) par=0;
    break;

   default: return false;
  }
  param.s=par;
  return true;

}

bool itemCmd::incrementTemp(long int dif)
{int par=param.colorTemp;
  switch (cmd.itemArgType)
  {
    case ST_HSV255:
     par+=dif/TENS_BASE;
     if (par>100) par=100;
     if (par<1) par=1;
    break;

   default: return false;
  }
  param.colorTemp=par;
  return true;

}


itemCmd itemCmd::assignFrom(itemCmd from, short chanType)
{
  bool RGBW_flag   = false;
  bool toFarenheit = false;
  short prefferedStorageType = getStoragetypeByChanType(chanType);

  int t=from.getColorTemp();

  if (t>=0) 
              {
              setColorTemp(t);
              }
  cmd.suffixCode=from.cmd.suffixCode;
  cmd.cmdCode=from.cmd.cmdCode;
  //cmd.cmdFlag
  //cmd.cmdParam  
  
  switch (cmd.itemArgType){ //Destination
        case ST_HSV255:
        case ST_PERCENTS255:
           switch (from.cmd.itemArgType)
             {
               case ST_RGBW:
                    param.w=from.param.w;
               case ST_RGB:
                    param.r=from.param.r;
                    param.g=from.param.g;
                    param.b=from.param.b;
                    cmd.itemArgType=from.cmd.itemArgType;
                    break;
              case ST_HS:
                   param.h=from.param.h;
                   param.s=from.param.s;
                   break;
              case ST_INT32:
              case ST_UINT32:
                   param.v=constrain(from.param.asInt32,0,255);
                   break;
              case ST_TENS:
                   param.v=constrain(from.param.asInt32/TENS_BASE,0,255);   
                   break;                    
              case ST_HSV255:
                   param.h=from.param.h;
                   param.s=from.param.s;
                   param.v=from.param.v;
                   break;
              case ST_PERCENTS255:
                   param.v=from.param.v;
                   break;
              case ST_FLOAT:
                          param.v=constrain(from.param.asfloat,0.,255.);
                  break;   
              case ST_VOID:
                  break; 
              case ST_STRING:
                   cmd.itemArgType=from.cmd.itemArgType;
                   param.asString=from.param.asString;
                  break;    
              default:
                       debugSerial<<F("Wrong Assignment ")<<from.cmd.itemArgType<<F("->")<<cmd.itemArgType<<endl;
              }
             break;
        case ST_VOID:
             cmd.itemArgType=from.cmd.itemArgType;
             param=from.param;
        break;     

        case ST_INT32:
        case ST_UINT32:
        case ST_TENS:
           switch (from.cmd.itemArgType)
           {
            case ST_HS:
              param.v=getPercents255();
              param.h=from.param.h;
              param.s=from.param.s; 
              cmd.itemArgType=ST_HSV255;  
            break;  
            case ST_STRING:
                  cmd.itemArgType=from.cmd.itemArgType;
                  param.asString=from.param.asString;
            break;               
           default:
              param.asInt32=from.param.asInt32;
              cmd.itemArgType=from.cmd.itemArgType;
           }
           break;
        case ST_HS: //ToDo - string ?
           param.v=from.getPercents255();
           cmd.itemArgType=ST_HSV255; 
           break;     
        case ST_FLOAT_FARENHEIT:
            toFarenheit = true;
        case ST_FLOAT:
        case ST_FLOAT_CELSIUS:
           switch (from.cmd.itemArgType)
           {
             case ST_STRING:
              cmd.itemArgType=from.cmd.itemArgType;
              param.asString=from.param.asString;
             break; 

             case ST_TENS:
              //param.asfloat=(float) from.param.asInt32/(float)TENS_BASE;
               param.asInt32 = from.param.asInt32;
               cmd.itemArgType=from.cmd.itemArgType;
             break;
             case ST_PERCENTS255:             
              param.asfloat=from.param.v; 
             break;

             case ST_INT32:             
              param.asfloat=from.param.asInt32; 
             break;
             case ST_UINT32:
              param.asfloat=from.param.asUint32;
             break;

             case ST_FLOAT_FARENHEIT: 
             // F to C code should be here
             // if (!toFarenheit) convert (from.param.asfloat); cmd.itemArgType=ST_FARENHEIT
             case ST_FLOAT:
             case ST_FLOAT_CELSIUS:
              cmd.itemArgType=from.cmd.itemArgType;
              param.asfloat=from.param.asfloat; 
              break;
             case ST_HSV255:
             case ST_RGB:
             case ST_RGBW:
              cmd.itemArgType=from.cmd.itemArgType;
              param=from.param; 
              break;
              case ST_HS:
                   param.v=getPercents255();
                   param.h=from.param.h;
                   param.s=from.param.s; 
                   cmd.itemArgType=ST_HSV255;
              break;                
              case ST_VOID:
              break; 
           default:
                     debugSerial<<F("Wrong Assignment ")<<from.cmd.itemArgType<<F("->")<<cmd.itemArgType<<endl;
           }
           
           break;

        case ST_RGBW:
             RGBW_flag=true;
        case ST_RGB:
        switch (from.cmd.itemArgType)
          {
            case ST_STRING:
                  cmd.itemArgType=from.cmd.itemArgType;
                  param.asString=from.param.asString;
            break; 
            case ST_RGBW:
                 // RGBW_flag=true;
            case ST_RGB:
                  param.asInt32=from.param.asInt32;
                  cmd.itemArgType=from.cmd.itemArgType;
                  return *this;
            //break;
            // Those types are not possible to apply over RGB without convertion toward HSV
            case ST_FLOAT:
            case ST_HS:
            case ST_INT32:
            case ST_PERCENTS255:
            case ST_TENS:
            case ST_UINT32:
            {

             #ifndef ADAFRUIT_LED  
             // Restoring HSV from RGB
              CRGB rgb;
              rgb.r = param.r;
              rgb.g = param.g;
              rgb.b = param.b;    
              CHSV hsv = rgb2hsv_approximate(rgb);
             #endif 

              // Calculate volume
              int vol=0;
              switch (from.cmd.itemArgType)
            {  
            case ST_PERCENTS255:
              vol=from.param.v;
              break;
            case ST_INT32:
            case ST_UINT32:
              vol=from.param.asInt32;
              break; 
            case ST_TENS:
              vol=from.param.asInt32/TENS_BASE;
              break; 
            case ST_FLOAT:
              vol=from.param.asfloat;
              break;
            case ST_HS:
               #ifndef ADAFRUIT_LED  
               vol=hsv.v;
               #else
               vol=255;
               #endif 
            }

              #ifndef ADAFRUIT_LED  
              // Restoring HSV from RGB
              from.param.h = map(hsv.h, 0, 255, 0, 365);
              from.param.s = map(hsv.s, 0, 255, 0, 100);
              #else
              from.param.h=100;
              from.param.s=0;  
              #endif

              from.cmd.itemArgType=ST_HSV255;
              from.param.v=vol;
            }
            // Continue processing with filled from HSV 

           case ST_HSV255:
                { // HSV_XX to RGB_XX translation code
                int   rgbSaturation=constrain(map(from.param.s, 0, 100, 0, 255),0,255);
                int   rgbValue = getBright255(from.param.v);
                short colorT =from.param.colorTemp-1;
                  if (RGBW_flag)        
                          {
                              if (colorT<0)
                              { //ColorTemperature not set
                              if (rgbSaturation < 128) { // Using white
                                                param.w=map((127 - rgbSaturation) * rgbValue, 0, 127*255, 0, 255);
                                                int rgbvLevel = map (rgbSaturation,0,127,0,255*2);
                                                rgbValue = map(rgbValue, 0, 255, 0, rgbvLevel);
                                                rgbSaturation = map(rgbSaturation, 0, 127, 100, 255);
                                                if (rgbValue>255) rgbValue = 255;
                                              }
                                  else
                                              {
                                                rgbSaturation = map(rgbSaturation, 128, 255, 100, 255);
                                                param.w=0;
                                              };
                              debugSerial<<F("Converted S:")<<rgbSaturation<<F(" Converted V:")<<rgbValue<<endl;                    
                              }
                              else
                              {
                                long coldPercent = map (colorT,0,100,100,30); 
                                long hotPercent  = map (colorT,0,100,30,100);
                                int rgbvLevel = 0;

                                if (rgbSaturation < 128) { // Using white
                                                param.w=map((127 - rgbSaturation) * rgbValue *hotPercent, 0, 127*255*100, 0, 255);
                                                rgbvLevel = map (rgbSaturation+coldPercent,0,127+100,0,255*2);
                                                rgbValue = map(rgbValue, 0, 255, 0, rgbvLevel);
                                                rgbSaturation = map(rgbSaturation, 0, 127, 0, 255);
                                                if (rgbValue>255) rgbValue = 255;
                                              }
                                  else
                                      {
                                        rgbSaturation = map(rgbSaturation, 128, 255, 100, 255);
                                        param.w=0;
                                      };
                              debugSerial<<F("Cold:")<<coldPercent<<F(" Hot:")<<hotPercent<<F(" ConvS:")<<rgbSaturation<<F(" ConvV:")<<rgbValue<<F(" vLevel:")<<rgbvLevel<<endl;             
                              } 
                          }                

                  #ifndef HSV_DISABLE
                  #ifdef ADAFRUIT_LED
                    Adafruit_NeoPixel strip(0, 0, 0);
                    uint32_t rgb = strip.ColorHSV(map(from.param.h, 0, 365, 0, 65535), rgbSaturation, rgbValue);
                    param.r=(rgb >> 16)& 0xFF;
                    param.g=(rgb >> 8) & 0xFF;
                    param.b=rgb & 0xFF;
                  #else
                    CRGB rgb = CHSV(map(from.param.h, 0, 365, 0, 255), rgbSaturation, rgbValue);
                    param.r=rgb.r;
                    param.g=rgb.g;
                    param.b=rgb.b;
                  #endif
                  #else
                  debugSerial<<F("HSV disabled")<<endl;
                  #endif
                  debugSerial<<F("RGBx: ");
                  debugOut();
                  break;
                }
            case ST_VOID:
                  break;    
            default:
                    debugSerial<<F("Wrong Assignment ")<<from.cmd.itemArgType<<F("->")<<cmd.itemArgType<<endl;
         } //Translation to RGB_XX
        break;
     } //Destination

   if (prefferedStorageType) convertTo(prefferedStorageType);

    return *this;
}

bool itemCmd::isCommand()
{
  return (cmd.cmdCode);
}

bool itemCmd::isChannelCommand()
{
 if (cmd.suffixCode==S_NOTFOUND || cmd.suffixCode==S_CMD )
    return (cmd.cmdCode);
  else return 0;  
}
bool itemCmd::isValue()
{
return (cmd.itemArgType);
}

bool itemCmd::isColor()
{

return (cmd.itemArgType==ST_HS || cmd.itemArgType==ST_HSV255 || cmd.itemArgType==ST_RGB || cmd.itemArgType==ST_RGBW);

}

long int itemCmd::getTens()
{
  switch (cmd.itemArgType) {

    case ST_INT32:
    case ST_UINT32:
    case ST_RGB:
    case ST_RGBW:
    
      return param.aslong*10;
    case ST_PERCENTS255:
    case ST_HSV255:
      return param.v*10;

    case ST_FLOAT:
    case ST_FLOAT_CELSIUS:
    case ST_FLOAT_FARENHEIT:
      return param.asfloat*10.0;
    case ST_TENS:
      return param.aslong/(TENS_BASE/10); 
    default:
    return 0;
  }
}

long int itemCmd::getTens_raw()
{
  switch (cmd.itemArgType) {

    case ST_INT32:
    case ST_UINT32:
    case ST_RGB:
    case ST_RGBW:
    
      return param.aslong*TENS_BASE;
    case ST_PERCENTS255:
    case ST_HSV255:
      return param.v*TENS_BASE;

    case ST_FLOAT:
    case ST_FLOAT_CELSIUS:
    case ST_FLOAT_FARENHEIT:
      return param.asfloat*(float)TENS_BASE;
    case ST_TENS:
      return param.aslong; 
    default:
    return 0;
  }
}



long int itemCmd::getInt()
{
  switch (cmd.itemArgType) {

    case ST_INT32:
    case ST_UINT32:
    case ST_RGB:
    case ST_RGBW:
    
      return param.aslong;
    case ST_PERCENTS255:
    case ST_HSV255:
      return param.v;

    case ST_FLOAT:
    case ST_FLOAT_CELSIUS:
    case ST_FLOAT_FARENHEIT:
      return param.asfloat;
    case ST_TENS:
      return param.aslong/TENS_BASE; 


 
    default:
    return 0;
  }
}

char*  itemCmd::getString()
{
  switch (cmd.itemArgType) {

    case ST_STRING:
      return param.asString;
    default:
    return NULL;
  }
}


float itemCmd::getFloat()
{
  switch (cmd.itemArgType) {

    case ST_INT32:
    case ST_UINT32:
    case ST_RGB:
    case ST_RGBW:
   
      return param.aslong;
     case ST_TENS:
      return param.aslong/TENS_BASE;  

    case ST_PERCENTS255:
    case ST_HSV255:
      return param.v;

    case ST_FLOAT:
    case ST_FLOAT_CELSIUS:
    case ST_FLOAT_FARENHEIT:
      return param.asfloat;

 
    default:
    return 0.;
  }
}



long int itemCmd::getSingleInt()
{
  if (cmd.cmdCode) return cmd.cmdCode;
  return getInt();
}

short itemCmd::getPercents(bool inverse)
{
  switch (cmd.itemArgType) {
  
    case ST_INT32:
    case ST_UINT32:
           if (inverse) return constrain(100-param.asInt32,0,100);
            else return constrain(param.asInt32,0,100); 

    case ST_PERCENTS255:
    case ST_HSV255:
      if (inverse) return map(param.v,0,255,100,0);
          else return map(param.v,0,255,0,100);

    case ST_FLOAT:
       if (inverse) return constrain (100-param.asfloat,0,100);
            else return constrain (param.asfloat,0,100);   

    case ST_TENS:
           if (inverse) return constrain (100-param.asInt32/TENS_BASE,0,100);
            else return constrain(param.asInt32/TENS_BASE,0,100);
    case ST_VOID:
          return 0;

    default:
    return -1;
  }
}

bool itemCmd::setPercents(int percents)
{
  switch (cmd.itemArgType) {

    case ST_INT32:
    case ST_UINT32:
        param.asInt32=map(percents,0,100,0,255);
    break;       
    case ST_PERCENTS255:
    case ST_HSV255:
      param.v=map(percents,0,100,0,255);
    break;  
    case ST_FLOAT:
      param.asfloat=map(percents,0,100,0,255);;
    break;   
    case ST_TENS:
    param.asInt32 = map(percents,0,100,0,255*TENS_BASE);;
    default:
    return false;
  }
return true;  
}

short itemCmd::getPercents255(bool inverse)
{
  switch (cmd.itemArgType) {

    case ST_INT32:
    case ST_UINT32:
        if (inverse) return 255-constrain(param.asInt32,0,255); else return constrain(param.asInt32,0,255);

    case ST_PERCENTS255:
    case ST_HSV255:
       if (inverse) return 255-param.v; else return param.v;

    case ST_FLOAT:
       if (inverse) return 255-constrain(param.asfloat,0,255); else return constrain(param.asfloat,0,255); 

    case ST_TENS:
      if (inverse) return 255-constrain(param.asInt32/TENS_BASE,0,255); else return constrain(param.asInt32/TENS_BASE,0,255); 
    
    case ST_VOID:
      return 0;
    default:
    return -1;
  }
}

uint8_t itemCmd::getCmd()
{
  return cmd.cmdCode;
}

uint8_t itemCmd::getArgType()
{
  return cmd.itemArgType;
}

itemCmd itemCmd::setArgType(uint8_t type)
{
   cmd.itemArgType=type & 0xF;
  return *this;
}


itemCmd itemCmd::convertTo(uint8_t type)
{ 
  if (cmd.itemArgType == type) return *this;
  debugSerial << F("Converting ") << cmd.itemArgType << F("->") << type << F(" ");
  itemCmd out(type,cmd.cmdCode);
   *this=out.assignFrom(*this);
   debugOut();
  return *this;
}

uint8_t itemCmd::getCmdParam()
{
  if (isCommand()) return cmd.cmdParam;
  return 0;
}

itemCmd itemCmd::Percents(int i)
    {
      if (i<0) i=0;
      if (i>100) i=100;

      switch (cmd.itemArgType)
      {
       case ST_HSV255:
       case ST_PERCENTS255:
       break;
       default:
       cmd.itemArgType=ST_PERCENTS255;
      }
      param.v=map(i,0,100,0,255);
      return *this;
    }

    itemCmd itemCmd::Percents255(int i)
        {
          if (i<0) i=0;
          if (i>255) i=255;

          switch (cmd.itemArgType)
          {
           case ST_HSV255:
           case ST_PERCENTS255:
              param.v=i;
            break;
           default:
           cmd.itemArgType=ST_PERCENTS255;
           param.v=i;
          }
          return *this;
        }

itemCmd itemCmd::Int(int32_t i)
        {
          cmd.itemArgType=ST_INT32;
          param.asInt32=i;
          return *this;
        }

itemCmd itemCmd::uInt(uint32_t i)
        {
          cmd.itemArgType=ST_UINT32;
          param.asUint32=i;
          return *this;
        }

itemCmd itemCmd::Float(float f)
        {
          cmd.itemArgType=ST_FLOAT;
          param.asfloat=f;
          return *this;
        }

itemCmd itemCmd::Tens(int32_t i)
        {
          cmd.itemArgType=ST_TENS;
          param.asInt32=i*(TENS_BASE/10);
          return *this;
        }

itemCmd itemCmd::Tens_raw(int32_t i)
        {
          cmd.itemArgType=ST_TENS;
          param.asInt32=i;
          return *this;
        }        

itemCmd itemCmd::HSV(uint16_t h, uint8_t s, uint8_t v)
{
  cmd.itemArgType=ST_HSV255;
  param.h=h;
  param.s=s;
  param.v=map(v,0,100,0,255);

  return *this;
}

itemCmd itemCmd::HSV255(uint16_t h, uint8_t s, uint8_t v)
{
  cmd.itemArgType=ST_HSV255;
  param.h=h;
  param.s=s;
  param.v=v;

  return *this;
}


itemCmd itemCmd::HS(uint16_t h, uint8_t s)
{
  cmd.itemArgType=ST_HS;
  param.h=h;
  param.s=s;

  return *this;
}


itemCmd itemCmd::RGB(uint8_t r, uint8_t g, uint8_t b)
{
  cmd.itemArgType=ST_RGB;
  param.r=r;
  param.g=g;
  param.b=b;

  return *this;
}

itemCmd itemCmd::RGBW(uint8_t r, uint8_t g, uint8_t b, uint8_t w)
{
  cmd.itemArgType=ST_RGBW;
  param.r=r;
  param.g=g;
  param.b=b;
  param.w=w;

  return *this;
}

itemCmd itemCmd::Str(char * str)
{
 cmd.itemArgType=ST_STRING;
 param.asString = str;
 return *this;  
}


itemCmd itemCmd::Cmd(uint8_t i)
    {
          cmd.cmdCode=i;
          return *this;
    }

itemCmd itemCmd::Cmd(itemCmd i)
    {
          cmd.cmdCode=i.cmd.cmdCode;
          return *this;
    }

uint8_t itemCmd::getSuffix()
{
  return cmd.suffixCode;
}

itemCmd itemCmd::setSuffix(uint8_t suffix)
{
  cmd.suffixCode=suffix;
  return *this;
}

bool itemCmd::loadItem(Item * item, uint16_t optionsFlag)
{
   bool res = false;

  if (item && item->isValid())
  {
  short subtype =item->getSubtype();
  if (optionsFlag & FLAG_COMMAND)    cmd.cmdCode    =  item->getCmd();

  //if (optionsFlag & FLAG_FLAGS)    
  //        if (item->getFlag(FLAG_DISABLED))
  //          cmd.cmdCode    =  CMD_DISABLE;

  if (subtype)
        {
          cmd.itemArgType= subtype;
          if (optionsFlag & FLAG_PARAMETERS) param.asInt32  =  item->itemVal->valueint;
          //debugSerial<<F("Loaded :");
          //debugOut();
          return true;
        }
 
        if (optionsFlag & FLAG_PARAMETERS) 
          switch (item->itemVal->type)
          {
            case aJson_Int:

            Int(item->itemVal->valueint);
               //debugSerial<<F("Loaded Int:");
               //debugOut();
            return true;
            
            case aJson_Float:
            Float(item->itemVal->valuefloat);
               //debugSerial<<F("Loaded Float:");
               //debugOut();
            return true;
          }

  }
return false;
}


bool itemCmd::loadItemDef(Item * item, uint16_t optionsFlag)
{
           //Restrieve previous channel state to "stored"
           //if no values - set default values, save and put to MQTT
           if (!loadItem(item,optionsFlag))
                                    {
                                    debugSerial<<F("No stored values - default: ");
                                    setChanType(item->getChanType());
                                    setDefault();
                                    saveItem(item);
                                    debugOut();
                                    item->SendStatus(FLAG_PARAMETERS | FLAG_SEND_DEFFERED);
                                    return false;
                                    }
return true;                                    
}

bool itemCmd::saveItem(Item * item, uint16_t optionsFlag)
{
  if (item && item->isValid())
  {

  if (optionsFlag & FLAG_FLAGS)    
                                  switch (cmd.cmdCode)
                                  {
                                  case CMD_DISABLE:
                                  item->setFlag(FLAG_DISABLED);
                                  break;

                                  case CMD_ENABLE:
                                  item->clearFlag(FLAG_DISABLED);
                                  //? item->clearFlag(FLAG_FREEZED); //?
                                  break;
                                  case CMD_FREEZE:
                                  item->setFlag(FLAG_FREEZED);
                                  break;

                                  case CMD_UNFREEZE:
                                  item->clearFlag(FLAG_FREEZED);
                                  break;                                  
                                  }  
  if (optionsFlag & FLAG_COMMAND)    
                                  switch (cmd.cmdCode)
                                  {
                                  case CMD_DISABLE:
                                  case CMD_ENABLE:
                                  case CMD_FREEZE:
                                  case CMD_UNFREEZE:
                                  break;
                                  default:  
                                  item->setCmd(cmd.cmdCode);
                                  
                                  }
  if (optionsFlag & FLAG_PARAMETERS) 
                                    switch (cmd.itemArgType)
                                    {
                                     case ST_FLOAT:
                                     case ST_FLOAT_CELSIUS:
                                     item->setFloatVal(param.asfloat);
                                     //
                                     break;

                                     case ST_INT32:
                                     case ST_UINT32:
                                     item->setVal(param.asInt32);
                                     break;

                                     default: 
                                     //item->setSubtype(cmd.itemArgType); 
                                     item->setVal(param.asInt32);
                                     item->setSubtype(cmd.itemArgType); 
                                    }
  //debugSerial<<F("Saved:");
  //debugOut();
  return true;
  }
return false;
}

 int replaceCmdToInt(aJsonObject* verb)
  {
   if (verb && verb->type == aJson_String) 
    {
      int cmd = txt2cmd(verb->valuestring);
      if (cmd>0)
          {
            freeString(verb->valuestring);
            verb->valueint=cmd;
            verb->type=aJson_Int;
            return verb->valueint;
          }   
    } else if (verb && verb->type == aJson_Int) return verb->valueint;
   return 0; 
  }

  int replaceTypeToInt(aJsonObject* verb)
  {
    if (!verb) return -1;
   switch (verb->type)
   {
      case aJson_Int: return verb->valueint;    
      case aJson_Array: return replaceTypeToInt(verb->child);     
      case aJson_Object:  return replaceTypeToInt(aJson.getObjectItem(verb, "type"));
      case  aJson_String: 
      {
      int type = type2num(verb->valuestring);
      if (type>=0)
          {
            freeString(verb->valuestring);
            verb->valueint=type;
            verb->type=aJson_Int;
            return verb->valueint;
          }   
      } 
      break;
   }          
   return -1; 
  }

  // Mapping from unified itemCmd object to some specific device-depended value
  itemCmd itemCmd::doMapping(aJsonObject *mappingData)
  {
   if (!mappingData) return *this; 
   aJsonObject *cmdMapping = aJson.getObjectItem(mappingData, "cmd");
   aJsonObject *matchedCmd = NULL;
                 
   if (isCommand() && cmdMapping)
   switch (cmdMapping->type)
   {
    case aJson_Array:
    {
      traceSerial<<"Array mapping"<<endl; 
      aJsonObject *i = cmdMapping->child;
      //if first array element is not array - this is default mapping value
      if (i && i->type==aJson_Int) 
        {
         matchedCmd = i;
         i=i->next;
        }

      while (i)
        {
          if (i->type == aJson_Array && aJson.getArraySize(i) == 2)
            {
              int cmdFrom = replaceCmdToInt(aJson.getArrayItem(i,0));
              aJsonObject *to = aJson.getArrayItem(i,1);
              if (getCmd()==cmdFrom && to->type == aJson_Int) 
                                            {
                                            matchedCmd=to;
                                            break;
                                            }

            }
          i=i->next;
        }
    }
   break; 
  case aJson_String:
    if (strcmp(cmdMapping->valuestring,"fan")==0)
      switch (getCmd())
      {
        /*
        case CMD_AUTO:
        case CMD_ON:
        return itemCmd().Int((uint32_t)3);
        case CMD_FAN:
        return itemCmd().Int((uint32_t)1);
        case CMD_HEAT:
        return itemCmd().Int((uint32_t)2);   
        */
        case CMD_OFF:
        return itemCmd().Int(0);
        case CMD_LOW:
        return itemCmd().Int(20);
        case CMD_MED:
        return itemCmd().Int(128);
        case CMD_HIGH:
        return itemCmd().Int(255);  

        default:
         return *this;     
      }

  } //switch 

if (matchedCmd && matchedCmd->type != aJson_NULL)  
          {
          traceSerial<<F("MAP: cmd mapped to ")<<matchedCmd->valueint<<endl;     
          return itemCmd().Int(matchedCmd->valueint); 
          }

aJsonObject *valMapping = aJson.getObjectItem(mappingData, "val");
if (isValue() && valMapping && valMapping->type == aJson_Array && aJson.getArraySize(valMapping) >= 4)
    { //ПРЯМОЕ
      //"val":[0-вход_мин, 1-вход_макс, 2-выход_мин, 3-выход_макс, 4-вход<мин_прямое, 5-вых<мин_обратное, 6-вход>макс_прямое, 7-вых>макс_обратное]   
     aJsonObject *leftBoundObj = aJson.getArrayItem(valMapping,4);
     aJsonObject *rightBoundObj = aJson.getArrayItem(valMapping,6);
 
     //if (getInt()<aJson.getArrayItem(valMapping,0)->valueint) return itemCmd().Int((uint32_t) 0); было если меньше левой границы то ноль. Неперь для такого поведения надо пятым элементом явно поставить ноль

     if (getInt()<aJson.getArrayItem(valMapping,0)->valueint) 
         {
         traceSerial<<F("MAP: value ")<<getInt()<<F(" is below left bound ")<<aJson.getArrayItem(valMapping,0)->valueint<<endl;
         if (leftBoundObj && leftBoundObj->type == aJson_Int )
                  return itemCmd().Int(leftBoundObj->valueint);
         else     return itemCmd(ST_VOID,CMD_VOID);     
         }

     if (getInt()>aJson.getArrayItem(valMapping,1)->valueint) 
         {
         traceSerial<<F("MAP: value above right bound ")<<endl; 
         if (rightBoundObj && rightBoundObj->type == aJson_Int )
                  return itemCmd().Int(rightBoundObj->valueint);
         else     return itemCmd(ST_VOID,CMD_VOID);     
         }
      long res = map(getInt(),
        aJson.getArrayItem(valMapping,0)->valueint,aJson.getArrayItem(valMapping,1)->valueint,
        aJson.getArrayItem(valMapping,2)->valueint,aJson.getArrayItem(valMapping,3)->valueint);
     traceSerial<<F("MAP: val mapped to  ")<<res<<endl;    
     return itemCmd().Int(res); 

    }
    else if (valMapping && valMapping->type == aJson_NULL)  return itemCmd(ST_VOID,CMD_VOID);
  return *this;
  }

  // Mapping from some device specific value back to unified itemcmd
  itemCmd itemCmd::doReverseMapping (aJsonObject *mappingData)
  {
  if (!mappingData) return *this; 
   aJsonObject *cmdMapping = aJson.getObjectItem(mappingData, "cmd");
   aJsonObject *matchedCmd = NULL;
                 
   if (cmdMapping)

    switch (cmdMapping->type )
    {
    case aJson_Array:  
    {
      aJsonObject *i = cmdMapping->child;
      //if first array element is not array - this is default mapping value, no reverse mapping, skipping
      if (i && i->type==aJson_Int) 
        {
         //matchedCmd = i;
         i=i->next;
        }

      while (i)
        {
          if (i->type == aJson_Array && aJson.getArraySize(i) == 2)
            {
              aJsonObject *from =aJson.getArrayItem(i,0);
              int cmdFrom = replaceCmdToInt(from);
              aJsonObject *to = aJson.getArrayItem(i,1);
              if (to->type == aJson_Int && getInt()==to->valueint) 
                                            {
                                            matchedCmd=from;
                                            break;
                                            }

            }
          i=i->next;
        }
    }
 break;   
 case aJson_String:
 {
   if (strcmp(cmdMapping->valuestring,"fan")==0)
     {  
     if (getInt())
         switch (constrain(map(getInt(),0,255,1,3),1,3))
            {
              case 1:
                  return itemCmd().setSuffix(S_FAN).Cmd(CMD_LOW);
              case 2:
                  return itemCmd().setSuffix(S_FAN).Cmd(CMD_MED); 
              case 3:
                  return itemCmd().setSuffix(S_FAN).Cmd(CMD_HIGH);
            }
      else return itemCmd().setSuffix(S_FAN).Cmd(CMD_OFF);
     }     
 }
}//switch


if (matchedCmd)  return itemCmd().Cmd(matchedCmd->valueint); 

aJsonObject *valMapping = aJson.getObjectItem(mappingData, "val");
if (valMapping && valMapping->type == aJson_Array && aJson.getArraySize(valMapping) >= 4)
    {
//ОБРАТНОЕ      
//"val":[0-вход_мин, 1-вход_макс, 2-выход_мин, 3-выход_макс, 4-вход<мин_прямое, 5-вых<мин_обратное, 6-вход>макс_прямое, 7-вых>макс_обратное]      
     int a =  aJson.getArrayItem(valMapping,0)->valueint;
     int b =  aJson.getArrayItem(valMapping,1)->valueint;
     int c =  aJson.getArrayItem(valMapping,2)->valueint;
     int d =  aJson.getArrayItem(valMapping,3)->valueint;

     aJsonObject *leftBoundObj = aJson.getArrayItem(valMapping,5);
     aJsonObject *rightBoundObj = aJson.getArrayItem(valMapping,7);
    // Dev to unified
    //было если меньше левой границы то ноль. Неперь для такого поведения надо 7m элементом явно поставить ноль
    //if (getInt()<aJson.getArrayItem(valMapping,2)->valueint) return itemCmd().Int((uint32_t) 0);  


     if (getInt()<aJson.getArrayItem(valMapping,2)->valueint) 
         {
         if (leftBoundObj && leftBoundObj->type == aJson_Int )
                  return itemCmd().Int(leftBoundObj->valueint);
         else     return itemCmd(ST_VOID,CMD_VOID);     
         }

     if (getInt()>aJson.getArrayItem(valMapping,3)->valueint) 
         {
         if (rightBoundObj && rightBoundObj->type == aJson_Int )
                  return itemCmd().Int(rightBoundObj->valueint);
         else     return itemCmd(ST_VOID,CMD_VOID);     
         }

     int diff = ((b-a)/(d-c))/2;
     //return itemCmd().Int((uint32_t) constrain(map(getInt(),c,d,a,b)+diff,0,255));  
     return itemCmd().Int((uint32_t) constrain(map(getInt(),c,d,a,b)+diff,0,b));  
     //return itemCmd().Int((uint32_t) map(getInt(),c,d,a,b)+diff);  
    }
  if (valMapping && valMapping->type == aJson_NULL) return itemCmd();  
  return *this;
  }


char * itemCmd::toString(char * Buffer, int bufLen, int sendFlags, bool scale100 )
     {

       if (!Buffer || !bufLen) return NULL;
       *Buffer=0;
       char * argPtr=Buffer;
       if (isCommand() && (sendFlags & FLAG_COMMAND) && cmd.cmdCode<commandsNum)
                        {
                          int len;
                          strncpy_P(Buffer, commands_P[cmd.cmdCode], bufLen);
                          if (isValue() && (sendFlags & FLAG_PARAMETERS)) strncat(Buffer, " ", bufLen);
                          len=strlen(Buffer);
                          argPtr+=len;
                          bufLen-=len;
                          bufLen--;
                        }
       if (isValue() && (sendFlags & FLAG_PARAMETERS))
       {
       //strncat(Buffer, " ", bufLen);
       //bufLen--;
       //argPtr++;

       switch (cmd.itemArgType)
       { short colorTemp;

         case ST_PERCENTS255:
            snprintf(argPtr, bufLen, "%u", (scale100)?map (param.v,0,255,0,100):param.v);
        break;
         case ST_UINT32:
            snprintf(argPtr, bufLen, "%lu", param.asUint32);
         break;
         case ST_INT32:
            snprintf(argPtr, bufLen, "%ld", param.asInt32);
         break;
         case ST_TENS:
         if (param.asInt32<0)
            snprintf(argPtr, bufLen, "-%ld.%0"QUOTE(TENS_FRACT_LEN)"d", abs(param.asInt32)/TENS_BASE, abs(param.asInt32 % TENS_BASE));
         else 
            snprintf(argPtr, bufLen, "%ld.%0"QUOTE(TENS_FRACT_LEN)"d", param.asInt32/TENS_BASE, abs(param.asInt32 % TENS_BASE));   
         break;
         case ST_HSV255:
         colorTemp=getColorTemp();

         if (colorTemp<0 || scale100) 
              snprintf(argPtr, bufLen, "%d,%d,%d", param.h, param.s, (scale100)?map (param.v,0,255,0,100):param.v);
            else   
              snprintf(argPtr, bufLen, "%d,%d,%d,%d", param.h, param.s, (scale100)?map (param.v,0,255,0,100):param.v, colorTemp);    

         break;
         case ST_HS:
         snprintf(argPtr, bufLen, "%d,%d", param.h, param.s);

         break;
         case ST_FLOAT_CELSIUS:
         case ST_FLOAT_FARENHEIT:
         case ST_FLOAT:
        { 

        float tmpVal = (param.asfloat < 0) ? -param.asfloat : param.asfloat;
        int tmpInt1 = tmpVal;                  // Get the integer 
        float tmpFrac = tmpVal - tmpInt1;      // Get fraction 
        int tmpInt2 = trunc(tmpFrac * 1000);  // Turn into integer 
        // Print as parts, note that you need 0-padding for fractional bit.

        if (param.asfloat < 0)
        snprintf (argPtr, bufLen, "-%d.%03d",  tmpInt1, tmpInt2);
        else snprintf (argPtr, bufLen, "%d.%03d", tmpInt1, tmpInt2);
        }  
         break;
         case ST_RGB:
         snprintf(argPtr, bufLen, "%d,%d,%d", param.r, param.g, param.b);
         break;

         case ST_RGBW:
         snprintf(argPtr, bufLen, "%d,%d,%d,%d", param.r, param.g, param.b,param.w);
         break;

         case ST_STRING:
         strncpy(argPtr, param.asString,bufLen);

         break;
         default:
         ;
       }
      } 
      return Buffer;
     }

     void itemCmd::debugOut()
     {
       char buf[32];
       toString(buf,sizeof(buf));
       debugSerial<<buf<<F(" AT:")<<getArgType()<<F(" Suff:")<<getSuffix()<<endl;
     }

bool itemCmd::scale100()
{
  switch (cmd.itemArgType)
  {
    case ST_PERCENTS255:
    case ST_HSV255:
         param.v=constrain(map(param.v,0,100,0,255),0,255);
    return true;     
  }
return false;  
}