text: "use strict";
  
  /* Magic Mirror
   * Module: MMM-OpenmapWeather
   *
   * By Sathyaraj V
   * MIT Licensed.
   */
  
  Module.register("MMM-OpenmapWeather", {
    // Default module config.
    defaults: {
      location: false,
      locationID: false,
      appid: "",
      units: config.units,
      updateInterval: 10 * 60 * 1000, // every 10 minutes
      animationSpeed: 1000,
      timeFormat: config.timeFormat,
      showPeriod: true,
      showPeriodUpper: false,
      showWindDirection: true,
      showWindDirectionAsArrow: false,
      useBeaufort: true,
      useKMPHwind: false,
      lang: config.language,
      decimalSymbol: ".",
      showHumidity: false,
      degreeLabel: false,
      showIndoorTemperature: false,
      showIndoorHumidity: false,
      showFeelsLike: true,
  
      initialLoadDelay: 0, // 0 seconds delay
      retryDelay: 2500,
  
      apiVersion: "2.5",
      apiBase: "https://api.openweathermap.org/data/",
      weatherEndpoint: "weather",
  
      appendLocationNameToHeader: true,
      calendarClass: "calendar",
  
      onlyTemp: false,
      roundTemp: false,
  
      colorIcon: false,
      iconTable: {
        "01d": "wi-day-sunny",
        "02d": "wi-day-cloudy",
        "03d": "wi-cloudy",
        "04d": "wi-cloudy-windy",
        "09d": "wi-showers",
        "10d": "wi-rain",
        "11d": "wi-thunderstorm",
        "13d": "wi-snow",
        "50d": "wi-fog",
        "01n": "wi-night-clear",
        "02n": "wi-night-cloudy",
        "03n": "wi-night-cloudy",
        "04n": "wi-night-cloudy",
        "09n": "wi-night-showers",
        "10n": "wi-night-rain",
        "11n": "wi-night-thunderstorm",
        "13n": "wi-night-snow",
        "50n": "wi-night-alt-cloudy-windy"
      },
      iconImageTable: {
        "01d": "sunny.png",
        "02d": "partlysunny.png",
        "03d": "partlycloudy.png",
        "04d": "cloudy.png",
        "09d": "sleet.png",
        "10d": "rain.png",
        "11d": "tstorms.png",
        "13d": "snow.png",
        "50d": "fog.png",
        "01n": "nt_clear.png",
        "02n": "nt_cloudy.png",
        "03n": "nt_mostlycloudy.png",
        "04n": "cloudy.png",
        "09n": "nt_sleet.png",
        "10n": "nt_rain.png",
        "11n": "nt_tstorms.png",
        "13n": "nt_snow.png"
        //,"50n": "wi-night-alt-cloudy-windy"
      }
    },
  
    // create a variable for the first upcoming calendar event. Used if no location is specified.
    firstEvent: false,
  
    // create a variable to hold the location name based on the API result.
    fetchedLocationName: "",
  
    // Define required scripts.
    getScripts: function () {
      return ["moment.js"];
    },
  
    // Define required scripts.
    getStyles: function () {
      return ["weather-icons.css", "MMM-OpenmapWeather.css"];
    },
  
    // Define required translations.
    getTranslations: function () {
      // The translations for the default modules are defined in the core translation files.
      // Therefor we can just return false. Otherwise we should have returned a dictionary.
      // If you're trying to build your own module including translations, check out the documentation.
      return false;
    },
  
    // Define start sequence.
    start: function () {
      Log.info("Starting module: " + this.name);
  
      // Set locale.
      moment.locale(config.language);
  
      this.windSpeed = null;
      this.windDirection = null;
      this.windDeg = null;
      this.sunriseSunsetTime = null;
      this.sunriseSunsetIcon = null;
      this.temperature = null;
      this.indoorTemperature = null;
      this.indoorHumidity = null;
      this.weatherType = null;
      this.feelsLike = null;
      this.loaded = false;
      this.scheduleUpdate(this.config.initialLoadDelay);
    },
  
    // add extra information of current weather
    // windDirection, humidity, sunrise and sunset
    addExtraInfoWeather: function (wrapper) {
      var small = document.createElement("div");
      small.className = "normal medium";
  
      var windIcon = document.createElement("span");
      windIcon.className = "wi wi-strong-wind dimmed";
      small.appendChild(windIcon);
  
      var windSpeed = document.createElement("span");
      windSpeed.innerHTML = " " + this.windSpeed;
      small.appendChild(windSpeed);
  
      if (this.config.showWindDirection) {
        var windDirection = document.createElement("sup");
        if (this.config.showWindDirectionAsArrow) {
          if (this.windDeg !== null) {
            windDirection.innerHTML =
              ' &nbsp;<i class="fa fa-long-arrow-down" style="transform:rotate(' +
              this.windDeg +
              'deg);"></i>&nbsp;';
          }
        } else {
          windDirection.innerHTML = " " + this.translate(this.windDirection);
        }
        small.appendChild(windDirection);
      }
      var spacer = document.createElement("span");
      spacer.innerHTML = "&nbsp;";
      small.appendChild(spacer);
  
      if (this.config.showHumidity) {
        var humidity = document.createElement("span");
        humidity.innerHTML = this.humidity;
  
        var spacer = document.createElement("sup");
        spacer.innerHTML = "&nbsp;";
  
        var humidityIcon = document.createElement("sup");
        humidityIcon.className = "wi wi-humidity humidityIcon";
        humidityIcon.innerHTML = "&nbsp;";
  
        small.appendChild(humidity);
        small.appendChild(spacer);
        small.appendChild(humidityIcon);
      }
  
      var sunriseSunsetIcon = document.createElement("span");
      sunriseSunsetIcon.className = "wi dimmed " + this.sunriseSunsetIcon;
      small.appendChild(sunriseSunsetIcon);
  
      var sunriseSunsetTime = document.createElement("span");
      sunriseSunsetTime.innerHTML = " " + this.sunriseSunsetTime;
      small.appendChild(sunriseSunsetTime);
  
      wrapper.appendChild(small);
    },
  
    // Override dom generator.
    getDom: function () {
      var wrapper = document.createElement("div");
  
      if (this.config.appid === "") {
        wrapper.innerHTML =
          "Please set the correct openweather <i>appid</i> in the config for module: " +
          this.name +
          ".";
        wrapper.className = "dimmed light small";
        return wrapper;
      }
  
      if (!this.loaded) {
        wrapper.innerHTML = this.translate("LOADING");
        wrapper.className = "dimmed light small";
        return wrapper;
      }
  
      if (this.config.onlyTemp === false) {
        this.addExtraInfoWeather(wrapper);
      }
  
      var large = document.createElement("div");
      large.className = "large light";
  
      var weatherIcon = document.createElement("span");
      if (this.config.colorIcon) {
        weatherIcon.innerHTML = this.weatherTypeTxt;
      } else {
        weatherIcon.className = "wi weathericon " + this.weatherType;
      }
      weatherIcon.classList.add("currentWeatherIconWrapper");
      large.appendChild(weatherIcon);
  
      var degreeLabel = "";
      if (this.config.degreeLabel) {
        switch (this.config.units) {
          case "metric":
            degreeLabel = "C";
            break;
          case "imperial":
            degreeLabel = "F";
            break;
          case "default":
            degreeLabel = "K";
            break;
        }
      }
  
      if (this.config.decimalSymbol === "") {
        this.config.decimalSymbol = ".";
      }
  
      var temperature = document.createElement("span");
      temperature.className = "bright";
      temperature.innerHTML =
        " " +
        this.temperature.replace(".", this.config.decimalSymbol) +
        "&deg;" +
        degreeLabel;
      large.appendChild(temperature);
  
      if (this.config.showIndoorTemperature && this.indoorTemperature) {
        var indoorIcon = document.createElement("span");
        indoorIcon.className = "fa fa-home";
        large.appendChild(indoorIcon);
  
        var indoorTemperatureElem = document.createElement("span");
        indoorTemperatureElem.className = "bright";
        indoorTemperatureElem.innerHTML =
          " " +
          this.indoorTemperature.replace(".", this.config.decimalSymbol) +
          "&deg;" +
          degreeLabel;
        large.appendChild(indoorTemperatureElem);
      }
  
      if (this.config.showIndoorHumidity && this.indoorHumidity) {
        var indoorHumidityIcon = document.createElement("span");
        indoorHumidityIcon.className = "fa fa-tint";
        large.appendChild(indoorHumidityIcon);
  
        var indoorHumidityElem = document.createElement("span");
        indoorHumidityElem.className = "bright";
        indoorHumidityElem.innerHTML = " " + this.indoorHumidity + "%";
        large.appendChild(indoorHumidityElem);
      }
  
      wrapper.appendChild(large);
  
      if (this.config.showFeelsLike && this.config.onlyTemp === false) {
        var small = document.createElement("div");
        small.className = "normal medium";
  
        var feelsLike = document.createElement("span");
        feelsLike.className = "dimmed";
        feelsLike.innerHTML =
          this.translate("FEELS") + " " + this.feelsLike + "&deg;" + degreeLabel;
        small.appendChild(feelsLike);
  
        wrapper.appendChild(small);
      }
  
      return wrapper;
    },
  
    // Override getHeader method.
    getHeader: function () {
      if (
        this.config.appendLocationNameToHeader &&
        this.data.header !== undefined
      ) {
        return this.data.header + " " + this.fetchedLocationName;
      }
  
      return this.data.header;
    },
  
    // Override notification handler.
    notificationReceived: function (notification, payload, sender) {
      if (notification === "DOM_OBJECTS_CREATED") {
        if (this.config.appendLocationNameToHeader) {
          this.hide(0, { lockString: this.identifier });
        }
      }
      if (notification === "CALENDAR_EVENTS") {
        var senderClasses = sender.data.classes.toLowerCase().split(" ");
        if (
          senderClasses.indexOf(this.config.calendarClass.toLowerCase()) !== -1
        ) {
          this.firstEvent = false;
  
          for (var e in payload) {
            var event = payload[e];
            if (event.location || event.geo) {
              this.firstEvent = event;
              //Log.log("First upcoming event with location: ", event);
              break;
            }
          }
        }
      }
      if (notification === "INDOOR_TEMPERATURE") {
        this.indoorTemperature = this.roundValue(payload);
        this.updateDom(this.config.animationSpeed);
      }
      if (notification === "INDOOR_HUMIDITY") {
        this.indoorHumidity = this.roundValue(payload);
        this.updateDom(this.config.animationSpeed);
      }
    },
  
    /* updateWeather(compliments)
     * Requests new data from openweather.org.
     * Calls processWeather on succesfull response.
     */
    updateWeather: function () {
      if (this.config.appid === "") {
        Log.error("MMM-OpenmapWeather: APPID not set!");
        return;
      }
  
      var url =
        this.config.apiBase +
        this.config.apiVersion +
        "/" +
        this.config.weatherEndpoint +
        this.getParams();
      var self = this;
      var retry = true;
  
      var weatherRequest = new XMLHttpRequest();
      weatherRequest.open("GET", url, true);
      weatherRequest.onreadystatechange = function () {
        if (this.readyState === 4) {
          if (this.status === 200) {
            self.processWeather(JSON.parse(this.response));
          } else if (this.status === 401) {
            self.updateDom(self.config.animationSpeed);
  
            Log.error(self.name + ": Incorrect APPID.");
            retry = true;
          } else {
            Log.error(self.name + ": Could not load weather.");
          }
  
          if (retry) {
            self.scheduleUpdate(self.loaded ? -1 : self.config.retryDelay);
          }
        }
      };
      weatherRequest.send();
    },
  
    /* getParams(compliments)
     * Generates an url with api parameters based on the config.
     *
     * return String - URL params.
     */
    getParams: function () {
      var params = "?";
      if (this.config.locationID) {
        params += "id=" + this.config.locationID;
      } else if (this.config.location) {
        params += "q=" + this.config.location;
      } else if (this.firstEvent && this.firstEvent.geo) {
        params +=
          "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
      } else if (this.firstEvent && this.firstEvent.location) {
        params += "q=" + this.firstEvent.location;
      } else {
        this.hide(this.config.animationSpeed, { lockString: this.identifier });
        return;
      }
  
      params += "&units=" + this.config.units;
      params += "&lang=" + this.config.lang;
      params += "&APPID=" + this.config.appid;
  
      return params;
    },
  
    /* processWeather(data)
     * Uses the received data to set the various values.
     *
     * argument data object - Weather information received form openweather.org.
     */
    processWeather: function (data) {
      if (!data || !data.main || typeof data.main.temp === "undefined") {
        // Did not receive usable new data.
        // Maybe this needs a better check?
        return;
      }
  
      this.humidity = parseFloat(data.main.humidity);
      this.temperature = this.roundValue(data.main.temp);
      this.feelsLike = 0;
      this.fetchedLocationName = data.name + "," + data.sys.country;
      if (this.config.useBeaufort) {
        this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed));
      } else if (this.config.useKMPHwind) {
        this.windSpeed = parseFloat((data.wind.speed * 60 * 60) / 1000).toFixed(
          0
        );
      } else {
        this.windSpeed = parseFloat(data.wind.speed).toFixed(0);
      }
  
      // ONLY WORKS IF TEMP IN C //
      var windInMph = parseFloat(data.wind.speed * 2.23694);
  
      var tempInF = 0;
      switch (this.config.units) {
        case "metric":
          tempInF = 1.8 * this.temperature + 32;
          break;
        case "imperial":
          tempInF = this.temperature;
          break;
        case "default":
          var tc = this.temperature - 273.15;
          tempInF = 1.8 * tc + 32;
          break;
      }
  
      if (windInMph > 3 && tempInF < 50) {
        // windchill
        var windChillInF = Math.round(
          35.74 +
            0.6215 * tempInF -
            35.75 * Math.pow(windInMph, 0.16) +
            0.4275 * tempInF * Math.pow(windInMph, 0.16)
        );
        var windChillInC = (windChillInF - 32) * (5 / 9);
        // this.feelsLike = windChillInC.toFixed(0);
  
        switch (this.config.units) {
          case "metric":
            this.feelsLike = windChillInC.toFixed(0);
            break;
          case "imperial":
            this.feelsLike = windChillInF.toFixed(0);
            break;
          case "default":
            var tc = windChillInC + 273.15;
            this.feelsLike = tc.toFixed(0);
            break;
        }
      } else if (tempInF > 80 && this.humidity > 40) {
        // heat index
        var Hindex =
          -42.379 +
          2.04901523 * tempInF +
          10.14333127 * this.humidity -
          0.22475541 * tempInF * this.humidity -
          6.83783 * Math.pow(10, -3) * tempInF * tempInF -
          5.481717 * Math.pow(10, -2) * this.humidity * this.humidity +
          1.22874 * Math.pow(10, -3) * tempInF * tempInF * this.humidity +
          8.5282 * Math.pow(10, -4) * tempInF * this.humidity * this.humidity -
          1.99 *
            Math.pow(10, -6) *
            tempInF *
            tempInF *
            this.humidity *
            this.humidity;
  
        switch (this.config.units) {
          case "metric":
            this.feelsLike = parseFloat((Hindex - 32) / 1.8).toFixed(0);
            break;
          case "imperial":
            this.feelsLike = Hindex.toFixed(0);
            break;
          case "default":
            var tc = parseFloat((Hindex - 32) / 1.8) + 273.15;
            this.feelsLike = tc.toFixed(0);
            break;
        }
      } else {
        this.feelsLike = parseFloat(this.temperature).toFixed(0);
      }
  
      this.windDirection = this.deg2Cardinal(data.wind.deg);
      this.windDeg = data.wind.deg;
      this.weatherType = this.config.iconTable[data.weather[0].icon];
      this.weatherTypeTxt =
        "<img src='./modules/MMM-OpenmapWeather/img/" +
        this.config.iconImageTable[data.weather[0].icon] +
        "' style='vertical-align:middle' class='currentWeatherIcon'>";
  
      var now = new Date();
      var sunrise = new Date(data.sys.sunrise * 1000);
      var sunset = new Date(data.sys.sunset * 1000);
  
      // The moment().format('h') method has a bug on the Raspberry Pi.
      // So we need to generate the timestring manually.
      // See issue: https://github.com/MichMich/MagicMirror/issues/181
      var sunriseSunsetDateObject =
        sunrise < now && sunset > now ? sunset : sunrise;
      var timeString = moment(sunriseSunsetDateObject).format("HH:mm");
      if (this.config.timeFormat !== 24) {
        //var hours = sunriseSunsetDateObject.getHours() % 12 || 12;
        if (this.config.showPeriod) {
          if (this.config.showPeriodUpper) {
            //timeString = hours + moment(sunriseSunsetDateObject).format(':mm A');
            timeString = moment(sunriseSunsetDateObject).format("h:mm A");
          } else {
            //timeString = hours + moment(sunriseSunsetDateObject).format(':mm a');
            timeString = moment(sunriseSunsetDateObject).format("h:mm a");
          }
        } else {
          //timeString = hours + moment(sunriseSunsetDateObject).format(':mm');
          timeString = moment(sunriseSunsetDateObject).format("h:mm");
        }
      }
  
      this.sunriseSunsetTime = timeString;
      this.sunriseSunsetIcon =
        sunrise < now && sunset > now ? "wi-sunset" : "wi-sunrise";
  
      this.show(this.config.animationSpeed, { lockString: this.identifier });
      this.loaded = true;
      this.updateDom(this.config.animationSpeed);
      this.sendNotification("CURRENTWEATHER_DATA", { data: data });
    },
  
    /* scheduleUpdate()
     * Schedule next update.
     *
     * argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
     */
    scheduleUpdate: function (delay) {
      var nextLoad = this.config.updateInterval;
      if (typeof delay !== "undefined" && delay >= 0) {
        nextLoad = delay;
      }
  
      var self = this;
      setTimeout(function () {
        self.updateWeather();
      }, nextLoad);
    },
  
    /* ms2Beaufort(ms)
     * Converts m2 to beaufort (windspeed).
     *
     * see:
     *  http://www.spc.noaa.gov/faq/tornado/beaufort.html
     *  https://en.wikipedia.org/wiki/Beaufort_scale#Modern_scale
     *
     * argument ms number - Windspeed in m/s.
     *
     * return number - Windspeed in beaufort.
     */
    ms2Beaufort: function (ms) {
      var kmh = (ms * 60 * 60) / 1000;
      var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
      for (var beaufort in speeds) {
        var speed = speeds[beaufort];
        if (speed > kmh) {
          return beaufort;
        }
      }
      return 12;
    },
  
    deg2Cardinal: function (deg) {
      if (deg > 11.25 && deg <= 33.75) {
        return "NNE";
      } else if (deg > 33.75 && deg <= 56.25) {
        return "NE";
      } else if (deg > 56.25 && deg <= 78.75) {
        return "ENE";
      } else if (deg > 78.75 && deg <= 101.25) {
        return "E";
      } else if (deg > 101.25 && deg <= 123.75) {
        return "ESE";
      } else if (deg > 123.75 && deg <= 146.25) {
        return "SE";
      } else if (deg > 146.25 && deg <= 168.75) {
        return "SSE";
      } else if (deg > 168.75 && deg <= 191.25) {
        return "S";
      } else if (deg > 191.25 && deg <= 213.75) {
        return "SSW";
      } else if (deg > 213.75 && deg <= 236.25) {
        return "SW";
      } else if (deg > 236.25 && deg <= 258.75) {
        return "WSW";
      } else if (deg > 258.75 && deg <= 281.25) {
        return "W";
      } else if (deg > 281.25 && deg <= 303.75) {
        return "WNW";
      } else if (deg > 303.75 && deg <= 326.25) {
        return "NW";
      } else if (deg > 326.25 && deg <= 348.75) {
        return "NNW";
      } else {
        return "N";
      }
    },
  
    /* function(temperature)
     * Rounds a temperature to 1 decimal or integer (depending on config.roundTemp).
     *
     * argument temperature number - Temperature.
     *
     * return string - Rounded Temperature.
     */
    roundValue: function (temperature) {
      var decimals = this.config.roundTemp ? 0 : 1;
      return parseFloat(temperature).toFixed(decimals);
    }
  });
  