<?php
namespace SmartData;

require_once( __DIR__ . '/Exception.php');
require_once( __DIR__ . '/Packer.php');
require_once( __DIR__ . '/Unit.php');
require_once( __DIR__ . '/SmartData.php');

use SmartData\Utility\Pack;
use SmartData\Utility\Unpack;
use SmartData\Exception\{AuthenticationException,BadRequestException,InternalException};

class MultiSmartData
{
    public $version;
    public $unit;
    public $unit_code;
    public $value;
    public $error;
    public $uncertainty;
    public $x;
    public $y;
    public $z;
    public $time;
    public $dev;
    public $gw;
    public $blob;
    public $type;
    public $encoding;
    public $workflow;
    public $signature;
    public $datapoints; // keep to send to workflow, encoded sent to the Cassandra Table.
    
    public function __construct() { } 

    public static function unpackMultiValue(& $bin)
    {
        $datapoints = array();
        $ver = Unpack::uInt8($bin, true);
        $unit_code = Unpack::uInt32($bin, true);
        $u   = Unit::interpret($unit_code);
        $x   = Unpack::int32($bin, true);
        $y   = Unpack::int32($bin, true);
        $z   = Unpack::int32($bin, true);
        $t0  = Unpack::uInt64($bin, true);
        $dev   = Unpack::uint32($bin, true);
        $flags = Unpack::uInt8($bin, true);
    	$period = 0;
        $all_uncert = 0;
        if (($flags & 0x01) != 0) {
            $period = Unpack::int32($bin, true);
        }
        if (($flags & 0x02) != 0) {
            $uncertainty = Unpack::int32($bin, true);
            $all_uncert = 1;
	    }
	    if ($period === 0 or $all_uncert === 0) {
            $n = 0;
            while ($bin) {
                if ($period == 0) {
                   $offset = Unpack::uint32($bin, true);
                } else {
    	           $offset = $n * $period;
                }
    	        $n = $n + 1;
                $v  = Unpack::d64($bin, true);
                if ( $all_uncert == 1 ) {
                    $uncert = $uncertainty;
                } else {
                    $uncert = Unpack::uint32($bin, true);
                }
                $e = ( $uncert & 0xFF000000 ) >> 24;
                $c = ( $uncert & 0x00FF0000 ) >> 16;
                $t = $t0 + $offset;
                $sd = new StaticSmartData( $u, $v, $e, $c, $x, $y, $z, $t, $dev);
                array_push($datapoints, $sd);
            }
            return $datapoints;
        } else { 
            $sd = new MultiSmartData();
            $sd->version = $ver;
            $sd->unit = $u;
            $sd->unit_code = $unit_code;
            if ($unit_code == 0x00000001) {
                $code = 8; // for digital switches.
            } else {
                $code = ($unit_code & 0x60000000) >> 28;
            }
            switch( $code ) {
                case 0 : $sd->encoding = 'I32'; break;
                case 2 : $sd->encoding = 'I64'; break;
                case 4 : $sd->encoding = 'F32'; break;
                case 6 : $sd->encoding = 'D64'; break;
            }
            $sd->x = $x;
            $sd->y = $y;
            $sd->z = $z;
            $sd->time = $t0;
            $sd->dev = $dev;  // TODO: remove this
            $sd->gw = -1;
            $sd->error = 0;
            $sd->uncertainty = $uncertainty;
            $sd->type = 'TTH';
            $sd->period = $period;
            $cont = 0;
            $nbin = '';
            while ($bin) {
                $v  = Unpack::d64($bin, true);
                switch ( $code ) {
                    case 0 : $val = (int) $v; $straux = Pack::int32( $val ); break;
                    case 2 : $val = (int) $v; $straux = Pack::int64( $val ); break;
                    case 4 : $val = (float) $v; $straux = Pack::f32( $val ); break; 
                    case 6 : $val = (float) $v; $straux = Pack::d64( $val ); break;
                    case 8 : $aux = base64_decode( $v );
                             if ($aux != false) {
                                 $val = ord($aux[0]);
                                 $straux = Pack::uint8( $val );
                             } else {
                                 $val = (float) $v;
                                 $straux = Pack::d64( $val ); // being pessimistic!
                             }
                             break; // for digital switches.                    
                }
                $nbin .= $straux;
                $cont += 1;
            }
            $sd->blob = $nbin;
            return $sd;            
        }
        return $datapoints;
    }

    public static function unpackMultiDevice(& $bin)
    {
        $datapoints = array();
        $ver = Unpack::uInt8($bin, true);
        $u   = Unit::unpack($bin);
        $x   = Unpack::int32($bin, true);
        $y   = Unpack::int32($bin, true);
        $z   = Unpack::int32($bin, true);
        $t0  = Unpack::uInt64($bin, true);
        while ($bin) {
            $offset = Unpack::uint32($bin, true);
            $v      = Unpack::d64($bin, true);
            $dev    = Unpack::uint32($bin, true);
            $uncert = Unpack::uint32($bin, true);
            $e = ( $uncert & 0xFF000000 ) >> 24;
            $c = ( $uncert & 0x00FF0000 ) >> 16;
            $t = $t0 + $offset;
            $sd = new StaticSmartData( $u, $v, $e, $c, $x, $y, $z, $t, $dev);
            array_push($datapoints, $sd);
        }
        return $datapoints;
    }

    public static function unpackMultiUnit(& $bin)
    {
        $datapoints = array();
        $ver = Unpack::uInt8($bin, true);
        $x   = Unpack::int32($bin, true);
        $y   = Unpack::int32($bin, true);
        $z   = Unpack::int32($bin, true);
        $t0  = Unpack::uInt64($bin, true);
        while ($bin) {
            $u      = Unpack::uint32($bin, true);
            $set    = array();
            if (array_key_exists($u, $datapoints)) {
               $set = $datapoints[$u];
            }
            $offset = Unpack::uint32($bin, true);
            $v      = Unpack::d64($bin, true);
            $dev    = Unpack::uint32($bin, true);
            $uncert = Unpack::uint32($bin, true);
            $e = ( $uncert & 0xFF000000 ) >> 24;
            $c = ( $uncert & 0x00FF0000 ) >> 16;
            $t = $t0 + $offset;
            $sd = new StaticSmartData( $u, $v, $e, $c, $x, $y, $z, $t, $dev); 
            array_push($set, $sd);
            $datapoints [ $u ] = $set;
        }
        return $datapoints;
    }

    public static function parseJson(\stdClass $json) 
    {
        
    }

    public static function multi_values(\stdClass $json)
    {
     	// echo "Testando... ";
        $header = $json->MultiValueSmartData;
        $version = $header->version;
        if (!(is_numeric($version) && is_int($version+0))){
            $version = explode(".", $version);
            $version = ($version[0]<<4) + $version[1];
        }
    	$u = Unit::interpret($header->unit);
        $x = $header->x;
        $y = $header->y;
        $z = $header->z;
        $t0 = $header->t0;
        $dev = $header->dev  ?? 0;            // TODO: remove this
        $type = $header->type ?? 'OLD';
        $data = $header->datapoints;
        $gw = $header->gateway ?? -1;
        $signature = $header->signature ?? 0;
        $wf = $header->workflow ??  0;
        $period = 0;
        if (isset($header->period)) {
    	    $period = $header->period;
        }
        $uncertainty = -1;
        if (isset($header->uncertainty)) {
            $uncertainty = $header->uncertainty;
        }
        $n = 0;
        $smartdatas = array();
        if ($type != 'TTH') {
            foreach($data as $point) {
                $val = $point->value;
                if ($period == 0) {
                    if (!isset($point->offset)) {
                         throw new BadRequestException("Period not defined in header neither in points (offset)");
                    }
                    $offset = $point->offset;
                } else {
                    $offset = $n * $period;
                    $n = $n + 1;
                }
                $t = $t0 + $offset;
                if (isset($point->uncertainty)) {
                    $uncert = $point->uncertainty;
                } else {
                    $uncert = 0;
                }
                $e = ( $uncertainty & 0xFF000000 ) >> 24;
                $c = ( $uncertainty & 0x00FF0000 ) >> 16;
                if ($version == SmartData::STATIC_VERSION) {
                    $sd = new StaticSmartData($u, $val, $e, $c, $x, $y, $z, $t, $dev);
                } else {
                     $sd = new MobileSmartData($u, $val, $e, $c, $x, $y, $z, $t, $dev, $gw, $signature);
                }
                array_push($smartdatas, $sd);
            }
            return $smartdatas;
        } else {
            $tam = count($data);
            $sd = new MultiSmartData();
            $sd->version = $version;
            $sd->unit = $u;
            $sd->x = $x;
            $sd->y = $y;
            $sd->z = $z;
            $sd->time = $t0;
            $sd->dev = $dev;  // TODO: remove this
            $sd->gw = $gw;
            $sd->error = $header->error ?? 0;
            $sd->uncertainty = $header->uncertainty ?? 0;
            $sd->signature = $header->signature ?? null;
            $sd->type = 'TTH';
            $sd->period = $period;
            $sd->workflow = $wf;
            $sd->datapoints = $data;
            if ($header->unit == 0x00000001) {
                $code = 8; // for digital switches.
            } else {
                $code = ($header->unit & 0x60000000) >> 28;
            }
            switch( $code ) {
                case 0 : $sd->encoding = 'I32'; break;
                case 2 : $sd->encoding = 'I64'; break;
                case 4 : $sd->encoding = 'F32'; break;
                case 6 : $sd->encoding = 'D64'; break;
                case 8 : $sd->encoding = 'I8';  break; // for digital switches.
            }
            $bin = '';
            foreach($data as $point) {
                switch ( $code ) {
                    case 0 : $val = (int) $point->value; $straux = Pack::int32( $val ); break;
                    case 2 : $val = (int) $point->value; $straux = Pack::int64( $val ); break;
                    case 4 : $val = (float) $point->value; $straux = Pack::f32( $val ); break; 
                    case 6 : $val = (float) $point->value; $straux = Pack::d64( $val ); break;
                    case 8 : $aux = base64_decode( $point->value );
                             if ($aux != false) {
                                 $val = ord($aux[0]);
                                 $straux = Pack::uint8( $val );
                             } else {
                                 $val = (float) $point->value;
                                 $straux = Pack::d64( $val ); // being pessimistic!
                             }
                             break; // for digital switches.
                }
                $bin .= $straux;
            }
            $sd->blob = $bin;
            return $sd;
        }
    	return $smartdatas;
    }

    public static function multi_devices(\stdClass $json)
    {
     	$header = $json->MultiDeviceSmartData;
        $version = $header->version;
        if (!(is_numeric($version) && is_int($version+0))) {
            $version = explode(".", $version);
            $version = ($version[0]<<4) + $version[1];
        }
	    $u = Unit::interpret($header->unit);
        $x = $header->x;
        $y = $header->y;
        $z = $header->z;
        $t0 = $header->t0;
        $gw = $header->gateway ?? -1;
        $signature = $header->signature ?? 0;
        $data = $header->datapoints;
        $smartdatas = array();
        foreach($data as $point) {
            $val = $point->value;
            $offset = $point->offset;
            $t = $t0 + $offset;
            $uncertainty = $point->uncertainty;
            $e = ( $uncertainty & 0xFF000000 ) >> 24;
            $c = ( $uncertainty & 0x00FF0000 ) >> 16;
            $dev = $point->dev;
            if ($version == SmartData::STATIC_VERSION) {
                $sd = new StaticSmartData($u, $val, $e, $c, $x, $y, $z, $t, $dev);
            } else {
                $sd = new MobileSmartData($u, $val, $e, $c, $x, $y, $z, $t, $dev, $gw, $signature);
            } 
            array_push($smartdatas, $sd);
        }
	    return $smartdatas;
    }

    // returns a map, with each unit refering to a list of all datapoints of this unit.
    public static function multi_units(\stdClass $json)
    {
     	$header = $json->MultiUnitSmartData;
        $version = $header->version;
        if(is_numeric($version) && is_int($version+0)){
            // nothing
        } else {
            $version = explode(".", $version);
            $version = ($version[0]<<4) + $version[1];
        }
        $x = $header->x;
        $y = $header->y;
        $z = $header->z;
        $t0 = $header->t0;
        $data = $header->datapoints;
        $smartdatas = array();
        foreach($data as $point) {
            $u = $point->unit;
            $set = array();
            if (array_key_exists($u, $smartdatas)) {
               $set = $smartdatas[$u];
            }
            $val = $point->value;
            $offset = $point->offset;
            $t = $t0 + $offset;
            $uncertainty = $point->uncertainty;
            $e = ( $uncertainty & 0xFF000000 ) >> 24;
            $c = ( $uncertainty & 0x00FF0000 ) >> 16;
            $dev = $point->dev;
            $sd = new StaticSmartData($u, $val, $e, $c, $x, $y, $z, $t, $dev);
            array_push($set, $sd);
            $smartdatas[ $u ] = $set; 
        }
        return $smartdatas;
    }
    
    public function toArray()
    { 
        $header['version'] = $this->version;
        $header['unit']    = $this->unit->cod;
        $header['x']       = $this->x;
        $header['y']       = $this->y;
        $header['z']       = $this->z;
        $header['t0']      = $this->time;
        $header['dev']     = $this->dev;
        $header['type']    = $this->type ?? 'OLD';
        if( $this->period ?? false) {
            $header['period']  = $this->period;
        }
        if ($this->uncertainty ?? false) {
            $header['uncertainty'] = $this->uncertainty;
        }
        $header['workflow'] = $this->workflow ?? 0;
        $header['gateway'] = $this->gw ?? -1;
        if ($this->signature ?? false) {
            $header['signature'] = $this->signature;
        }
        $header['datapoints'] = $this->datapoints;
        $mv = array("MultiValueSmartData" => $header);
        return array("series" => $mv);
    }

    public function toJson()
    {
        $json = json_encode($this->toArray());
        //$json = preg_replace('/:"([0-9]*)"/',':$1',$json);
        //$json = preg_replace('/\"t0\":\"/', "\"t0\":", $json);
        //$json = preg_replace('/\"t1\":\"/', "\"t1\":", $json);
        return $json;
    }
    
}