/*	Copyright  (c)	Günter Woigk 2010 - 2018
					mailto:kio@little-bat.de

	This file is free software

 	This program is distributed in the hope that it will be useful,
 	but WITHOUT ANY WARRANTY; without even the implied warranty of
 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

	Redistribution and use in source and binary forms, with or without
	modification, are permitted provided that the following conditions are met:

	• Redistributions of source code must retain the above copyright notice,
	  this list of conditions and the following disclaimer.
	• Redistributions in binary form must reproduce the above copyright notice,
	  this list of conditions and the following disclaimer in the documentation
	  and/or other materials provided with the distribution.

	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
	THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
	PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
	CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
	OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
	WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
	OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
	ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <cmath>
//#include <limits>
#include "Libraries/kio/kio.h"
#include "Libraries/cstrings/utf8.h"
#include "Value.h"
#include "Type.h"


inline cBasicType* intForSize (int64 n)
{
	if (n < 0) n = ~n;
	return n <= 7 ? tint8 : n <= 15 ? tint16 : n<=31 ? tint32 : tint64;
}

inline cBasicType* uintForSize (uint64 n)
{
	return n <= 8 ? tuint8 : n <= 16 ? tuint16 : n<=32 ? tuint32 : tuint64;
}

inline int64 maskValue (int64 n, uint bits)
{
	switch(bits>>4)
	{
	case 0: return int8(n);
	case 1:	return int16(n);
	case 2:	return int32(n);
	default:return n;
	}
}

inline uint64 maskValue (uint64 n, uint bits)
{
	switch(bits>>4)
	{
	case 0: return uint8(n);
	case 1:	return uint16(n);
	case 2:	return uint32(n);
	default:return n;
	}
}

Value::Value (uint64 n) noexcept
:	type(uintForSize(n)),
	value(n)
{}

Value::Value (int64 n) noexcept
:	type(intForSize(n)),
	value(n)
{}

Value::Value (cType* t, float128 n) noexcept
:	type(t),
	value(n)
{
	assert(t->isNumeric());
	assert(!t->isSignedInt() || value == int64(value));
	assert(!t->isUnsignedInt() || value == uint64(value));
}

Value::Value (cstr utf8string) noexcept	 // sets errno
:	type(tcstr),
	value(0)
{
	assert(tcstr->basetype == tchar);

	uint cnt = utf8::charcount(utf8string);

	if(tchar->bits == 8)
	{
		new(&u8array) Array<uint8>(cnt);
		utf8::utf8_to_ucs1(utf8string,&u8array[0]);
	}
	else if(tchar->bits == 16)
	{
		new(&u16array) Array<uint16>(cnt);
		utf8::utf8_to_ucs2(utf8string,&u16array[0]);
	}
	else if(tchar->bits == 32)
	{
		new(&u32array) Array<uint32>(cnt);
		utf8::utf8_to_ucs4(utf8string,&u32array[0]);
	}
	else IERR();
}

Value::Value (cType* t, Array<uint8> q) noexcept
:	type(t),
	u8array(q)
{
	assert(t->isArray() && t->basetype->stripEnum() == tuint8);
}

Value::Value (cType* t, Array<int8> q) noexcept
:	type(t),
	i8array(q)
{
	assert(t->isArray() && t->basetype->stripEnum() == tint8);
}

Value::Value (cType* t, Array<uint16> q) noexcept
:	type(t),
	u16array(q)
{
	assert(t->isArray() && t->basetype->stripEnum() == tuint16);
}

Value::Value (cType* t, Array<int16> q) noexcept
:	type(t),
	i16array(q)
{
	assert(t->isArray() && t->basetype->stripEnum() == tint16);
}

Value::Value (cType* t, Array<uint32> q) noexcept
:	type(t),
	u32array(q)
{
	assert(t->isArray() && t->basetype->stripEnum() == tuint32);
}

Value::Value (cType* t, Array<int32> q) noexcept
:	type(t),
	i32array(q)
{
	assert(t->isArray() && t->basetype->stripEnum() == tint32);
}

Value::Value (cType* t, Array<uint64> q) noexcept
:	type(t),
	u64array(q)
{
	assert(t->isArray() && t->basetype->stripEnum() == tuint64);
}

Value::Value (cType* t, Array<int64> q) noexcept
:	type(t),
	i64array(q)
{
	assert(t->isArray() && t->basetype->stripEnum() == tint64);
}

Value::Value (cType* t, Array<float32> q) noexcept
:	type(t),
	f32array(q)
{
	assert(t->isArray() && t->basetype->stripEnum() == tsfloat);
}

Value::Value (cType* t, Array<float64> q) noexcept
:	type(t),
	f64array(q)
{
	assert(t->isArray() && t->basetype->stripEnum() == tfloat);
}

Value::Value (cType* t, Array<float128> q) noexcept
:	type(t),
	f128array(q)
{
	assert(t->isArray() && t->basetype->stripEnum() == tlfloat);
}

Value::~Value()
{
	if(type->isAllocated()) u8array.purge();	// works for all item types
}

Value::Value (Value const& q)
:	type(q.type),
	value(q.value)
{
	if(type->isAllocated())
	{
		if (type->basetype->bits == 8)		 new(&u8array)  Array<uint8> (q.u8array);
		else if (type->basetype->bits == 16) new(&u16array) Array<uint16>(q.u16array);
		else if (type->basetype->bits == 32) new(&u32array) Array<uint32>(q.u32array);
		else if (type->basetype->bits == 64) new(&u64array) Array<uint64>(q.u64array);
		else if (type->basetype->bits == 128) new(&f128array) Array<float128>(q.f128array);
		else TODO();
	}
}

Value::Value (Value&& q)
:	type(q.type),
	value(q.value)
{
	if(type->isAllocated())
		new(&u8array) Array<uint8>(std::move(q.u8array));	// works for all item types
}

Value& Value::operator= (Value const& q)
{
	if(this != &q)
	{
		this->~Value();
		new(this) Value(q);
	}
	return *this;
}

Value& Value::operator= (Value&& q)
{
	assert(this != &q);

	this->~Value();
	new(this) Value(std::move(q));

	return *this;
}

cstr Value::getString() const	// tempmem
{
	assert(isString());

	if(tchar->bits == 8)  return utf8::to_utf8(u8array.getData(), u8array.count());
	if(tchar->bits == 16) return utf8::to_utf8(u16array.getData(),u16array.count());
	if(tchar->bits == 32) return utf8::to_utf8(u32array.getData(),u32array.count());
	IERR();
}


/* --------------------------------------------------------------
	Rechnen mit konstanten Zahlen

	Gerechnet wird mit int64, uint64 und float128.
	Typen werden möglichst beibehalten.
	Integer-Typen werden ggf. in Richtung int/uint vergrößert, selten verkleinert.
	Bei ungeeigneten Datentypen für eine Operation wird ein cstring geworfen.
-------------------------------------------------------------- */

/*BasicType* intForBits (uint n)
{
	return n > 16
		? n > 32 ? tint64 : tint32
		: n > 8  ? tint16 : tint8;
}*/

/*BasicType* uintForBits (uint n)
{
	return n > 16
		? n > 32 ? tuint64 : tuint32
		: n > 8  ? tuint16 : tuint8;
}*/

Value floatValue ( cType* t1, cType* t2, float128 value)
{
	assert(t1->isFloat() || t2->isFloat());
	assert(t1->isNumeric() && t2->isNumeric());

	if (t1->isInteger()) t1 = t2; else
	if (t2->isInteger()) t2 = t1; else
	if (t2->bits > t1->bits) t1 = t2;

	return Value(t1,value);
}

Value Value::cast (cType* zt) const
{
	if (isNumeric())
	{
		assert(zt->isNumeric());

		if (zt->isFloat()) return Value(zt,value);

		int64 n = value<0 ? int64(value) : int64(uint64(value));

		switch (int(zt->stripEnum()->type))
		{
		case Type::INT8:	return Value(zt, int8 (n));
		case Type::UINT8:	return Value(zt,uint8 (n));
		case Type::INT16:	return Value(zt, int16(n));
		case Type::UINT16:	return Value(zt,uint16(n));
		case Type::INT32:	return Value(zt, int32(n));
		case Type::UINT32:	return Value(zt,uint32(n));
		case Type::INT64:	return Value(zt, int64(n));
		case Type::UINT64:	return Value(zt,uint64(n));
		}
	}

	if (isArray())
	{
		assert(zt->isArray() && type->basetype->bits == zt->basetype->bits);

		switch (int(type->basetype->stripEnum()->type))
		{
		case Type::INT8:	return Value(zt,i8array);
		case Type::UINT8:	return Value(zt,u8array);
		case Type::INT16:	return Value(zt,i16array);
		case Type::UINT16:	return Value(zt,u16array);
		case Type::INT32:	return Value(zt,i32array);
		case Type::UINT32:	return Value(zt,u32array);
		case Type::INT64:	return Value(zt,i64array);
		case Type::UINT64:	return Value(zt,u64array);
		case Type::SFLOAT:	return Value(zt,f32array);
		case Type::FLOAT:	return Value(zt,f64array);
		case Type::LFLOAT:	return Value(zt,f128array);
		}
	}

	IERR();
}

Value Value::operator<< (Value const& q) const
{
	if (!isNumeric() || !q.isInteger())	throw "bad data type";

	if (isFloat())
		return Value(type, ldexpl(value, int(q.value)));

	if (abs(q.value) > 64) throw "value out of range";

	if (isSignedInt())
		return Value(int64(value) << int(q.value));

	else
		return Value(uint64(value) << int(q.value));
}

Value Value::operator>> (Value const& q) const
{
	if (!isNumeric() || !q.isInteger())	throw "bad data type";

	if (isFloat())
		return Value(type, ldexpl(value, -q.i16Value()));

	if (abs(q.value) > 64) throw "value out of range";

	if (isSignedInt())
		return Value(int64(value) >> int(q.value));

	else
		return Value(uint64(value) >> int(q.value));
}

Value Value::operator& (Value const& q) const
{
	if(!isInteger() || !q.isInteger()) throw "bad data type";

	// signed & signed = signed; else unsigned
	bool sn = isSignedInt() && q.isSignedInt();

	if (sn)	return Value(int64(value) & int64(q.value));
	else	return Value(rawUInt64() & q.rawUInt64());
}

Value Value::operator| (Value const& q) const
{
	if(!isInteger() || !q.isInteger()) throw "bad data type";

	// unsigned | unsigned = unsigned; else signed
	bool sn = isSignedInt() || q.isSignedInt();

	if (sn)	return Value(rawInt64() | q.rawInt64());
	else	return Value(uint64(value) | uint64(q.value));
}

Value Value::operator^ (Value const& q) const
{
	if (!isInteger() || !q.isInteger()) throw "bad data type";

	// unsigned ^ unsigned = unsigned; else signed
	bool sn = isSignedInt() || q.isSignedInt();

	if (sn)	return Value(rawInt64() ^ q.rawInt64());
	else	return Value(uint64(value) ^ uint64(q.value));
}

Value Value::operator+ (Value const& q) const
{
	if (isArray() && q.isArray())
	{
		assert(type->basetype == q.type->basetype);

		if (type->basetype->bits == 8)
		{
			Array<int8> z(i8array); z.append(q.i8array);
			return Value(type, std::move(z));
		}
		if (type->basetype->bits == 16)
		{
			Array<int16> z(i16array); z.append(q.i16array);
			return Value(type, std::move(z));
		}
		if (type->basetype->bits == 32)
		{
			Array<int32> z(i32array); z.append(q.i32array);
			return Value(type, std::move(z));
		}
		if (type->basetype->bits == 64)
		{
			Array<int64> z(i64array); z.append(q.i64array);
			return Value(type, std::move(z));
		}
		if (type->basetype->bits == 128)
		{
			Array<float128> z(f128array); z.append(q.f128array);
			return Value(type, std::move(z));
		}
		IERR();
	}

	if (!isNumeric() || !q.isNumeric()) throw "bad data type";		// maybe: string + char ?

	if (isFloat() || q.isFloat())
		return floatValue(type, q.type, value + q.value);

	// unsigned + unsigned = unsigned; else signed
	bool sn = isSignedInt() || q.isSignedInt();

	if (sn)	return Value(rawInt64() + q.rawInt64());
	else	return Value(uint64(value) + uint64(q.value));
}

Value Value::operator- (Value const& q) const
{
	if (!isNumeric() || !q.isNumeric()) throw "bad data type";

	if (isFloat() || q.isFloat())
		return floatValue(type, q.type, value - q.value);

	// unsigned - unsigned = unsigned; else signed
	bool sn = isSignedInt() || q.isSignedInt() || uint64(q.value) > uint64(value);

	if (sn)	return Value(rawInt64() - q.rawInt64());
	else	return Value(uint64(value) - uint64(q.value));
}

Value Value::operator* (Value const& q) const
{
	if (!isNumeric() || !q.isNumeric()) throw "bad data type";

	if (isFloat() || q.isFloat())
		return floatValue(type, q.type, value * q.value);

	// unsigned * unsigned = unsigned; else signed
	bool sn = isSignedInt() || q.isSignedInt();

	if (sn)	return Value(rawInt64() * q.rawInt64());
	else	return Value(uint64(value) * uint64(q.value));
}

Value Value::operator/ (Value const& q) const
{
	if (!isNumeric() || !q.isNumeric()) throw "bad data type";
	if (q.is0()) throw "division by zero";

	if (isFloat() || q.isFloat())
		return floatValue(type, q.type, value / q.value);

	if (isUnsignedInt() && q.isUnsignedInt())
		return Value(uint64(value) / uint64(q.value));

	bool neg = (value<0) != (q.value<0);
	int64 r = int64( uint64(abs(value)) / uint64(abs(q.value)) );

	return Value(neg ? -r : +r);
}

Value Value::operator% (Value const& q) const
{
	if (!isNumeric() || !q.isNumeric()) throw "bad data type";
	if (q.is0()) throw "division by zero";

	if (isFloat() || q.isFloat())
		return floatValue(type, q.type, fmod(value,q.value));

	if (isUnsignedInt())
		return Value(uint64(value) % uint64(abs(q.value)));

	bool neg = value < 0;
	int64 r = int64( uint64(abs(value)) % uint64(abs(q.value)) );

	return Value(neg ? -r : +r);
}

Value Value::operator== (Value const& q) const
{
	if (isNumeric() && q.isNumeric())
		return Value(tbool, value == q.value);

	if (isArray() && q.isArray() && type->basetype->stripEnum() == q.type->basetype->stripEnum())
	{
		if (type->basetype->isInteger())
		{
			if (type->basetype->bits == 8)  return Value(tbool, u8array  == q.u8array);
			if (type->basetype->bits == 16) return Value(tbool, u16array == q.u16array);
			if (type->basetype->bits == 32) return Value(tbool, u32array == q.u32array);
			if (type->basetype->bits == 64) return Value(tbool, u64array == q.u64array);
		}
		else
		{
			if (type->basetype->bits == 32) return Value(tbool, f32array == q.f32array);
			if (type->basetype->bits == 64) return Value(tbool, f64array == q.f64array);
			if (type->basetype->bits ==128) return Value(tbool,f128array == q.f128array);
		}
	}
	throw "bad data type";
}

Value Value::operator> (Value const& q) const
{
	if (isNumeric() && q.isNumeric())
		return Value(tbool, value > q.value);

	if (isArray() && q.isArray() && type->basetype->stripEnum() == q.type->basetype->stripEnum())
	{
		if (type->basetype->isInteger())
		{
			if (type->basetype->bits == 8)  return Value(tbool, u8array  > q.u8array);
			if (type->basetype->bits == 16) return Value(tbool, u16array > q.u16array);
			if (type->basetype->bits == 32) return Value(tbool, u32array > q.u32array);
			if (type->basetype->bits == 64) return Value(tbool, u64array > q.u64array);
		}
		else
		{
			if (type->basetype->bits == 32) return Value(tbool, f32array > q.f32array);
			if (type->basetype->bits == 64) return Value(tbool, f64array > q.f64array);
			if (type->basetype->bits ==128) return Value(tbool,f128array > q.f128array);
		}
	}
	throw "bad data type";
}

Value Value::operator!= ( Value const& q ) const
{
	if (isNumeric() && q.isNumeric())
		return Value(tbool, value != q.value);

	return !(*this == q);
}

Value Value::operator< (Value const& q) const
{
	if (isNumeric() && q.isNumeric())
		return Value(tbool, value < q.value);

	return q > *this;
}

Value Value::operator<= (Value const& q) const
{
	if (isNumeric() && q.isNumeric())
		return Value(tbool, value <= q.value);

	return !(*this > q);
}

Value Value::operator>= (Value const& q) const
{
	if (isNumeric() && q.isNumeric())
		return Value(tbool, value >= q.value);

	return !(*this < q);
}

Value Value::operator~ () const
{
	if (isFloat())
		return Value(type, -1.0l - value);

	if (isSignedInt())
		return Value(type, ~int64(value));

	// Note: operator~() should only be applied to unsigned integers of known size!
	if (isUnsignedInt())
		return Value(type, maskValue(~uint64(value), type->bits));

	throw "bad data type";
}

Value Value::operator! () const
{
	if (isNumeric()) return Value(tbool, is0());
	throw "bad data type";
}

Value Value::operator+ () const
{
	if (isUnsignedInt()) return Value(int64(uint64(value)));
	if (isSignedInt())	 return Value(int64(value));
	if (isFloat())		 return *this;
	throw "bad data type";
}

Value Value::operator- () const
{
	if (isUnsignedInt()) return Value(-int64(uint64(value)));
	if (isSignedInt())	 return Value(-int64(value));
	if (isFloat())		 return Value(type, -value);
	throw "bad data type";
}

//Value Value::operator++ (int)	// post++
//{
//	value += 1;
//	if (isUnsignedInt()) return uintValue(type,type,uint64(value)-1);
//	if (isSignedInt())	 return intValue(type,type,int64(value)-1);
//	if (isFloat())		 return Value(type,value-1);
//	throw "bad data type";
//}

Value eval (cValue& a, IdfID op, cValue& b)
{
	switch(int(op))
	{
	//case tANDAND:	return a && b;
	//case tOROR:  	return a || b;
	case tADD:		return a + b;
	case tSUB:		return a - b;
	case tMUL:		return a * b;
	case tDIV:		return a / b;
	case tMOD:		return a % b;
	case tAND:		return a & b;
	case tOR:		return a | b;
	case tXOR:		return a ^ b;
	case tSL:		return a << b;
	case tSR:		return a >> b;
	case tEQ:		return a == b;
	case tNE:		return a != b;
	case tGT:		return a > b;
	case tGE:		return a >= b;
	case tLT:		return a < b;
	case tLE:		return a <= b;
	default:		IERR();
	}
}

cstr Value::toString () const
{
	if (type==tchar)
	{
		uint32 c = uint32(rawUInt64());
		if (ucs4::is_printable(c)) return usingstr("'%s'",utf8::to_utf8(&c,1));
		if (c<32) return usingstr("'\\x%02X'",c);
		if (c<=0xffff) return usingstr("'\\u%04X'",c);
		else return usingstr("'\\U%08X'",c);
	}

	if(isSignedInt())	return tostr(i64Value());
	if(isUnsignedInt())	return tostr(u64Value());
	if(type==tlfloat)	return tostr(f128Value());
	if(isFloat())		return tostr(f64Value());
	if(isString())		return quotedstr(getString());
	//if(is0())			return "NULL";

	if(isArray())
	{
		uint n = u8array.count();		// same for all types
		if (n == 0) return "{}";
		cstr s = "";
		uint i = 1;

		switch(int(type->basetype->stripEnum()->type))
		{
		case Type::UINT8:  s=tostr(u8array [0]); while (i<n) { s = catstr(s,", ",tostr(u8array [i++])); } break;
		case Type::INT8:   s=tostr(i8array [0]); while (i<n) { s = catstr(s,", ",tostr(i8array [i++])); } break;
		case Type::UINT16: s=tostr(u16array[0]); while (i<n) { s = catstr(s,", ",tostr(u16array[i++])); } break;
		case Type::INT16:  s=tostr(i16array[0]); while (i<n) { s = catstr(s,", ",tostr(i16array[i++])); } break;
		case Type::UINT32: s=tostr(u32array[0]); while (i<n) { s = catstr(s,", ",tostr(u32array[i++])); } break;
		case Type::INT32:  s=tostr(i32array[0]); while (i<n) { s = catstr(s,", ",tostr(i32array[i++])); } break;
		case Type::UINT64: s=tostr(u64array[0]); while (i<n) { s = catstr(s,", ",tostr(u64array[i++])); } break;
		case Type::INT64:  s=tostr(i64array[0]); while (i<n) { s = catstr(s,", ",tostr(i64array[i++])); } break;
		case Type::SFLOAT: s=tostr(f32array[0]); while (i<n) { s = catstr(s,", ",tostr(f32array[i++])); } break;
		case Type::FLOAT:  s=tostr(f64array[0]); while (i<n) { s = catstr(s,", ",tostr(f64array[i++])); } break;
		case Type::LFLOAT: s=tostr(f128array[0]);while (i<n) { s = catstr(s,", ",tostr(f128array[i++]));} break;
		default: IERR();
		}
		return catstr("{",s,"}");
	}
	IERR();
}

void Value::serialize (FD& fd) const throws
{
	type->serialize(fd);

	if(isNumeric())
	{
		fd.write(value); return;
	}

	if(isArray())
	{
		switch(type->basetype->bits >> 4)
		{
		case 0: u8array.serialize(fd); return;
		case 1: u16array.serialize(fd); return;
		case 2: u32array.serialize(fd); return;
		case 4: u64array.serialize(fd); return;
		case 8: f128array.serialize(fd); return;
		}
	}

	IERR();
}

void Value::deserialize (FD& fd) throws
{
	assert(type->isNumeric());	// not an array

	new (this) Value(Type::restore(fd));

	if(type->isNumeric())
	{
		fd.read(value); return;
	}

	if(type->isArray())
	{
		switch(type->basetype->bits >> 4)
		{
		case 0: u8array.deserialize(fd); return;
		case 1: u16array.deserialize(fd); return;
		case 2: u32array.deserialize(fd); return;
		case 4: u64array.deserialize(fd); return;
		case 8: f128array.deserialize(fd); return;
		}
	}

	IERR();
}

















