//Browser Locale conversion utilities
//dates,numbers currency etc
export default {
  ////////////////////////////////////////////////////////
  // attempt to determine user's preferred language settings
  // As of Jan 2020 all major browsers support
  //  navigator.languages
  // but some use navigator.language (singular) to denote UI language preference
  // not browsing language preference
  // so the ideal way to do this is to use navigator.languages[0] for the preferred language
  // and ignore the singular property since we don't care about the actual browser UI language
  // only how the user expects to see the page itself
  //
  // also for sake of future proofing and edge cases need to have it be manually settable as well
  //
  //https://appmakers.dev/bcp-47-language-codes-list/
  ///////////////////////////////////////////
  // Get users default language code
  // first check if overriden in useroptions
  // if not then use browsers own setting
  //if not that then final default of en-US
  getResolvedLanguage() {
    let l = window.$gz.store.state.userOptions.languageOverride;
    if (!window.$gz.util.stringIsNullOrEmpty(l)) {
      return l;
    } else {
      l = window.navigator.languages[0];
      if (!window.$gz.util.stringIsNullOrEmpty(l)) {
        return l;
      } else {
        return "en-US";
      }
    }
  },

  ///////////////////////////////////////////
  // Get users default time zone
  // first check if overriden in useroptions
  // if not then use browsers own setting
  // if that is empty then final default of "America/New_York"
  //https://www.iana.org/time-zones
  //https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
  getResolvedTimeZoneName() {
    let tz = window.$gz.store.state.userOptions.timeZoneOverride;
    if (!window.$gz.util.stringIsNullOrEmpty(tz)) {
      return tz;
    } else {
      tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
      if (!window.$gz.util.stringIsNullOrEmpty(tz)) {
        return tz;
      } else {
        return "America/New_York";
      }
    }
  },
  //////////////////////////////////////////////////
  // Get the user's chosen currency name
  //https://en.wikipedia.org/wiki/ISO_4217
  //default to USD if nothing specified
  getCurrencyName() {
    const cur = window.$gz.store.state.userOptions.currencyName;
    if (!window.$gz.util.stringIsNullOrEmpty(cur)) {
      return cur;
    } else {
      return "USD";
    }
  },
  //////////////////////////////////////////////////
  // Get the user's chosen 12hr clock
  //
  getHour12() {
    return window.$gz.store.state.userOptions.hour12;
  },

  /////////////////////////////////////////////////////////////////////
  // Turn a utc ISO date from server into a vuetify calendar
  // schedule control compatible (epoch) format
  // localized.
  // For ease of use in schedule the epoch (milliseconds) is the best format
  // "It must be a Date, number of seconds since Epoch, or a string in the format of YYYY-MM-DD or YYYY-MM-DD hh:mm. Zero-padding is optional and seconds are ignored.""
  //
  //
  utcDateToScheduleCompatibleFormatLocalized(value, timeZoneName) {
    //This function takes a UTC iso format date string, parses it into a date then converts that date to the User's configured time zone
    //outputs that in a format close to ISO, fixes the space in the middle of the output to match ISO 8601 format then returns as an
    //epoch
    //this is to support controls that are not time zone settable so they are always in local browser time zone of os, however user may be operating
    //ayanova in another desired time zone so this is all to support that scenario

    if (!value) {
      if (window.$gz.dev) {
        throw new Error(
          `locale::utcDateToScheduleCompatibleFormatLocalized - Value is empty`
        );
      }
      return null;
    }

    return new Date(
      new Date(value) //convert to locale timezone and output in the closest thing to iso-8601 format
        .toLocaleString("sv-SE", {
          timeZone: timeZoneName
        })
        .replace(" ", "T") //Safari can't parse the date from here because sv-SE puts a space between date and time and Safari will only parse if it has a T between
    ).getTime();
  },
  ///////////////////////////////////////////////
  // Convert a local schedule epoch timestamp
  // to specified time zone equivalent then
  // to UTC and output as ISO 8601
  //
  //
  localScheduleFormatToUTC8601String(value, timeZoneName) {
    if (!timeZoneName) {
      timeZoneName = this.getResolvedTimeZoneName();
    }

    //input: epoch in local browser time zone
    //output: transform to date and time string
    //convert to desired time zone but at same time and date
    //(i.e. if it browser is vancouver and 1pm is selected but desired is new york's 1pm
    // so convert the string as if it was new york then back to iso so that the time is adjusted forward
    // as if the user was in new york in their browser default)

    //parse in the time in to the specified timezone
    let ret = window.$gz.DateTime.fromISO(
      //output the sched epoch as local time string without zone
      new Date(value).toLocaleString("sv-SE").replace(" ", "T"),
      {
        zone: timeZoneName
      }
    );

    ret = ret.setZone("utc"); //convert to UTC
    ret = ret.toISO(); //output as ISO 8601
    return ret;
  },
  ///////////////////////////////////////////
  // Turn a utc date into a displayable
  // short date and time
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString
  //
  utcDateToShortDateAndTimeLocalized(
    value,
    timeZoneName,
    languageName,
    hour12
  ) {
    if (!value) {
      return "";
    }
    if (!timeZoneName) {
      timeZoneName = this.getResolvedTimeZoneName();
    }
    if (!languageName) {
      languageName = this.getResolvedLanguage();
    }

    if (!hour12) {
      hour12 = this.getHour12();
    }

    //parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
    const parsedDate = new Date(value);

    //is it a valid date?
    if (!(parsedDate instanceof Date && !isNaN(parsedDate))) {
      return "not valid";
    }

    return parsedDate.toLocaleString(languageName, {
      timeZone: timeZoneName,
      dateStyle: "short",
      timeStyle: "short",
      hour12: hour12
    });
  },
  ///////////////////////////////////////////
  // Turn a utc date into a displayable
  // date and time with specific formats
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString
  //
  utcDateToSpecifiedDateAndTimeLocalized(
    value,
    timeZoneName,
    languageName,
    hour12,
    dateStyle,
    timeStyle
  ) {
    if (!value) {
      return "";
    }
    if (!timeZoneName) {
      timeZoneName = this.getResolvedTimeZoneName();
    }
    if (!languageName) {
      languageName = this.getResolvedLanguage();
    }

    if (!hour12) {
      hour12 = this.getHour12();
    }

    //parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
    const parsedDate = new Date(value);

    //is it a valid date?
    if (!(parsedDate instanceof Date && !isNaN(parsedDate))) {
      return "not valid";
    }

    return parsedDate.toLocaleString(languageName, {
      timeZone: timeZoneName,
      dateStyle: dateStyle,
      timeStyle: timeStyle,
      hour12: hour12
    });
  },
  ///////////////////////////////////////////
  // Turn a utc date into a displayable
  // short date
  //
  utcDateToShortDateLocalized(value, timeZoneName, languageName) {
    if (!value) {
      return "";
    }
    if (!timeZoneName) {
      timeZoneName = this.getResolvedTimeZoneName();
    }
    if (!languageName) {
      languageName = this.getResolvedLanguage();
    }

    //parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
    const parsedDate = new Date(value);

    //is it a valid date?
    if (!(parsedDate instanceof Date && !isNaN(parsedDate))) {
      return "not valid";
    }

    return parsedDate.toLocaleDateString(languageName, {
      timeZone: timeZoneName,
      dateStyle: "short"
    });
  }, ///////////////////////////////////////////
  // Turn a utc date into a displayable
  // short time
  //
  utcDateToShortTimeLocalized(value, timeZoneName, languageName, hour12) {
    if (!value) {
      return "";
    }
    if (!timeZoneName) {
      timeZoneName = this.getResolvedTimeZoneName();
    }
    if (!languageName) {
      languageName = this.getResolvedLanguage();
    }

    if (!hour12) {
      hour12 = this.getHour12();
    }

    //parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
    const parsedDate = new Date(value);

    //is it a valid date?
    if (!(parsedDate instanceof Date && !isNaN(parsedDate))) {
      return "not valid";
    }

    return parsedDate.toLocaleTimeString(languageName, {
      timeZone: timeZoneName,
      timeStyle: "short",
      hour12: hour12
    });
  },
  ///////////////////////////////////////////
  // Turn a duration value into a display
  //
  durationLocalized(value, hideSeconds) {
    if (value == null || value == "00:00:00") {
      return "";
    }

    let theDays = 0;
    let theHours = 0;
    let theMinutes = 0;
    let theSeconds = 0;
    let ret = "";

    const work = value.split(":");
    //has days?
    if (work[0].includes(".")) {
      let dh = work[0].split(".");
      theDays = Number(dh[0]);
      theHours = Number(dh[1]);
    } else {
      theHours = Number(work[0]);
    }
    theMinutes = Number(work[1]);
    //has milliseconds? (ignore them)
    if (work[2].includes(".")) {
      let dh = work[2].split(".");
      theSeconds = Number(dh[0]);
    } else {
      theSeconds = Number(work[2]);
    }

    if (theDays != 0) {
      ret += theDays + " " + window.$gz.translation.get("TimeSpanDays") + " ";
    }
    if (theHours != 0) {
      ret += theHours + " " + window.$gz.translation.get("TimeSpanHours") + " ";
    }
    if (theMinutes != 0) {
      ret +=
        theMinutes + " " + window.$gz.translation.get("TimeSpanMinutes") + " ";
    }
    if (!hideSeconds && theSeconds != 0) {
      ret +=
        theSeconds + " " + window.$gz.translation.get("TimeSpanSeconds") + " ";
    }

    return ret;
  },
  ///////////////////////////////////////////////
  // Convert a utc date to local time zone
  // and return time portion only in iso 8601
  // format (used by time and date picker components)
  //
  utcDateStringToLocal8601TimeOnlyString(value, timeZoneName) {
    if (!value) {
      //if no value, return the current time as expected by the time picker
    } else {
      //ok, the reason for sv-SE is that it's a locale that returns the time already in ISO format and 24hr by default
      //that can change over time so if this breaks that's why
      //also fr-CA does as well as possibly en-CA
      //https://stackoverflow.com/a/58633686/8939
      if (!timeZoneName) {
        timeZoneName = this.getResolvedTimeZoneName();
      }

      return new Date(value).toLocaleTimeString("sv-SE", {
        timeZone: timeZoneName
      });
    }
  },
  ///////////////////////////////////////////////
  // Convert a local time only string with date string
  // to UTC and output as ISO 8601
  //  also converts to time zone specified if diff from browser
  //  (used by time and date picker components)
  //
  localTimeDateStringToUTC8601String(value, timeZoneName) {
    //https://moment.github.io/luxon/docs/manual/zones.html#creating-datetimes-in-a-zone
    if (!timeZoneName) {
      timeZoneName = this.getResolvedTimeZoneName();
    }

    //parse in the time in the currently used timezone
    let ret = window.$gz.DateTime.fromISO(value, {
      zone: timeZoneName
    });

    ret = ret.setZone("utc"); //convert to UTC
    ret = ret.toISO(); //output as ISO 8601

    return ret;
  },
  ///////////////////////////////////////////////
  // UTC Now in api format
  // to UTC and output as ISO 8601
  //  (used to set defaults)
  //
  nowUTC8601String(timeZoneName) {
    if (!timeZoneName) {
      timeZoneName = this.getResolvedTimeZoneName();
    }
    const ret = window.$gz.DateTime.local()
      .setZone(timeZoneName)
      .toUTC()
      .toString();
    return ret;
  },
  ///////////////////////////////////////////////
  // UTC ISO 8601 string add minutes
  // and return as UTC ISO 8601 string
  //  (used to set automatic / default adjusted times)
  //
  addMinutesToUTC8601String(val, minutes) {
    if (!val || val == "" || minutes == null || minutes == 0) {
      return val;
    }
    //instantiate a luxon date object from val which is assumed to be an iso string
    let dt = window.$gz.DateTime.fromISO(val);
    if (!dt.isValid) {
      console.error("locale::addMinutes, input not valid:", {
        val: val,
        dt: dt
      });
      return val;
    }
    //add minutes
    dt = dt.plus({ minutes: minutes });
    return dt.toUTC().toString();
  },
  ///////////////////////////////////////////////
  // UTC ISO 8601 string add arbitrary value based
  // on luxon duration format
  // and return as UTC ISO 8601 string
  //https://moment.github.io/luxon/api-docs/index.html#datetimeplus
  //
  addDurationToUTC8601String(val, duration) {
    if (
      !val ||
      val == "" ||
      duration == null ||
      !typeof duration === "object"
    ) {
      return val;
    }
    //instantiate a luxon date object from val which is assumed to be an iso string
    let dt = window.$gz.DateTime.fromISO(val);
    if (!dt.isValid) {
      console.error("locale::addDurationToUTC8601String, input not valid:", {
        val: val,
        dt: dt
      });
      return val;
    }
    //add minutes
    dt = dt.plus(duration);
    return dt.toUTC().toString();
  },
  ///////////////////////////////////////////////
  // parse UTC ISO 8601 strings, diff, return hours
  //
  diffHoursFromUTC8601String(start, stop) {
    if (!start || start == "" || !stop == null || stop == "") {
      return 0;
    }
    //instantiate a luxon date object from val which is assumed to be an iso string
    const startDate = window.$gz.DateTime.fromISO(start);
    if (!startDate.isValid) {
      console.error("locale::diffHours, start not valid:", {
        start: start,
        startDate: startDate
      });
      return 0;
    }
    const stopDate = window.$gz.DateTime.fromISO(stop);
    if (!stopDate.isValid) {
      console.error("locale::diffHours, start not valid:", {
        stop: stop,
        stopDate: stopDate
      });
      return 0;
    }

    // console.log(
    //   "locale:diffhours...",
    //   stopDate.diff(startDate, "hours").toObject().hours
    // );

    // console.log(
    //   "locale:diffhours.. ROUNDED.",
    //   window.$gz.util.roundAccurately(
    //     stopDate.diff(startDate, "hours").toObject().hours,
    //     2
    //   )
    // );

    return window.$gz.util.roundAccurately(
      stopDate.diff(startDate, "hours").toObject().hours,
      2
    );
  },
  ///////////////////////////////////////////////
  // Local now timestamp converted to timeZoneName
  // and output as ISO 8601
  //  (used to inform server of local client time)
  //
  clientLocalZoneTimeStamp(timeZoneName) {
    if (!timeZoneName) {
      timeZoneName = this.getResolvedTimeZoneName();
    }
    const ret = window.$gz.DateTime.local()
      .setZone(timeZoneName)
      .toString();
    return ret;
  },
  ///////////////////////////////////////////////
  // Get default start date time in api format
  // (this is used to centralize and for future)
  defaultStartDateTime() {
    return {
      start: window.$gz.DateTime.local()
        .toUTC()
        .toString(),
      end: window.$gz.DateTime.local()
        .plus({ hours: 1 })
        .toUTC()
        .toString()
    };
  },
  ///////////////////////////////////////////////
  // Convert a utc date to local time zone
  // and return date only portion only in iso 8601
  // format (used by time and date picker components)
  //
  utcDateStringToLocal8601DateOnlyString(value, timeZoneName) {
    if (!value) {
      //if no value, return the current time as expected by the time picker
    } else {
      //ok, the reason for sv-SE is that it's a locale that returns the time already in ISO format and 24hr by default
      //that can change over time so if this breaks that's why
      //also fr-CA does as well as possibly en-CA
      //https://stackoverflow.com/a/58633686/8939
      if (!timeZoneName) {
        timeZoneName = this.getResolvedTimeZoneName();
      }
      return new Date(value).toLocaleDateString("sv-SE", {
        timeZone: timeZoneName
      });
    }
  },
  ///////////////////////////////////////////////
  // Date/time past or future evaluation
  //
  dateIsPast(value) {
    if (!value) {
      return false;
    }

    return new Date(value) < new Date();
  },
  ///////////////////////////////////////////
  // Turn a decimal number into a local
  // currency display
  //
  currencyLocalized(value, languageName, currencyName) {
    if (value == null) return "";
    if (!languageName) {
      languageName = this.getResolvedLanguage();
    }
    if (!currencyName) {
      currencyName = this.getCurrencyName();
    }

    return new Intl.NumberFormat(languageName, {
      style: "currency",
      currency: currencyName
    }).format(value);
  },
  ///////////////////////////////////////////
  // Turn a decimal number into a local
  // decimal format display
  //
  decimalLocalized(value, languageName) {
    if (value == null) return "";
    if (!languageName) {
      languageName = this.getResolvedLanguage();
    }
    //This forces 2 digits after the decimal
    // return new Intl.NumberFormat(languageName, {
    //   minimumFractionDigits: 2
    // }).format(value);
    //this goes with whatever is the local format which for dev testing turned out to be perfect: 1.00 displays as 1 and 1.75 displays as 1.75
    //alignment goes out the window but it follows v7 format
    return new Intl.NumberFormat(languageName).format(value);
  },
  ///////////////////////////////////////////
  // Turn a file / memory size number into a local
  // decimal format display and in reasonable human readable range
  //
  humanFileSize(bytes, languageName, si = false, dp = 1) {
    const thresh = si ? 1000 : 1024;

    if (Math.abs(bytes) < thresh) {
      return bytes + " B";
    }

    const units = si
      ? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
      : ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
    let u = -1;
    const r = 10 ** dp;

    do {
      bytes /= thresh;
      ++u;
    } while (
      Math.round(Math.abs(bytes) * r) / r >= thresh &&
      u < units.length - 1
    );

    return (
      this.decimalLocalized(bytes.toFixed(dp), languageName) + " " + units[u]
    );
  }
};
