function setOutValveTo(controller_id, nextState) {
  var state = Meteor.zoblak.server.controller_state(controller_id);
  ControllerState.update(state._id, {
    '$set': {
      'state.out_valve': nextState,
      'time': new Date(),
      'set_by': 'server'
    }
  });

  if (nextState === "open") {
    ControllerState.update(state._id, {
      '$set': {
        'significantEvents.lastOutValveOpen': new Date(),
      }
    });
  }

}

function setInValveTo(controller_id, nextState) {
  var state = Meteor.zoblak.server.controller_state(controller_id);
  ControllerState.update(state._id, {
    '$set': {
      'state.in_valve': nextState,
      'time': new Date(),
      'set_by': 'server'
    }
  });

  if (nextState === "open") {
    ControllerState.update(state._id, {
      '$set': {
        'significantEvents.lastInValveOpen': new Date(),
      }
    });
  }

}

function requestNewPicture(controller_id) {
  var state = Meteor.zoblak.server.controller_state(controller_id);
  ControllerState.update(state._id, {
    '$set': {
      'state.picture_requested': 'true',
      'time': new Date(),
      'set_by': 'server'
    }
  });
};

function openInValve(controller_id) {
  var state = Meteor.zoblak.server.controller_state(controller_id);
  var config = state.config;
  if (config.manualInflow) {
    setInValveTo(controller_id, 'opening');
  }
  reactToSensorData(last_sensor_reading(controller_id));
}

function closeInValve(controller_id) {
  var state = Meteor.zoblak.server.controller_state(controller_id);
  var config = state.config;
  if (config.manualInflow) {
    setInValveTo(controller_id, 'closing');
  }
  reactToSensorData(last_sensor_reading(controller_id));

}



function openOutValve(controller_id) {
  setOutValveTo(controller_id, 'opening');
  setInValveTo(controller_id, 'closing');
  var state = Meteor.zoblak.server.controller_state(controller_id);
  var config = state.config;
  var jobName = "Close out valve " + state.controller_id + " after draining";
  console.log("Opening valve ", controller_id, jobName);
  SyncedCron.remove(jobName);
  SyncedCron.add({
    name: jobName,
    schedule: function(parser) {
      var time = moment().add(config.draining_period_amount, config.draining_period_unit).toDate();
      return parser.recur().on(time).fullDate();
    },
    job: function() {
      closeOutValve(controller_id);
    }
  });
  console.log(Meteor.sharedFunctions);

  reactToSensorData(last_sensor_reading(controller_id));
}

function closeOutValve(controller_id) {
  var state = Meteor.zoblak.server.controller_state(controller_id);

  var jobName = "Close out valve " + state.controller_id + " after draining";
  console.log("Closing valve ", controller_id, jobName);
  SyncedCron.remove(jobName);
  setOutValveTo(controller_id, 'closing');
  console.log("Finished clearing cron ", controller_id);

  console.log(Meteor.sharedFunctions);

  reactToSensorData(last_sensor_reading(controller_id));
}

function clearLog() {
  console.log("Removing sensor data");
  SensorData.remove({});
}

function saveControllerConfig(controller_id, time, days, manualInflow) {
  var state = Meteor.zoblak.server.controller_state(controller_id);
  ControllerState.update(state._id, {
    '$set': {
      'config.automaticTimeOfDay': time,
      'config.automaticDaysOfWeek': days,
      'config.manualInflow': manualInflow
    }
  });
  var jobName = "automatic_" + controller_id;
  var times = time.split(":");
  var hours = parseInt(times[0]);
  var minutes = parseInt(times[1]);

  SyncedCron.remove(jobName);
  SyncedCron.add({
    name: jobName,
    schedule: function(parser) {

      var period = parser.recur();
      for (var i = 0; i < days.length; i++) {
        period = period.and().on(parseInt(days[i])).dayOfWeek().on(hours).hour().on(minutes).minute();
      }
      return period;
    },
    job: function() {
      openOutValve(controller_id);
    }
  });
}

function saveAlarmSettings(controller_id, minTemperature, maxTemperature, timeoutBox, timeoutPhone, smsNumbers, sensorsEnabled) {
  var state = Meteor.zoblak.server.controller_state(controller_id);
  ControllerState.update(state._id, {
    '$set': {
      'config.minTemperature': parseFloat(minTemperature),
      'config.maxTemperature': parseFloat(maxTemperature),
      'config.timeoutBox': timeoutBox ? parseInt(timeoutBox) : null,
      'config.timeoutPhone': timeoutPhone ? parseInt(timeoutPhone) : null,
      'config.smsNumbers': smsNumbers,
      'config.sms1': smsNumbers[0],
      'config.sms2': smsNumbers[1],
      'config.sms3': smsNumbers[2],
      'config.sms4': smsNumbers[3],
      'config.sensorsEnabled': sensorsEnabled
    }
  });

  var jobName = "automatic_alarm_" + controller_id;

  SyncedCron.remove(jobName);
  SyncedCron.add({
    name: jobName,
    schedule: function(parser) {
      return parser.text('every 10 seconds');
    },
    job: function() {
      reactToAlarmData(controller_id);
    }
  });

  reactToAlarmData(controller_id);
}


// there are three states of alarm:
// 1. normal ( state.alarmTriggered: false,  state.alarmStopped: null )
// 2. triggered ( state.alarmTriggered: true, state.alarmStopped: null )
// 3. silenced ( state.alarmTriggered: false, state.alarmStopped: (sometime) )

reactToAlarmData = function(controller_id) {
  var reading = last_sensor_reading(controller_id);
  var state = Meteor.zoblak.server.controller_state(controller_id);
  var config = state.config;


  var minTemperature = function(temperatures) {
    // if it gets a lot colder than absolute zero
    // we will have more problems than the bug in this code
    if (temperatures.length <= 0) return -1000;
    var minimal = parseFloat(temperatures[0]);
    for (var i in temperatures) {
      if (parseFloat(temperatures[i]) < minimal) {
        minimal = parseFloat(temperatures[i]);
      }
    }
    return minimal;
  };

  var maxTemperature = function(temperatures) {
    // obviously - hell is not supported in this version
    if (temperatures.length <= 0) return 1000;
    var maximal = parseFloat(temperatures[0]);
    for (var i in temperatures) {
      if (parseFloat(temperatures[i]) > maximal) {
        maximal = parseFloat(temperatures[i]);
      }
    }
    return maximal;
  };

  var temperatures = (reading.temperatures || []).filter(function(temperature, index) {
    var is_on = (index in config.sensorsEnabled)?config.sensorsEnabled[index]: true;
    return Meteor.zoblak.shared.valid_temperature(temperature) && is_on;
  });


  var tooCold = config.minTemperature && (minTemperature(temperatures) < config.minTemperature);

  var tooHot = config.maxTemperature && (maxTemperature(temperatures) > config.maxTemperature);

  var minutesSinceLastBoxContact = reading.lastBoxContact ? moment(new Date()).diff(moment(reading.lastBoxContact), 'minutes') : -1;
  var boxSilent = config.timeoutBox && minutesSinceLastBoxContact > config.timeoutBox;

  var minutesSinceLastPhoneContact = state.lastPhoneContact ? moment(new Date()).diff(moment(state.lastPhoneContact), 'minutes') : -1;
  var phoneSilent = false; //config.timeoutPhone && minutesSinceLastPhoneContact > config.timeoutPhone;

  console.log("too ", tooCold, tooHot, boxSilent, phoneSilent);
  console.log("lpc", state.lastPhoneContact);
  console.log("mslpc", minutesSinceLastPhoneContact);
  console.log("phoneSilent", phoneSilent);


  if (tooCold || tooHot || boxSilent || phoneSilent) {
    var alarmSilenced = !!state.state.alarmStopped;
    if (!alarmSilenced) soundTheAlarm(controller_id, tooCold, tooHot, boxSilent, phoneSilent);
  } else {
    stopTheAlarm(controller_id, true);
  }
}

function soundTheAlarm(controller_id, tooCold, tooHot, boxSilent, phoneSilent) {
  var state = Meteor.zoblak.server.controller_state(controller_id);
  var reason = {
    tooHot: tooHot,
    tooCold: tooCold,
    boxSilent: boxSilent,
    phoneSilent: phoneSilent
  };
  console.log("Alarmiram", reason);

  var firstTime = {};
  if (!state.state.alarmTriggered) {
    firstTime = {
      'state.alarmStarted': new Date()
    }
  };

  var smsSent = !!state.state.alarmSmsSent;
  var needsToSendSms = !smsSent // && phoneSilent;

  var sendSmsPart = needsToSendSms ? {
    'state.alarmSmsSent': true
  } : {};

  ControllerState.update(state._id, {
    '$set': Object.assign({
      'state.alarmTriggered': true,
      'state.alarmStopped': null,
      'state.alarmReasons': reason
    }, firstTime, sendSmsPart)
  });

  if (needsToSendSms) {
    sendAlarmingSms(controller_id, reason, state.config.smsNumbers);
    callTheUser(controller_id, reason, state.config.smsNumbers);
  }
}

function sendAlarmingSms(controller_id, reason, numbers) {
  for (var i in numbers) {
    var number = numbers[i];
    twilio = Twilio('AC10d7ed0bf54c1be4b1cd7133130e63f4', 'e133d3f02a69b79e93ad9ca1d73517d1');
    twilio.sendSms({
      to: number, // Any number Twilio can deliver to
      from: '+19282124174', // A number you bought from Twilio and can use for outbound communication
      body: 'Zoblak alarm! Pokrenite aplikaciju! HITNO! http://agrar.zoblak.com/alarm?controller_id=' + controller_id // body of the SMS message
    }, function(err, responseData) { //this function is executed when a response is received from Twilio
      if (!err) { // "err" is an error received during the request, if any
        // "responseData" is a JavaScript object containing data received from Twilio.
        // A sample response from sending an SMS message is here (click "JSON" to see how the data appears in JavaScript):
        // http://www.twilio.com/docs/api/rest/sending-sms#example-1
        console.log(responseData.from); // outputs "+14506667788"
        console.log(responseData.body); // outputs "word to your mother."
      }
    });
  }
}

function callTheUser(controller_id, reason, numbers) {
  for (var i in numbers) {
    var number = numbers[i];
    twilio = Twilio('AC10d7ed0bf54c1be4b1cd7133130e63f4', 'e133d3f02a69b79e93ad9ca1d73517d1');
    twilio.makeCall({
      to: number, // Any number Twilio can call
      from: '+441143031932', // A number you bought from Twilio and can use for outbound communication
      url: 'https://handler.twilio.com/twiml/EH9491c24474db07ec52b598baa5724f1e' // A URL that produces an XML document (TwiML) which contains instructions for the call
    }, function(err, responseData) {
      //executed when the call has been initiated.
      console.log(err); // outputs "+14506667788"
    });
  }
}

function stopTheAlarm(controller_id, everythingIsBackToNormal = false) {
  // time of alarm stopped is reset so that scheduled job can raise the alarm
  // again
  var timeOfStopping = (everythingIsBackToNormal) ? null : new Date();
  var state = Meteor.zoblak.server.controller_state(controller_id);
  ControllerState.update(state._id, {
    '$set': {
      'state.alarmTriggered': false,
      'state.alarmStopped': timeOfStopping,
      'state.alarmSmsSent': false
    }
  });
}

function last_sensor_reading(controller_id) {
  var result = null;

  if (controller_id) {
    result = SensorData.find({
      controllerId: controller_id
    }, {
      sort: {
        created_at: -1
      },
      limit: 1
    });
  }
  if (result && result.count() > 0) {
    return result.fetch()[0];
  } else {
    return {}
  }
}


Meteor.methods({
  openOutValve: openOutValve,
  closeOutValve: closeOutValve,
  openInValve: openInValve,
  closeInValve: closeInValve,
  clearLog: clearLog,
  saveControllerConfig: saveControllerConfig,
  requestNewPicture: requestNewPicture,
  saveAlarmSettings: saveAlarmSettings,
  stopTheAlarm: stopTheAlarm
});
