<?php
   namespace App;

   use App\Jobs\ReadPropertyAndUpdate;
   use App\Jobs\ReadPropertyMultipleAndUpdate;
   use Carbon\Carbon;
   use Illuminate\Support\Collection;
   use App\Device;
   use App\Object;
   use Symfony\Component\Process\Process;

   class Bacnet {

      public static function readObjectProperty( Object $object, $property, $index = null ) {
         $instance = $object->device_instance;
         $identifier = $object->identifier;
         $type = $object->numeric_type;
         if ( $index != null ) {
            $statement = "bacrp $instance $type $identifier $property $index";
         } else {
            $statement = "bacrp $instance $type $identifier $property";
         }
         $command = config( 'optimize.bacnet_stack' ) . $statement;
         $output = run_command( $command );
         $value = preg_replace( '/[\x00-\x1F\x7F]/', '', $output );
         if ( strpos( $value, 'BACnet Error:' ) > -1 ) {
            $output = strstr( $value, 'BACnet Error:' );
            $output = stristr( $output, 'Error Output:', true );

            return $output;
         }
         $value = str_replace( [ '"' ], [ '' ], $value );
         if ( strpos( $value, '{' ) > -1 ) {
            $value = str_replace( [ '{', '}' ], '', $value );
            $output = explode( ',', $value );

            return $output;
         }

         return $value;
      }

      public static function writeObjectProperty( Object $object, $value, $property, $tag, $priority = 8, $index = -1 ) {
         $instance = $object->device_instance;
         $identifier = $object->identifier;
         $type = $object->numeric_type;
         $statement = "bacwp $instance $type $identifier $property $priority $index $tag $value";
         $command = config( 'optimize.bacnet_stack' ) . $statement;
         $output = run_command( $command );
         if ( str_contains(strtolower($output),"writeproperty acknowledged" )) {
            return true;
         } else {
            return false;
         }
      }

      public static function readDeviceProperty( Device $device, $property, $index = '') {
         $instance = $device->instance;
         $statement = "bacrp $instance 8 $instance $property $index";
         $command = config( 'optimize.bacnet_stack' ) . $statement;
         $output = run_command( $command );
         $value = preg_replace( '/[\x00-\x1F\x7F]/', '', $output );
         if ( strpos( $value, 'BACnet Error:' ) > -1 ) {
            $output = strstr( $value, 'BACnet Error:' );
            $output = stristr( $output, 'Error Output:', true );

            return $output;
         }
         $value = str_replace( [ '"' ], [ '' ], $value );
         if ( strpos( $value, '{' ) > -1 ) {
            $value = str_replace( [ '{', '}' ], '', $value );
            $output = explode( ',', $value );

            return $output;
         }

         return $value;
      }

      public static function updateObjectsWithReadProperty( Collection $collection ) {
         $collection = $collection->unique();
         $offlineGroup = collect();
         $onlineGroup = collect();
         // Make sure the objects are on "online" devices
         foreach ( $collection as $object ) {
            if ( $object->device->status == "online" ) {
               $onlineGroup->push( $object );
               dump('Online object: ' . $object->shorthand . ' - ' . $object->device->status);
            } else {
               $offlineGroup->push( $object );
               dump('Offline object: ' . $object->shorthand . ' - ' . $object->device->status);
            }
         }
         // dump('Online: ' . $onlineGroup->count(), 'Offline: ' . $offlineGroup->count());
         foreach ( $onlineGroup as $object ) {
            $job = ( new ReadPropertyAndUpdate( $object, false ) )->onQueue( 'medium' );
            dispatch( $job );
         }
      }

      public static function updateObjectValues( Collection $collection, $subscribeCheck = false, $queue = 'medium' ) {
         // Declare our collections! We need some for Read Property and some for Read Property Multiple.
         $rpGroup = collect();
         $rpmGroup = collect();
         // You might ask yourself "why use GroupBy once and then again later?" Because
         // it's faster than evaluating each object by itself. Time tests were conducted.
         $groups = $collection->groupBy( 'device_instance' );
         foreach ( $groups as $key => $group ) {
            // assemble the different groups of RP and RPM
            $device = Device::where( 'instance', $key )->first();
            if($device instanceof Device) {
               if ( $device->supportsService( 'Read-Property-Multiple' ) ) {
                  $rpmGroup = $rpmGroup->merge( $group );
               } elseif ( !$device->supportsService( 'Read-Property-Multiple' ) ) {
                  $rpGroup = $rpGroup->merge( $group );
               }
            }
         }

         // Now we separate our RPMs into separate groups (again!)
         // because RPM can only be run once per device instance.
         // we also have to CHUNK those requests by the capability
         // of the device (APDU)
         $rpmGroup = $rpmGroup->groupBy( 'device_instance' );
         foreach ( $rpmGroup as $device_instance => $group ) {
            $job = ( new ReadPropertyMultipleAndUpdate( $group, $device_instance, $subscribeCheck ) )->onQueue( $queue);
            dispatch( $job );
         }

         // Alright! Now we can get to our Read Properties. Yeesh!
         foreach ( $rpGroup as $object ) {
            $job = ( new ReadPropertyAndUpdate( $object, $subscribeCheck ) )->onQueue( $queue);
            dispatch( $job );
         }
      }

      public static function runServer( $delay = 5, $instance = '' ) {
         exec( "pkill bacserv" );

         $process = new Process( config( 'optimize.bacnet_stack' ) . "bacserv $instance", null, null, null, null );
         $process->start();

         while ( $process->isRunning() ) {
            $block = $process->getIncrementalErrorOutput();
            if ( strpos( $block, "\n" ) > -1 ) {
               $arrays  = explode( "\n", $block );
               $uniques = array_unique( $arrays );
               foreach ( $uniques as $json ) {
                  $array = json_decode( $json, true );
                  if ( gettype( $array ) == 'array' ) {
                     if ( $array[ 'event' ] == 'ucov' ) {
                        $object
                           = Object::where( 'device_instance', $array[ 'instance' ] )->where( 'type', $array[ 'type' ] )->where( 'identifier', $array[ 'identifier' ] )->get();
                        if ( !$object->isEmpty() ) {
                           $object = $object->first();
                           if ( strpos( $object->type, 'multi' ) > -1 ) {
                              $stateText = $object->readProperty( 110 );
                              $object->update( [ 'present_value' => $stateText[ $array[ 'enumerated' ] - 1 ] ] );
                           } else if ( strpos( $object->type, 'binary' ) > -1 ) {
                              $binaryText = [
                                 0 => 'inactive',
                                 1 => 'active'
                              ];
                              $object->update( [ 'present_value' => $binaryText[ $array[ 'enumerated' ] ] ] );
                           } else {
                              $object->update( [ 'present_value' => $array[ 'real' ] ] );
                           }
                        }
                     }
                  }
               }
            }
            sleep( $delay );
         }
      }

      public static function unconfirmedCovSub( $object ) {
         /*
             /home/vagrant/Sites/bacnet-stack-0.8.3/bin/bacscov 200 0 2 1 unconfirmed
             Sent SubscribeCOV request.  Waiting up to 9 seconds....
             SubscribeCOV Acknowledged!
             RP: Sending Ack!
             UCOV: Received Notification!
             UCOV: PID=1 instance=200 analog-input 2 time remaining=0 seconds
             UCOV: present-value
             UCOV: status-flags
         */
         // Determine if it CAN subscribe first
         if($object->device->supportsService('Subscribe-COV')) {
            $instance = $object->device_instance;
            $identifier = $object->identifier;
            $type = $object->numeric_type;
            $statement = "bacscov $instance $type $identifier 1 unconfirmed";
            $command = config( 'optimize.bacnet_stack' ) . $statement;
            $output = run_command( $command );
            if ( strpos( $output, 'SubscribeCOV Acknowledged!' ) > -1 ) {
               $object->update( [ 'subscribed_at' => Carbon::now() ] );

               return true;
            } else {
               return false;
            }
         } else {
            return 'Unable to COV to device ' . $object->device_instance . ' due to hardware limitation.';
         }
      }
   }