/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { ctypes } from "resource://gre/modules/ctypes.sys.mjs";

const BYTE = ctypes.uint8_t;
const WORD = ctypes.uint16_t;
const DWORD = ctypes.uint32_t;
const WCHAR = ctypes.char16_t;
const BOOL = ctypes.int;

export var WindowsVersionInfo = {
  UNKNOWN_VERSION_INFO: {
    servicePackMajor: null,
    servicePackMinor: null,
    buildNumber: null,
  },

  /**
   * Gets the service pack and build number on Windows platforms.
   *
   * @param opts {Object} keyword arguments
   * @param opts.throwOnError {boolean} Optional, defaults to true. If set to
   *    false will return an object with keys set to null instead of throwing an
   *    error. If set to true, errors will be thrown instead.
   * @throws If `throwOnError` is true and version information cannot be
   *    determined.
   * @return {object} An object containing keys `servicePackMajor`,
   *    `servicePackMinor`, and `buildNumber`. If `throwOnError` is false, these
   *    values may be null.
   */
  get({ throwOnError = true } = {}) {
    function throwOrUnknown(err) {
      if (throwOnError) {
        throw err;
      }
      console.error(err);
      return WindowsVersionInfo.UNKNOWN_VERSION_INFO;
    }

    if (AppConstants.platform !== "win") {
      return throwOrUnknown(
        WindowsVersionInfo.NotWindowsError(
          `Cannot get Windows version info on platform ${AppConstants.platform}`
        )
      );
    }

    // This structure is described at:
    // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
    const SZCSDVERSIONLENGTH = 128;
    const OSVERSIONINFOEXW = new ctypes.StructType("OSVERSIONINFOEXW", [
      { dwOSVersionInfoSize: DWORD },
      { dwMajorVersion: DWORD },
      { dwMinorVersion: DWORD },
      { dwBuildNumber: DWORD },
      { dwPlatformId: DWORD },
      { szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH) },
      { wServicePackMajor: WORD },
      { wServicePackMinor: WORD },
      { wSuiteMask: WORD },
      { wProductType: BYTE },
      { wReserved: BYTE },
    ]);

    let kernel32;
    try {
      kernel32 = ctypes.open("kernel32");
    } catch (err) {
      return throwOrUnknown(
        new WindowsVersionInfo.CannotOpenKernelError(
          `Unable to open kernel32! ${err}`
        )
      );
    }

    try {
      let GetVersionEx = kernel32.declare(
        "GetVersionExW",
        ctypes.winapi_abi,
        BOOL,
        OSVERSIONINFOEXW.ptr
      );
      let winVer = OSVERSIONINFOEXW();
      winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;

      if (GetVersionEx(winVer.address()) === 0) {
        throw new WindowsVersionInfo.GetVersionExError(
          "Failure in GetVersionEx (returned 0)"
        );
      }

      return {
        servicePackMajor: winVer.wServicePackMajor,
        servicePackMinor: winVer.wServicePackMinor,
        buildNumber: winVer.dwBuildNumber,
      };
    } catch (err) {
      return throwOrUnknown(err);
    } finally {
      if (kernel32) {
        kernel32.close();
      }
    }
  },

  CannotOpenKernelError: class extends Error {},
  GetVersionExError: class extends Error {},
  NotWindowsError: class extends Error {},
};
