///|
/// Scale the user-space X and Y axes by `sx` and `sy`.
///
/// The scale is added after the context's existing user-space transformation;
/// the CTM remains part of save/restore graphics state. A scale that makes the
/// CTM non-invertible raises `CairoInvalidArgument(InvalidMatrix, _)` and makes
/// that Cairo status sticky.
pub fn Context::scale(
  self : Context,
  sx : Double,
  sy : Double,
) -> Unit raise CairoError {
  check_context_status_raw(@context_impl.scale_raw(self.to_raw(), sx, sy))
}

///|
/// Translate the user-space origin by `(tx, ty)`.
///
/// The offset is interpreted through the CTM that existed before this call, so
/// translation is added after the existing user-space transformation. The CTM
/// is part of save/restore graphics state. Raises the checked context status.
pub fn Context::translate(
  self : Context,
  tx : Double,
  ty : Double,
) -> Unit raise CairoError {
  check_context_status_raw(@context_impl.translate_raw(self.to_raw(), tx, ty))
}

///|
/// Rotate the user-space axes by `radians`.
///
/// Positive angles rotate from positive X toward positive Y, which appears
/// clockwise with Cairo's default downward-pointing Y axis. Rotation is added
/// after the existing user-space transformation. Raises the checked context
/// status.
pub fn Context::rotate(
  self : Context,
  radians : Double,
) -> Unit raise CairoError {
  check_context_status_raw(@context_impl.rotate_raw(self.to_raw(), radians))
}

///|
/// Apply `matrix` as an additional transformation of user space.
///
/// Unlike `set_matrix()`, this composes with the current CTM; Cairo applies the
/// new transformation after the existing one. If the result is not invertible,
/// raises `CairoInvalidArgument(InvalidMatrix, _)` and makes that status sticky.
pub fn Context::transform(
  self : Context,
  matrix : Matrix,
) -> Unit raise CairoError {
  check_context_status_raw(
    @context_impl.transform_raw(
      self.to_raw(),
      matrix.xx,
      matrix.yx,
      matrix.xy,
      matrix.yy,
      matrix.x0,
      matrix.y0,
    ),
  )
}

///|
/// Replace the current user-to-device transformation with `matrix`.
///
/// No composition with the previous CTM occurs. The value is copied, so later
/// use of the pure `Matrix` cannot change this context. A non-invertible matrix
/// raises `CairoInvalidArgument(InvalidMatrix, _)` and makes that status sticky.
pub fn Context::set_matrix(
  self : Context,
  matrix : Matrix,
) -> Unit raise CairoError {
  check_context_status_raw(
    @context_impl.set_matrix_raw(
      self.to_raw(),
      matrix.xx,
      matrix.yx,
      matrix.xy,
      matrix.yy,
      matrix.x0,
      matrix.y0,
    ),
  )
}

///|
fn matrix_from_context_output(
  f : (
    Ref[Double],
    Ref[Double],
    Ref[Double],
    Ref[Double],
    Ref[Double],
    Ref[Double],
  ) -> Int,
) -> Matrix raise CairoError {
  let xx = Ref(0.0)
  let yx = Ref(0.0)
  let xy = Ref(0.0)
  let yy = Ref(0.0)
  let x0 = Ref(0.0)
  let y0 = Ref(0.0)
  check_context_status_raw(f(xx, yx, xy, yy, x0, y0))
  Matrix::new(xx=xx.val, yx=yx.val, xy=xy.val, yy=yy.val, x0=x0.val, y0=y0.val)
}

///|
/// Return a pure snapshot of the current user-to-device matrix.
///
/// The returned `Matrix` shares no mutable state with this context. CTM changes
/// made after the call do not alter it. Raises any existing checked context
/// status.
pub fn Context::get_matrix(self : Context) -> Matrix raise CairoError {
  matrix_from_context_output((xx, yx, xy, yy, x0, y0) => {
    @context_impl.get_matrix_raw(self.to_raw(), xx, yx, xy, yy, x0, y0)
  })
}

///|
/// Reset the CTM so user space and device space coincide.
///
/// Afterward one user-space unit maps to one device-space unit and the origins
/// align. This CTM change can be undone by a matching `restore()` when made
/// inside saved graphics state. Raises the checked context status.
pub fn Context::identity_matrix(self : Context) -> Unit raise CairoError {
  check_context_status_raw(@context_impl.identity_matrix_raw(self.to_raw()))
}

///|
fn point_from_context_output(
  f : (Ref[Double], Ref[Double]) -> Int,
) -> (Double, Double) raise CairoError {
  let x = Ref(0.0)
  let y = Ref(0.0)
  check_context_status_raw(f(x, y))
  (x.val, y.val)
}

///|
/// Transform user-space point `(x, y)` into device-space coordinates.
///
/// The full CTM is applied, including translation. This query does not mutate
/// the input values or context and raises any existing checked context status.
pub fn Context::user_to_device(
  self : Context,
  x : Double,
  y : Double,
) -> (Double, Double) raise CairoError {
  point_from_context_output((out_x, out_y) => {
    @context_impl.user_to_device_raw(self.to_raw(), x, y, out_x, out_y)
  })
}

///|
/// Transform a user-space distance vector into device space.
///
/// Scale, rotation, and shear are applied, but CTM translation is deliberately
/// ignored. Use `user_to_device()` for positions. This query does not mutate
/// the context and raises any existing checked context status.
pub fn Context::user_to_device_distance(
  self : Context,
  dx : Double,
  dy : Double,
) -> (Double, Double) raise CairoError {
  point_from_context_output((out_dx, out_dy) => {
    @context_impl.user_to_device_distance_raw(
      self.to_raw(),
      dx,
      dy,
      out_dx,
      out_dy,
    )
  })
}

///|
/// Transform device-space point `(x, y)` into user-space coordinates.
///
/// Cairo applies the inverse CTM, including its translation. This query does
/// not mutate the context and raises any existing checked context status.
pub fn Context::device_to_user(
  self : Context,
  x : Double,
  y : Double,
) -> (Double, Double) raise CairoError {
  point_from_context_output((out_x, out_y) => {
    @context_impl.device_to_user_raw(self.to_raw(), x, y, out_x, out_y)
  })
}

///|
/// Transform a device-space distance vector into user space.
///
/// Cairo applies the inverse CTM's scale, rotation, and shear while ignoring
/// inverse translation. Use `device_to_user()` for positions. This query does
/// not mutate the context and raises any existing checked context status.
pub fn Context::device_to_user_distance(
  self : Context,
  dx : Double,
  dy : Double,
) -> (Double, Double) raise CairoError {
  point_from_context_output((out_dx, out_dy) => {
    @context_impl.device_to_user_distance_raw(
      self.to_raw(),
      dx,
      dy,
      out_dx,
      out_dy,
    )
  })
}