    import Log from "../Log"

    /**
     * The resulting geotransform is the equivalent to padfGT1 and then padfGT2 being applied to a point.
     * @param {Float64Array} padfGT1 the first geotransform, six values.
     * @param {Float64Array} padfGT2 the second geotransform, six values.
     * @param {Float64Array} padfGTOut the output geotransform, six values, may safely be the same array as padfGT1 or padfGT2.
     */
    export function GDALComposeGeoTransforms(padfGT1, padfGT2, padfGTOut) {
      // We need to think of the geotransform in a more normal form to do
      // the matrix multiple:
      //
      //  __                     __
      //  | gt[1]   gt[2]   gt[0] |
      //  | gt[4]   gt[5]   gt[3] |
      //  |  0.0     0.0     1.0  |
      //  --                     --
      //
      // Then we can use normal matrix multiplication to produce the
      // composed transformation.  I don't actually reform the matrix
      // explicitly which is why the following may seem kind of spagettish.

      padfGTOut[1] = padfGT2[1] * padfGT1[1] + padfGT2[2] * padfGT1[4]
      padfGTOut[2] = padfGT2[1] * padfGT1[2] + padfGT2[2] * padfGT1[5]
      padfGTOut[0] =
        padfGT2[1] * padfGT1[0] + padfGT2[2] * padfGT1[3] + padfGT2[0] * 1.0

      padfGTOut[4] = padfGT2[4] * padfGT1[1] + padfGT2[5] * padfGT1[4]
      padfGTOut[5] = padfGT2[4] * padfGT1[2] + padfGT2[5] * padfGT1[5]
      padfGTOut[3] =
        padfGT2[4] * padfGT1[0] + padfGT2[5] * padfGT1[3] + padfGT2[3] * 1.0
    }
    /**
     * Invert Geotransform.
     * @param {Float64Array} gt_in Input geotransform (six doubles - unaltered).
     * @param {Float64Array} gt_out Output geotransform (six doubles - updated).
     * @returns {Number} TRUE on success or FALSE if the equation is uninvertable.
     */
    export function GDALInvGeoTransform(gt_in, gt_out) {
      // Special case - no rotation - to avoid computing determinate
      // and potential precision issues.
      if (
        gt_in[2] === 0.0 &&
        gt_in[4] === 0.0 &&
        gt_in[1] !== 0.0 &&
        gt_in[5] !== 0.0
      ) {
        /*X = gt_in[0] + x * gt_in[1]
          Y = gt_in[3] + y * gt_in[5]
          -->
          x = -gt_in[0] / gt_in[1] + (1 / gt_in[1]) * X
          y = -gt_in[3] / gt_in[5] + (1 / gt_in[5]) * Y
        */
        gt_out[0] = -gt_in[0] / gt_in[1]
        gt_out[1] = 1.0 / gt_in[1]
        gt_out[2] = 0.0
        gt_out[3] = -gt_in[3] / gt_in[5]
        gt_out[4] = 0.0
        gt_out[5] = 1.0 / gt_in[5]
        return 1
      }
      // Assume a 3rd row that is [1 0 0].

      // Compute determinate.
      const det = gt_in[1] * gt_in[5] - gt_in[2] * gt_in[4]
      const magnitude = Math.max(
        Math.max(Math.abs(gt_in[1]), Math.abs(gt_in[2])),
        Math.max(Math.abs(gt_in[4]), Math.abs(gt_in[5]))
      )

      if (Math.abs(det) <= 1e-10 * magnitude * magnitude) return 0

      const inv_det = 1.0 / det

      // Compute adjoint, and divide by determinate.

      gt_out[1] = gt_in[5] * inv_det
      gt_out[4] = -gt_in[4] * inv_det

      gt_out[2] = -gt_in[2] * inv_det
      gt_out[5] = gt_in[1] * inv_det

      gt_out[0] = (gt_in[2] * gt_in[3] - gt_in[0] * gt_in[5]) * inv_det
      gt_out[3] = (-gt_in[1] * gt_in[3] + gt_in[0] * gt_in[4]) * inv_det

      return 1
    }
    /**
     * Applies the following computation, converting a (pixel, line) coordinate into a georeferenced (geo_x, geo_y) location.
     * @param {Float64Array} padfGeoTransform Six coefficient GeoTransform to apply.
     * @param {Number} dfPixel Input pixel position.
     * @param {Number} dfLine Input line position.
     * @returns {Object} output location
     */
    export function GDALApplyGeoTransform(padfGeoTransform, dfPixel, dfLine) {
      const pdfGeoX =
        padfGeoTransform[0] +
        dfPixel * padfGeoTransform[1] +
        dfLine * padfGeoTransform[2]

      const pdfGeoY =
        padfGeoTransform[3] +
        dfPixel * padfGeoTransform[4] +
        dfLine * padfGeoTransform[5]

      return { pdfGeoX, pdfGeoY }
    }

    /**
     * Ground Control Point
     * @typedef GCP
     * @type {Object}
     * @property {String} pszId Unique identifier, often numeric
     * @property {String} pszInfo Informational message or ""
     * @property {Number} dfGCPPixel Pixel (x) location of GCP on raster
     * @property {Number} dfGCPLine Line (y) location of GCP on raster
     * @property {Number} dfGCPX X position of GCP in georeferenced space
     * @property {Number} dfGCPY Y position of GCP in georeferenced space
     * @property {Number} dfGCPZ Elevation of GCP, or zero if not known
     */

    /**
     * Given a set of GCPs perform first order fit as a geotransform.
     * @param {GCP[]} pasGCPs the list of GCP structures.
     * @param {Float64Array} padfGeoTransform the six double array in which the affine geotransformation will be returned.
     * @param {Boolean} bApproxOK If FALSE the function will fail if the geotransform is not essentially an exact fit (within 0.25 pixel) for all GCPs.
     * @return {Boolean} TRUE on success or FALSE if there aren't enough points to prepare a geotransform, the pointers are ill-determined or if bApproxOK is FALSE and the fit is poor.
     */
    export function GDALGCPsToGeoTransform(pasGCPs, padfGeoTransform, bApproxOK) {
      let dfPixelThreshold = 0.25
      if (!bApproxOK) {
        // 这里是在读取配置信息 为方便前端实现逻辑默认开启
        bApproxOK = true
        dfPixelThreshold = 0.25
        // bApproxOK = CPLTestBool(
        //     CPLGetConfigOption("GDAL_GCPS_TO_GEOTRANSFORM_APPROX_OK", "NO"));
        // if (!bApproxOK)
        // {
        //     // coverity[tainted_data]
        //     dfPixelThreshold = CPLAtof(CPLGetConfigOption(
        //         "GDAL_GCPS_TO_GEOTRANSFORM_APPROX_THRESHOLD", "0.25"));
        // }
      }
      const nGCPCount = pasGCPs.length
      if (nGCPCount < 2) {
        return false
      }

      /* -------------------------------------------------------------------- */
      /*      Recognise a few special cases.                                  */
      /* -------------------------------------------------------------------- */
      if (nGCPCount === 2) {
        if (
          pasGCPs[1].dfGCPPixel == pasGCPs[0].dfGCPPixel ||
          pasGCPs[1].dfGCPLine == pasGCPs[0].dfGCPLine
        )
          return false

        padfGeoTransform[1] =
          (pasGCPs[1].dfGCPX - pasGCPs[0].dfGCPX) /
          (pasGCPs[1].dfGCPPixel - pasGCPs[0].dfGCPPixel)
        padfGeoTransform[2] = 0.0

        padfGeoTransform[4] = 0.0
        padfGeoTransform[5] =
          (pasGCPs[1].dfGCPY - pasGCPs[0].dfGCPY) /
          (pasGCPs[1].dfGCPLine - pasGCPs[0].dfGCPLine)

        padfGeoTransform[0] =
          pasGCPs[0].dfGCPX -
          pasGCPs[0].dfGCPPixel * padfGeoTransform[1] -
          pasGCPs[0].dfGCPLine * padfGeoTransform[2]

        padfGeoTransform[3] =
          pasGCPs[0].dfGCPY -
          pasGCPs[0].dfGCPPixel * padfGeoTransform[4] -
          pasGCPs[0].dfGCPLine * padfGeoTransform[5]

        return true
      }

      /* -------------------------------------------------------------------- */
      /*      Special case of 4 corner coordinates of a non-rotated           */
      /*      image.  The points must be in TL-TR-BR-BL order for now.        */
      /*      This case helps avoid some imprecision in the general           */
      /*      calculations.                                                   */
      /* -------------------------------------------------------------------- */
      if (
        nGCPCount == 4 &&
        pasGCPs[0].dfGCPLine == pasGCPs[1].dfGCPLine &&
        pasGCPs[2].dfGCPLine == pasGCPs[3].dfGCPLine &&
        pasGCPs[0].dfGCPPixel == pasGCPs[3].dfGCPPixel &&
        pasGCPs[1].dfGCPPixel == pasGCPs[2].dfGCPPixel &&
        pasGCPs[0].dfGCPLine != pasGCPs[2].dfGCPLine &&
        pasGCPs[0].dfGCPPixel != pasGCPs[1].dfGCPPixel &&
        pasGCPs[0].dfGCPY == pasGCPs[1].dfGCPY &&
        pasGCPs[2].dfGCPY == pasGCPs[3].dfGCPY &&
        pasGCPs[0].dfGCPX == pasGCPs[3].dfGCPX &&
        pasGCPs[1].dfGCPX == pasGCPs[2].dfGCPX &&
        pasGCPs[0].dfGCPY != pasGCPs[2].dfGCPY &&
        pasGCPs[0].dfGCPX != pasGCPs[1].dfGCPX
      ) {
        padfGeoTransform[1] =
          (pasGCPs[1].dfGCPX - pasGCPs[0].dfGCPX) /
          (pasGCPs[1].dfGCPPixel - pasGCPs[0].dfGCPPixel)
        padfGeoTransform[2] = 0.0
        padfGeoTransform[4] = 0.0
        padfGeoTransform[5] =
          (pasGCPs[2].dfGCPY - pasGCPs[1].dfGCPY) /
          (pasGCPs[2].dfGCPLine - pasGCPs[1].dfGCPLine)

        padfGeoTransform[0] =
          pasGCPs[0].dfGCPX - pasGCPs[0].dfGCPPixel * padfGeoTransform[1]
        padfGeoTransform[3] =
          pasGCPs[0].dfGCPY - pasGCPs[0].dfGCPLine * padfGeoTransform[5]
        return true
      }

      /* -------------------------------------------------------------------- */
      /*      Compute source and destination ranges so we can normalize       */
      /*      the values to make the least squares computation more stable.   */
      /* -------------------------------------------------------------------- */
      let min_pixel = pasGCPs[0].dfGCPPixel
      let max_pixel = pasGCPs[0].dfGCPPixel
      let min_line = pasGCPs[0].dfGCPLine
      let max_line = pasGCPs[0].dfGCPLine
      let min_geox = pasGCPs[0].dfGCPX
      let max_geox = pasGCPs[0].dfGCPX
      let min_geoy = pasGCPs[0].dfGCPY
      let max_geoy = pasGCPs[0].dfGCPY

      for (let i = 1; i < nGCPCount; ++i) {
        min_pixel = Math.min(min_pixel, pasGCPs[i].dfGCPPixel)
        max_pixel = Math.max(max_pixel, pasGCPs[i].dfGCPPixel)
        min_line = Math.min(min_line, pasGCPs[i].dfGCPLine)
        max_line = Math.max(max_line, pasGCPs[i].dfGCPLine)
        min_geox = Math.min(min_geox, pasGCPs[i].dfGCPX)
        max_geox = Math.max(max_geox, pasGCPs[i].dfGCPX)
        min_geoy = Math.min(min_geoy, pasGCPs[i].dfGCPY)
        max_geoy = Math.max(max_geoy, pasGCPs[i].dfGCPY)
      }

      const EPS = 1.0e-12

      if (
        Math.abs(max_pixel - min_pixel) < EPS ||
        Math.abs(max_line - min_line) < EPS ||
        Math.abs(max_geox - min_geox) < EPS ||
        Math.abs(max_geoy - min_geoy) < EPS
      ) {
        return false // degenerate in at least one dimension.
      }

      const pl_normalize = new Float64Array(6),
        geo_normalize = new Float64Array(6)

      pl_normalize[0] = -min_pixel / (max_pixel - min_pixel)
      pl_normalize[1] = 1.0 / (max_pixel - min_pixel)
      pl_normalize[2] = 0.0
      pl_normalize[3] = -min_line / (max_line - min_line)
      pl_normalize[4] = 0.0
      pl_normalize[5] = 1.0 / (max_line - min_line)

      geo_normalize[0] = -min_geox / (max_geox - min_geox)
      geo_normalize[1] = 1.0 / (max_geox - min_geox)
      geo_normalize[2] = 0.0
      geo_normalize[3] = -min_geoy / (max_geoy - min_geoy)
      geo_normalize[4] = 0.0
      geo_normalize[5] = 1.0 / (max_geoy - min_geoy)
      /* -------------------------------------------------------------------- */
      /* In the general case, do a least squares error approximation by       */
      /* solving the equation Sum[(A - B*x + C*y - Lon)^2] = minimum          */
      /* -------------------------------------------------------------------- */

      let sum_x = 0.0
      let sum_y = 0.0
      let sum_xy = 0.0
      let sum_xx = 0.0
      let sum_yy = 0.0
      let sum_Lon = 0.0
      let sum_Lonx = 0.0
      let sum_Lony = 0.0
      let sum_Lat = 0.0
      let sum_Latx = 0.0
      let sum_Laty = 0.0

      for (let i = 0; i < nGCPCount; ++i) {
        const { pdfGeoX: pixel, pdfGeoY: line } = GDALApplyGeoTransform(
          pl_normalize,
          pasGCPs[i].dfGCPPixel,
          pasGCPs[i].dfGCPLine
        )
        const { pdfGeoX: geox, pdfGeoY: geoy } = GDALApplyGeoTransform(
          geo_normalize,
          pasGCPs[i].dfGCPX,
          pasGCPs[i].dfGCPY
        )

        sum_x += pixel
        sum_y += line
        sum_xy += pixel * line
        sum_xx += pixel * pixel
        sum_yy += line * line
        sum_Lon += geox
        sum_Lonx += geox * pixel
        sum_Lony += geox * line
        sum_Lat += geoy
        sum_Latx += geoy * pixel
        sum_Laty += geoy * line
      }

      const divisor =
        nGCPCount * (sum_xx * sum_yy - sum_xy * sum_xy) +
        2 * sum_x * sum_y * sum_xy -
        sum_y * sum_y * sum_xx -
        sum_x * sum_x * sum_yy

      /* -------------------------------------------------------------------- */
      /*      If the divisor is zero, there is no valid solution.             */
      /* -------------------------------------------------------------------- */
      if (divisor === 0.0) return false

      /* -------------------------------------------------------------------- */
      /*      Compute top/left origin.                                        */
      /* -------------------------------------------------------------------- */
      const gt_normalized = new Float64Array(6)
      gt_normalized[0] =
        (sum_Lon * (sum_xx * sum_yy - sum_xy * sum_xy) +
          sum_Lonx * (sum_y * sum_xy - sum_x * sum_yy) +
          sum_Lony * (sum_x * sum_xy - sum_y * sum_xx)) /
        divisor

      gt_normalized[3] =
        (sum_Lat * (sum_xx * sum_yy - sum_xy * sum_xy) +
          sum_Latx * (sum_y * sum_xy - sum_x * sum_yy) +
          sum_Laty * (sum_x * sum_xy - sum_y * sum_xx)) /
        divisor

      /* -------------------------------------------------------------------- */
      /*      Compute X related coefficients.                                 */
      /* -------------------------------------------------------------------- */
      gt_normalized[1] =
        (sum_Lon * (sum_y * sum_xy - sum_x * sum_yy) +
          sum_Lonx * (nGCPCount * sum_yy - sum_y * sum_y) +
          sum_Lony * (sum_x * sum_y - sum_xy * nGCPCount)) /
        divisor

      gt_normalized[2] =
        (sum_Lon * (sum_x * sum_xy - sum_y * sum_xx) +
          sum_Lonx * (sum_x * sum_y - nGCPCount * sum_xy) +
          sum_Lony * (nGCPCount * sum_xx - sum_x * sum_x)) /
        divisor

      /* -------------------------------------------------------------------- */
      /*      Compute Y related coefficients.                                 */
      /* -------------------------------------------------------------------- */
      gt_normalized[4] =
        (sum_Lat * (sum_y * sum_xy - sum_x * sum_yy) +
          sum_Latx * (nGCPCount * sum_yy - sum_y * sum_y) +
          sum_Laty * (sum_x * sum_y - sum_xy * nGCPCount)) /
        divisor

      gt_normalized[5] =
        (sum_Lat * (sum_x * sum_xy - sum_y * sum_xx) +
          sum_Latx * (sum_x * sum_y - nGCPCount * sum_xy) +
          sum_Laty * (nGCPCount * sum_xx - sum_x * sum_x)) /
        divisor
      /* -------------------------------------------------------------------- */
      /*      Compose the resulting transformation with the normalization     */
      /*      geotransformations.                                             */
      /* -------------------------------------------------------------------- */
      const gt1p2 = new Float64Array(6)
      const inv_geo_normalize = new Float64Array(6)
      if (!GDALInvGeoTransform(geo_normalize, inv_geo_normalize)) return false

      GDALComposeGeoTransforms(pl_normalize, gt_normalized, gt1p2)

      GDALComposeGeoTransforms(gt1p2, inv_geo_normalize, padfGeoTransform)

      /* -------------------------------------------------------------------- */
      /*      Now check if any of the input points fit this poorly.           */
      /* -------------------------------------------------------------------- */
      if (bApproxOK) {
        // FIXME? Not sure if it is the more accurate way of computing
        // pixel size
        const dfPixelSize =
          0.5 *
          (Math.abs(padfGeoTransform[1]) +
            Math.abs(padfGeoTransform[2]) +
            Math.abs(padfGeoTransform[4]) +
            Math.abs(padfGeoTransform[5]))
        if (dfPixelSize === 0.0) {
          Log.info('dfPixelSize = 0')
          return false
        }

        for (let i = 0; i < nGCPCount; i++) {
          const dfErrorX =
            pasGCPs[i].dfGCPPixel * padfGeoTransform[1] +
            pasGCPs[i].dfGCPLine * padfGeoTransform[2] +
            padfGeoTransform[0] -
            pasGCPs[i].dfGCPX

          const dfErrorY =
            pasGCPs[i].dfGCPPixel * padfGeoTransform[4] +
            pasGCPs[i].dfGCPLine * padfGeoTransform[5] +
            padfGeoTransform[3] -
            pasGCPs[i].dfGCPY

          if (
            Math.abs(dfErrorX) > dfPixelThreshold * dfPixelSize ||
            Math.abs(dfErrorY) > dfPixelThreshold * dfPixelSize
          ) {
            Log.info(
              `dfErrorX/dfPixelSize = ${
                Math.abs(dfErrorX) / dfPixelSize
              }, dfErrorY/dfPixelSize = ${Math.abs(dfErrorY) / dfPixelSize}`
            )
            return false
          }
        }
      }
      return true
    }