// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

'use strict';

var Client = require('azure-iot-device').Client;
//var Protocol = require('azure-iot-device-amqp').Amqp;
var Protocol = require('azure-iot-device-mqtt').Mqtt;

var connectionString = process.env.DEVICE_CONNECTION_STRING;
if (!connectionString) {
  console.log('Please set the DEVICE_CONNECTION_STRING environment variable.');
  process.exit(-1);
}

// create the IoTHub client
var client = Client.fromConnectionString(connectionString, Protocol);
console.log('got client');

// connect to the hub
client.open(function(err) {
  if (err) {
    console.error('could not open IotHub client');
    process.exit(-1);
  } else {
    console.log('client opened');

    // Create device Twin
    client.getTwin(function(err, twin) {
      if (err) {
        console.error('could not get twin');
        process.exit(-1);
      } else {
        console.log('twin created');

        // First, set up code to handle desired property changes.

        // Important note: when the twin is done being created, the desired
        // properties have already been retrieved from the service.  When we
        // add these handlers, there is a chance that the handlers will be called
        // almost immediately if the relevant properties have already been
        // retrieved from the service.  In this way "property change" could mean
        // "the property is changing from one value to another value", or it could
        // mean "the property is changing from being unset to being set."
        //
        // There are 4 examples here.  The app developer has the option to chose
        // which sample style to use (or mix and match).  All of the events
        // shown here will fire and it's up to the app developer to decide what
        // to listen for.
        //
        // If there is a collision between different handlers, it is up to the
        // app developer to resolve the collisions.  In other words, there is
        // nothing that stops the developer from writing two different handlers
        // that process the same properties at different levels.
        //

        // Usage example #1: receiving all patches with a single event handler.
        //
        // This code will output any properties that are received from the
        // service.
        //
        twin.on('properties.desired', function(delta) {
            console.log('new desired properties received:');
            console.log(JSON.stringify(delta));
        });

        // Usage example #2: receiving an event if anything under
        // properties.desired.climate changes
        //
        // This code will output desired min and max temperature every time
        // the service updates either one.
        //
        // For example (service API code):
        //  twin.properties.desired.update({
        //    climate : {
        //      minTemperature: 68,
        //      maxTemperature: 76
        //    }
        //  });
        //
        twin.on('properties.desired.climate', function(delta) {
            //
            // Notice that twin.properties.desired has already been updated before
            // this function was called.
            //
            // If the delta contains a minTemp or a maxTemp, then update the
            // hardware with the values stored in the twin.  The twin has all the
            // same values as the delta, but the delta only has values that
            // have changed.  When we need the "current values", we don't use the
            // values stored in the delta because it's possible that the delta has
            // one or the other, but not both.
            //
            if (delta.minTemperature || delta.maxTemperature) {
              console.log('updating desired temp:');
              console.log('min temp = ' + twin.properties.desired.climate.minTemperature);
              console.log('max temp = ' + twin.properties.desired.climate.maxTemperature);
            }
        });

        // Usage example #3: receiving an event for a single (scalar) property
        // value.  This event is only fired if the fanOn boolean value is part
        // of the patch.
        //
        // This code will output the new desired fan state whenever the service
        // updates it.
        //
        // For example (service API code):
        // twin.properties.desired.update({
        //   climate : {
        //      hvac : {
        //        systemControl : {
        //          fanOn : true
        //        }
        //      }
        //    }
        //  });
        twin.on('properties.desired.climate.hvac.sytemControl', function(fanOn) {
            console.log('setting fan state to ' + fanOn);
        });

        // Usage example #4: handle add or delete operations.  The app developer
        // is responsible for inferring add/update/delete operations based on
        // the contents of the patch.
        //
        // This code will output the results of adding, updating, or deleting
        // modules.
        //
        // Add example (service API code):
        //  twin.properties.desired.update({
        //    modules : {
        //      wifi : { channel = 6, ssid = 'my_network' },
        //      climate : { id = 17, units = 'farenheit' }
        //    }
        //  });
        //
        // Update example (service API code):
        //  twin.properties.desired.update({
        //    modules : {
        //      wifi : { channel = 7, encryption = 'wpa', passphrase = 'foo' }
        //    }
        //  });
        //
        // Delete example (service API code):
        //  twin.properties.desired.update({
        //    modules : {
        //      climate = null
        //    }
        //  });
        //

        // To do this, first we have to keep track of "all modules that we know
        // about".
        var moduleList = {};

        // Then we use this internal list and compare it to the delta to know
        // if anything was added, removed, or updated.
        twin.on('properties.desired.modules', function(delta) {
          Object.keys(delta).forEach(function(key) {

            if (delta[key] === null && moduleList[key]) {
              // If our patch contains a null value, but we have a record of
              // this module, then this is a delete operation.
              console.log('deleting module ' + key);
              delete moduleList[key];

            } else if (delta[key]) {
              if (moduleList[key]) {
                // Our patch contains a module, and we've seen this before.
                // Must be an update operation.
                console.log('updating module ' + key + ':');
                console.log(JSON.stringify(delta[key]));
                // Store the complete object instead of just the delta
                moduleList[key] = twin.properties.desired.modules[key];

              } else {
                // Our patch contains a module, but we've never seen this
                // before.  Must be an add operation.
                console.log('adding module ' + key + ':');
                console.log(JSON.stringify(delta[key]));
                // Store the complete object instead of just the delta
                moduleList[key] = twin.properties.desired.modules[key];
              }
            }
          });
        });

        // create a patch to send to the hub
        var patch = {
          firmwareVersion:'1.2.2',
          weather:{
            temperature: 72,
            humidity: 17
          }
        };

         // send the patch
        twin.properties.reported.update(patch, function(err) {
          if (err) {
            console.error('unable to update twin: ' + err.toString());
            process.exit(-1);
          } else {
            console.log('twin state reported');
            process.exit(0);
          }
        });
      }
    });
  }
});
