#ifndef _DATA_H
#define _DATA_H

// Smart Data datatype for Scratch 2 Arduino code generation
// operator override: https://wuyuans.com/2012/09/cpp-operator-overload/
class Data {
  public:
    double d;
    String s;
    byte isDouble;  // true: double, false: string

    // ctor
    Data() {  // default values
      this->isDouble = true;
      this->d = 0;
    }

    // ctor for different data types
    // ctor
    Data(int val) { //  error: call of overloaded 'Data(int)' is ambiguous, for Data(0) (works for Data(1)...)
      this->isDouble = true;
      this->d = val;
    }

    // ctor
    Data(unsigned int val) { this->isDouble = true; this->d = val; }

    // ctor
    Data(long val) { //  error: call of overloaded 'Data(long int)' is ambiguous, for Data(100000)
      this->isDouble = true;
      this->d = val;
    }

    // ctor
    Data(long long val) { //  error: call of overloaded 'Data(long long int)' is ambiguous, for Data(3728739547)
      this->isDouble = true;
      this->d = val;
    }

    // ctor
    Data(unsigned long val) {
      this->isDouble = true;
      this->d = val;
    }

    // ctor
    Data(double val) {
      this->isDouble = true;
      this->d = val;
    }

    // ctor
    Data(const char* val) {
      this->isDouble = false;
      this->s = val;
    }

    // ctor
    Data(String s) {
      this->isDouble = false;
      this->s = String(s);
    }

    // copy ctor 复制构造函数
    Data(const Data& other) {
      this->d = other.d;
      this->s = String(other.s);
      this->isDouble = other.isDouble;
    }

    // operator= 赋值运算符
    Data& operator=(const Data& val) {
      this->isDouble = val.isDouble;
      this->d = val.d;
      this->s = String(val.s);
      return *this;
    }
    // operator () -> Data to double
    operator double () const {
      if (this->isDouble) {
        return this->d;
      }
      return 0.0;
    }
    // operator+-*/ (only valid for double data type)
    Data operator+(Data& val) {
      if (this->isDouble && val.isDouble) {
        return Data(this->d + val.d);
      }
      return Data(0.0);
    }
    Data operator-(Data& val) {
      if (this->isDouble && val.isDouble) {
        return Data(this->d - val.d);
      }
      return Data(0.0);
    }
    Data operator -() {  // like: -xx
      if (this->isDouble) {
        return -(this->d);
      }
      return Data(0.0);
    }
    Data operator*(Data& val) {
      if (this->isDouble && val.isDouble) {
        return Data(this->d * val.d);
      }
      return Data(0.0);
    }
    Data operator/(Data& val) { // TODO: /0.0 = inf?
      if (this->isDouble && val.isDouble) {
        return Data(this->d / val.d);
      }
      return Data(0.0);
    }

    // operator++, like: x++
    Data operator++(int) { // 后置++
      Data a = *this;
      if (a.isDouble) {
        a.d = a.d + 1;
      } else {
        a.isDouble = true;
        a.d = 1;
      }
      return a;
    }

    //////////////////////////////////////////
    // operator ><=
    //////////////////////////////////////////
    // Data > Data
    bool operator > (const Data& val) {
      if (this->isDouble && val.isDouble) {
        return this->d > val.d;
      } else if(this->isDouble && !val.isDouble) {
        return String(this->d) > val.s;
      } else if(!this->isDouble && val.isDouble) {
        return this->s > String(val.d);
      } else {
        return this->s > val.s;
      }
    }

    // Data > basic
    bool operator > (int val) { return ((double)(*this) > val); }
    bool operator > (unsigned int val) { return ((double)(*this) > val); }
    bool operator > (long val) { return ((double)(*this) > val); }
    bool operator > (unsigned long val) { return ((double)(*this) > val); }
    bool operator > (float val) { return ((double)(*this) > val); }
    bool operator > (double val) { return ((double)(*this) > val); }

    // Data < Data
    bool operator < (const Data& val) {
      if (this->isDouble && val.isDouble) {
        return this->d < val.d;
      } else if(this->isDouble && !val.isDouble) {
        return String(this->d) < val.s;
      } else if(!this->isDouble && val.isDouble) {
        return this->s < String(val.d);
      } else {
        return this->s < val.s;
      }
    }

    // Data < basic
    bool operator < (int val) { return ((double)(*this) < val); }
    bool operator < (unsigned int val) { return ((double)(*this) < val); }
    bool operator < (long val) { return ((double)(*this) < val); }
    bool operator < (unsigned long val) { return ((double)(*this) < val); }
    bool operator < (float val) { return ((double)(*this) < val); }
    bool operator < (double val) { return ((double)(*this) < val); }
    
    // Data = Data
    bool operator == (const Data& val) {
      if (this->isDouble && val.isDouble) {
        //return this->d == val.d;  // TODO: precise?
        return abs(this->d - val.d) <= 0.001;
      } else if(this->isDouble && !val.isDouble) {
        return String(this->d) == val.s;
      } else if(!this->isDouble && val.isDouble) {
        return this->s == String(val.d);
      } else {
        return this->s == val.s;
      }
    }

    // Data == basic
    // Note: basic == Data不会调用此方法，会使用()强制转换为double后按照basic类型运算
    bool operator == (bool val) { return abs(*this - val) <= 0.001; }
    bool operator == (int val) { return abs(*this - val) <= 0.001; }
    bool operator == (unsigned int val) { return abs(*this - val) <= 0.001; }
    bool operator == (long val) { return abs(*this - val) <= 0.001; }
    bool operator == (unsigned long val) { return abs(*this - val) <= 0.001; }
    bool operator == (float val) { return abs(*this - val) <= 0.001; }
    bool operator == (double val) { return abs(*this - val) <= 0.001; }

    //////////////////////////////////////////
    // operator && || !
    //////////////////////////////////////////
    bool operator || (const Data& val) {
      if (this->isDouble && val.isDouble) {
        return this->d || val.d;
      }  else if(this->isDouble && !val.isDouble) {
        return this->d || true;
      } else if(!this->isDouble && val.isDouble) {
        return true || val.d;
      } else {
        return true || true;
      }
    }
    bool operator || (bool val) {
      if(this->isDouble) {
        return (this->d || val);
      } else {
        return (true || val);
      }
    }
     bool operator&& (const Data& val) {
      if (this->isDouble && val.isDouble) {
        return this->d && val.d;
      } else if(this->isDouble && !val.isDouble) {
        return this->d && true;
      } else if(!this->isDouble && val.isDouble) {
        return true && val.d;
      } else {
        return true && true;
      }
    }
    bool operator && (bool val) {
      if(this->isDouble) {
        return (this->d && val);
      } else {
        return (true && val);
      }
    }
   
    bool operator ! () {
      return *this == Data(0.0);
    }
    
    void dump() {
      Serial.println("dump the data:");
      if (this->isDouble) {
        Serial.println("double data now...");
        Serial.println(this->d);
      } else {
        Serial.println("string data now...");
        Serial.println(this->s);
      }
    }
};

#endif


//#define TEST_DATA_CODE
#ifdef TEST_DATA_CODE

/*
 * Possible ScratchPi return values
 * bool, int, byte, unsigned long, unsigned int, float, double
 */
 /*
  All Arduino data types
void
boolean
char
unsigned char
byte
int
unsigned int
word
long
unsigned long
short
float
double
string - char array
String - object
array

Non-standard datatypes:
long long
  */
void setup() {
  Serial.begin(9600);
  
  // ctor
  Data(0);
  Data(100000);
  Data(-100000);
  Data(-100000);
  Data(3728739547);
  Data(-3728739547);
  Data((bool)1);
  Data((char)1);
  Data((unsigned char)1);
  Data((byte)1);
  Data((int)1);
  Data((unsigned int)1);
  Data((word)1);
  Data((long)1);
  Data((long long)1);
  Data((unsigned long)1);
  Data((short)1);
  Data((float)1);
  Data((double)1);
  
  // 等待x秒
  delay(Data(1));
  delay((bool)1);
  delay((int)1);
  delay((byte)1);
  delay((unsigned long)1);
  delay((unsigned int)1);
  delay((float)1);
  delay((double)1);
  
  // 等待x微妙
  delayMicroseconds(Data(1));
  delayMicroseconds((bool)1);
  delayMicroseconds((int)1);
  delayMicroseconds((byte)1);
  delayMicroseconds((unsigned long)1);
  delayMicroseconds((unsigned int)1);
  delayMicroseconds((float)1);
  delayMicroseconds((double)1);

  // 直到x前都等待
  // 重复执行y直到x
  while(!Data(1));
  while(!(bool)1);
  while(!(int)1);
  while(!(unsigned long)1);
  while(!(unsigned int)1);
  while(!(float)1);
  while(!(double)1);

  // 如果　如果否则
  if(!Data(1));
  if(!(bool)1);
  if(!(int)1);
  if(!(unsigned long)1);
  if(!(unsigned int)1);
  if(!(float)1);
  if(!(double)1);

  // 赋值 basic = Data
  bool a1 = Data(1);
  int b1 = Data(1);
  byte c1 = Data(1);
  unsigned long d1 = Data(1);
  unsigned int e1 = Data(1);
  float f1 = Data(1);
  double g1 = Data(1);

  // 赋值 Data = basic
  Data x;
  x = (bool) 1;
  x = (int) 1;
  x = (byte) 1;
  x = (unsigned long) 1;
  x = (unsigned int) 1;
  x = (float) 1;
  x = (double) 1;

  // + Data + basic
  Data(1) + (bool)1;
  Data(1) + (int)1;
  Data(1) + (byte)1;
  Data(1) + (unsigned long)1;
  Data(1) + (unsigned int)1;
  Data(1) + (float)1;
  Data(1) + (double)1;
  // + basic + Data
  (bool)1 + Data(1);
  (int)1 + Data(1);
  (byte)1 + Data(1);
  (unsigned long)1 + Data(1);
  (unsigned int)1 + Data(1);
  (float)1 + Data(1);
  (double)1 + Data(1);

  // - Data - basic
  Data(1) - (bool)1;
  Data(1) - (int)1;
  Data(1) - (byte)1;
  Data(1) - (unsigned long)1;
  Data(1) - (unsigned int)1;
  Data(1) - (float)1;
  Data(1) - (double)1;
  // - basic - Data
  (bool)1 - Data(1);
  (int)1 - Data(1);
  (byte)1 - Data(1);
  (unsigned long)1 - Data(1);
  (unsigned int)1 - Data(1);
  (float)1 - Data(1);
  (double)1 - Data(1);
  // - -Data
  -Data(1);
  // - -basic (not needed...)
  
  // * Data * basic
  // * basic * Data
  
  // / 

  /////////////////////////////////////////////
  ///测试　a > b
  /////////////////////////////////////////////
  // Data > basic
  Serial.println("Testing (Data > Basic) - all should be TRUE");
  Serial.println(Data(2) > (bool)1);
  Serial.println(Data(2) > (char)1);
  Serial.println(Data(2) > (unsigned char)1);
  Serial.println(Data(2) > (byte)1);
  Serial.println(Data(2) > (int)1);
  Serial.println(Data(2) > (unsigned int)1);
  Serial.println(Data(2) > (word)1);
  Serial.println(Data(2) > (long)1);
  Serial.println(Data(2) > (unsigned long)1);
  Serial.println(Data(2) > (short)1);
  Serial.println(Data(2) > (float)1);
  Serial.println(Data(2) > (double)1);

  // basic > Data
  Serial.println("Testing (Basic > Data) - all should be TRUE");
  Serial.println((bool)1 > Data(0));
  Serial.println((char)3 > Data(2));
  Serial.println((unsigned char)3 > Data(2));
  Serial.println((byte)3 > Data(2));
  Serial.println((int)3 > Data(2));
  Serial.println((unsigned int)3 > Data(2));
  Serial.println((word)3 > Data(2));
  Serial.println((long)3 > Data(2));
  Serial.println((unsigned long)3 > Data(2));
  Serial.println((short)3 > Data(2));
  Serial.println((float)3 > Data(2));
  Serial.println((double)3 > Data(2));

  // Data > Data
  Serial.println("Testing (Data > Data) - all should be TRUE");
  Serial.println(Data(123) > Data(122)); // 按照数字比较
  Serial.println(!(Data("123") > Data(123))); // 按照字符串比较
  Serial.println(!(Data("123") > Data(124))); // 按照字符串比较
  Serial.println(Data("123") > Data(122));   // 按照字符串比较
  Serial.println(Data("abc") > Data("abb"));
  Serial.println(!(Data("abc") > Data("abd")));
  Serial.println(!(Data("abc") > Data("abcd")));

  /////////////////////////////////////////////
  ///测试　a < b
  /////////////////////////////////////////////
  // Data < basic
  Serial.println("Testing (Data < Basic) - all should be TRUE");
  Serial.println(Data(0) < (bool)1);
  Serial.println(Data(1) < (char)2);
  Serial.println(Data(1) < (unsigned char)2);
  Serial.println(Data(1) < (byte)2);
  Serial.println(Data(1) < (int)2);
  Serial.println(Data(1) < (unsigned int)2);
  Serial.println(Data(1) < (word)2);
  Serial.println(Data(1) < (long)2);
  Serial.println(Data(1) < (unsigned long)2);
  Serial.println(Data(1) < (short)2);
  Serial.println(Data(1) < (float)2);
  Serial.println(Data(1) < (double)2);

  // basic < Data
  Serial.println("Testing (Basic < Data) - all should be TRUE");
  Serial.println((bool)1 < Data(2));
  Serial.println((char)1 < Data(2));
  Serial.println((unsigned char)1 < Data(2));
  Serial.println((byte)1 < Data(2));
  Serial.println((int)1 < Data(2));
  Serial.println((unsigned int)1 < Data(2));
  Serial.println((word)1 < Data(2));
  Serial.println((long)1 < Data(2));
  Serial.println((unsigned long)1 < Data(2));
  Serial.println((short)1 < Data(2));
  Serial.println((float)1 < Data(2));
  Serial.println((double)1 < Data(2));

  // Data < Data
  Serial.println("Testing (Data < Data) - all should be TRUE");
  Serial.println(Data(122) < Data(123)); // 按照数字比较
  //Serial.println(!(Data("123") < Data(123))); // 按照字符串比较 // 特殊情况，不满足！！！ 相当于"123"和"123.00"比较！！！！不过在ScratchPi中，不可能产生Data("123")的数字！！！暂时不考虑！！！
  Serial.println(!(Data("124") < Data(123))); // 按照字符串比较
  Serial.println(Data("122") < Data(123));   // 按照字符串比较
  Serial.println(Data("abb") < Data("abc"));
  Serial.println(!(Data("abd") < Data("abc")));
  Serial.println(!(Data("abcd") < Data("abc")));

  /////////////////////////////////////////////
  ///测试　a == b
  /////////////////////////////////////////////
  // Data == basic
  Serial.println("Testing (Data == Basic) - all should be TRUE");
  Serial.println(Data(0) == (bool)0);
  Serial.println(Data(1) == (bool)1);
  Serial.println(Data(1) == (char)1);
  Serial.println(Data(1.00001) == (char)1); // 浮点比较，一定范围都算相等！
  Serial.println(Data(1) == (unsigned char)1);
  Serial.println(Data(1) == (byte)1);
  Serial.println(Data(1) == (int)1);
  Serial.println(Data(1) == (unsigned int)1);
  Serial.println(Data(1) == (word)1);
  Serial.println(Data(1) == (long)1);
  Serial.println(Data(1) == (unsigned long)1);
  Serial.println(Data(1) == (short)1);
  Serial.println(Data(1) == (float)1);
  Serial.println(Data(1) == (double)1);
  Serial.println(!(Data(2) == (double)1));
  
  // basic == Data
  Serial.println("Testing (Basic == Data) - all should be TRUE");
  Serial.println((bool)0 == Data(0));
  Serial.println((bool)1 == Data(1));
  Serial.println((char)1 == Data(1));
  //Serial.println((char)1 == Data(1.00001)); // 这个会严格按照浮点数比较，不能相等，无法实现一定范围内相等！！！// 特殊情况，不满足！！！
  Serial.println((unsigned char)2 == Data(2));
  Serial.println((byte)2 == Data(2));
  Serial.println((int)2 == Data(2));
  Serial.println((unsigned int)2 == Data(2));
  Serial.println((word)2 == Data(2));
  Serial.println((long)2 == Data(2));
  Serial.println((unsigned long)2 == Data(2));
  Serial.println((short)2 == Data(2));
  Serial.println((float)2 == Data(2));
  Serial.println((double)2 == Data(2));

  // Data == Data
  Serial.println("Testing (Data == Data) - all should be TRUE");
  Serial.println(Data(122) == Data(122)); // 按照数字比较
  Serial.println(!(Data("123") == Data(124))); // 按照字符串比较
  Serial.println(Data("abc") == Data("abc"));
  Serial.println(!(Data("abc") == Data("abd")));
  Serial.println(!(Data("abc") == Data("abcd")));

  /////////////////////////////////////////////
  ///测试　fun(a)
  /////////////////////////////////////////////
  random(Data(1), Data(10) + 1);
  abs(Data(1));
  sin(Data(1));

  ////////////////////////
  /// regression bug cases
  ////////////////////////
  Serial.println("Test regression bug cases - all should be TRUE");
  // bug#7
  Data var_bug7 = Data(-1.0);
  // abs(var_bug7); // !bug: value of var_bug7 should Not be changed!
  Serial.println(abs(var_bug7) == Data(1.0));
  Serial.println(var_bug7 == Data(-1.0));
}

void loop() {
  
}

#endif
