#nullable enable

using System;
using System.Globalization;
using System.Text;

using Swan;

namespace Unosquare.RaspberryIO.Camera {
  /// <summary>
  /// A base class to implement raspistill and raspivid wrappers
  /// Full documentation available at
  /// https://www.raspberrypi.org/documentation/raspbian/applications/camera.md.
  /// </summary>
  public abstract class CameraSettingsBase {
    /// <summary>
    /// The Invariant Culture shorthand.
    /// </summary>
    protected static readonly CultureInfo Ci = CultureInfo.InvariantCulture;

    #region Capture Settings

    /// <summary>
    /// Gets or sets the timeout milliseconds.
    /// Default value is 5000
    /// Recommended value is at least 300 in order to let the light collectors open.
    /// </summary>
    public Int32 CaptureTimeoutMilliseconds { get; set; } = 5000;

    /// <summary>
    /// Gets or sets a value indicating whether or not to show a preview window on the screen.
    /// </summary>
    public Boolean CaptureDisplayPreview { get; set; } = false;

    /// <summary>
    /// Gets or sets a value indicating whether a preview window is shown in full screen  mode if enabled.
    /// </summary>
    public Boolean CaptureDisplayPreviewInFullScreen { get; set; } = true;

    /// <summary>
    /// Gets or sets a value indicating whether video stabilization should be enabled.
    /// </summary>
    public Boolean CaptureVideoStabilizationEnabled { get; set; } = false;

    /// <summary>
    /// Gets or sets the display preview opacity only if the display preview property is enabled.
    /// </summary>
    public Byte CaptureDisplayPreviewOpacity { get; set; } = 255;

    /// <summary>
    /// Gets or sets the capture sensor region of interest in relative coordinates.
    /// </summary>
    public CameraRect CaptureSensorRoi { get; set; } = CameraRect.Default;

    /// <summary>
    /// Gets or sets the capture shutter speed in microseconds.
    /// Default -1, Range 0 to 6000000 (equivalent to 6 seconds).
    /// </summary>
    public Int32 CaptureShutterSpeedMicroseconds { get; set; } = -1;

    /// <summary>
    /// Gets or sets the exposure mode.
    /// </summary>
    public CameraExposureMode CaptureExposure { get; set; } = CameraExposureMode.Auto;

    /// <summary>
    /// Gets or sets the picture EV compensation. Default is 0, Range is -10 to 10
    /// Camera exposure compensation is commonly stated in terms of EV units;
    /// 1 EV is equal to one exposure step (or stop), corresponding to a doubling of exposure.
    /// Exposure can be adjusted by changing either the lens f-number or the exposure time;
    /// which one is changed usually depends on the camera's exposure mode.
    /// </summary>
    public Int32 CaptureExposureCompensation { get; set; } = 0;

    /// <summary>
    /// Gets or sets the capture metering mode.
    /// </summary>
    public CameraMeteringMode CaptureMeteringMode { get; set; } = CameraMeteringMode.Average;

    /// <summary>
    /// Gets or sets the automatic white balance mode. By default it is set to Auto.
    /// </summary>
    public CameraWhiteBalanceMode CaptureWhiteBalanceControl { get; set; } = CameraWhiteBalanceMode.Auto;

    /// <summary>
    /// Gets or sets the capture white balance gain on the blue channel. Example: 1.25
    /// Only takes effect if White balance control is set to off.
    /// Default is 0.
    /// </summary>
    public Decimal CaptureWhiteBalanceGainBlue { get; set; } = 0M;

    /// <summary>
    /// Gets or sets the capture white balance gain on the red channel. Example: 1.75
    /// Only takes effect if White balance control is set to off.
    /// Default is 0.
    /// </summary>
    public Decimal CaptureWhiteBalanceGainRed { get; set; } = 0M;

    /// <summary>
    /// Gets or sets the dynamic range compensation.
    /// DRC changes the images by increasing the range of dark areas, and decreasing the brighter areas. This can improve the image in low light areas.
    /// </summary>
    public CameraDynamicRangeCompensation CaptureDynamicRangeCompensation {
      get; set;
    } = CameraDynamicRangeCompensation.Off;

    #endregion

    #region Image Properties

    /// <summary>
    /// Gets or sets the width of the picture to take.
    /// Less than or equal to 0 in either width or height means maximum resolution available.
    /// </summary>
    public Int32 CaptureWidth { get; set; } = 640;

    /// <summary>
    /// Gets or sets the height of the picture to take.
    /// Less than or equal to 0 in either width or height means maximum resolution available.
    /// </summary>
    public Int32 CaptureHeight { get; set; } = 480;

    /// <summary>
    /// Gets or sets the picture sharpness. Default is 0, Range form -100 to 100.
    /// </summary>
    public Int32 ImageSharpness { get; set; } = 0;

    /// <summary>
    /// Gets or sets the picture contrast. Default is 0, Range form -100 to 100.
    /// </summary>
    public Int32 ImageContrast { get; set; } = 0;

    /// <summary>
    /// Gets or sets the picture brightness. Default is 50, Range form 0 to 100.
    /// </summary>
    public Int32 ImageBrightness { get; set; } = 50; // from 0 to 100

    /// <summary>
    /// Gets or sets the picture saturation. Default is 0, Range form -100 to 100.
    /// </summary>
    public Int32 ImageSaturation { get; set; } = 0;

    /// <summary>
    /// Gets or sets the picture ISO. Default is -1 Range is 100 to 800
    /// The higher the value, the more light the sensor absorbs.
    /// </summary>
    public Int32 ImageIso { get; set; } = -1;

    /// <summary>
    /// Gets or sets the image capture effect to be applied.
    /// </summary>
    public CameraImageEffect ImageEffect { get; set; } = CameraImageEffect.None;

    /// <summary>
    /// Gets or sets the color effect U coordinates.
    /// Default is -1, Range is 0 to 255
    /// 128:128 should be effectively a monochrome image.
    /// </summary>
    public Int32 ImageColorEffectU { get; set; } = -1; // 0 to 255

    /// <summary>
    /// Gets or sets the color effect V coordinates.
    /// Default is -1, Range is 0 to 255
    /// 128:128 should be effectively a monochrome image.
    /// </summary>
    public Int32 ImageColorEffectV { get; set; } = -1; // 0 to 255

    /// <summary>
    /// Gets or sets the image rotation. Default is no rotation.
    /// </summary>
    public CameraImageRotation ImageRotation { get; set; } = CameraImageRotation.None;

    /// <summary>
    /// Gets or sets a value indicating whether the image should be flipped horizontally.
    /// </summary>
    public Boolean ImageFlipHorizontally {
      get; set;
    }

    /// <summary>
    /// Gets or sets a value indicating whether the image should be flipped vertically.
    /// </summary>
    public Boolean ImageFlipVertically {
      get; set;
    }

    /// <summary>
    /// Gets or sets the image annotations using a bitmask (or flags) notation.
    /// Apply a bitwise OR to the enumeration to include multiple annotations.
    /// </summary>
    public CameraAnnotation ImageAnnotations { get; set; } = CameraAnnotation.None;

    /// <summary>
    /// Gets or sets the image annotations text.
    /// Text may include date/time placeholders by using the '%' character, as used by strftime.
    /// Example: ABC %Y-%m-%d %X will output ABC 2015-10-28 20:09:33.
    /// </summary>
    public String ImageAnnotationsText { get; set; } = String.Empty;

    /// <summary>
    /// Gets or sets the font size of the text annotations
    /// Default is -1, range is 6 to 160.
    /// </summary>
    public Int32 ImageAnnotationFontSize { get; set; } = -1;

    /// <summary>
    /// Gets or sets the color of the text annotations.
    /// </summary>
    /// <value>
    /// The color of the image annotation font.
    /// </value>
    public CameraColor? ImageAnnotationFontColor { get; set; } = null;

    /// <summary>
    /// Gets or sets the background color for text annotations.
    /// </summary>
    /// <value>
    /// The image annotation background.
    /// </value>
    public CameraColor? ImageAnnotationBackground { get; set; } = null;

    #endregion

    #region Interface

    /// <summary>
    /// Gets the command file executable.
    /// </summary>
    public abstract String CommandName {
      get;
    }

    /// <summary>
    /// Creates the process arguments.
    /// </summary>
    /// <returns>The string that represents the process arguments.</returns>
    public virtual String CreateProcessArguments() {
      StringBuilder sb = new StringBuilder();
      _ = sb.Append("-o -"); // output to standard output as opposed to a file.
      _ = sb.Append($" -t {(this.CaptureTimeoutMilliseconds < 0 ? "0" : this.CaptureTimeoutMilliseconds.ToString(Ci))}");

      // Basic Width and height
      if(this.CaptureWidth > 0 && this.CaptureHeight > 0) {
        _ = sb.Append($" -w {this.CaptureWidth.ToString(Ci)}");
        _ = sb.Append($" -h {this.CaptureHeight.ToString(Ci)}");
      }

      // Display Preview
      if(this.CaptureDisplayPreview) {
        if(this.CaptureDisplayPreviewInFullScreen) {
          _ = sb.Append(" -f");
        }

        if(this.CaptureDisplayPreviewOpacity != Byte.MaxValue) {
          _ = sb.Append($" -op {this.CaptureDisplayPreviewOpacity.ToString(Ci)}");
        }
      } else {
        _ = sb.Append(" -n"); // no preview
      }

      // Picture Settings
      if(this.ImageSharpness != 0) {
        _ = sb.Append($" -sh {this.ImageSharpness.Clamp(-100, 100).ToString(Ci)}");
      }

      if(this.ImageContrast != 0) {
        _ = sb.Append($" -co {this.ImageContrast.Clamp(-100, 100).ToString(Ci)}");
      }

      if(this.ImageBrightness != 50) {
        _ = sb.Append($" -br {this.ImageBrightness.Clamp(0, 100).ToString(Ci)}");
      }

      if(this.ImageSaturation != 0) {
        _ = sb.Append($" -sa {this.ImageSaturation.Clamp(-100, 100).ToString(Ci)}");
      }

      if(this.ImageIso >= 100) {
        _ = sb.Append($" -ISO {this.ImageIso.Clamp(100, 800).ToString(Ci)}");
      }

      if(this.CaptureVideoStabilizationEnabled) {
        _ = sb.Append(" -vs");
      }

      if(this.CaptureExposureCompensation != 0) {
        _ = sb.Append($" -ev {this.CaptureExposureCompensation.Clamp(-10, 10).ToString(Ci)}");
      }

      if(this.CaptureExposure != CameraExposureMode.Auto) {
        _ = sb.Append($" -ex {this.CaptureExposure.ToString().ToLowerInvariant()}");
      }

      if(this.CaptureWhiteBalanceControl != CameraWhiteBalanceMode.Auto) {
        _ = sb.Append($" -awb {this.CaptureWhiteBalanceControl.ToString().ToLowerInvariant()}");
      }

      if(this.ImageEffect != CameraImageEffect.None) {
        _ = sb.Append($" -ifx {this.ImageEffect.ToString().ToLowerInvariant()}");
      }

      if(this.ImageColorEffectU >= 0 && this.ImageColorEffectV >= 0) {
        _ = sb.Append($" -cfx {this.ImageColorEffectU.Clamp(0, 255).ToString(Ci)}:{this.ImageColorEffectV.Clamp(0, 255).ToString(Ci)}");
      }

      if(this.CaptureMeteringMode != CameraMeteringMode.Average) {
        _ = sb.Append($" -mm {this.CaptureMeteringMode.ToString().ToLowerInvariant()}");
      }

      if(this.ImageRotation != CameraImageRotation.None) {
        _ = sb.Append($" -rot {((Int32)this.ImageRotation).ToString(Ci)}");
      }

      if(this.ImageFlipHorizontally) {
        _ = sb.Append(" -hf");
      }

      if(this.ImageFlipVertically) {
        _ = sb.Append(" -vf");
      }

      if(this.CaptureSensorRoi.IsDefault == false) {
        _ = sb.Append($" -roi {this.CaptureSensorRoi}");
      }

      if(this.CaptureShutterSpeedMicroseconds > 0) {
        _ = sb.Append($" -ss {this.CaptureShutterSpeedMicroseconds.Clamp(0, 6000000).ToString(Ci)}");
      }

      if(this.CaptureDynamicRangeCompensation != CameraDynamicRangeCompensation.Off) {
        _ = sb.Append($" -drc {this.CaptureDynamicRangeCompensation.ToString().ToLowerInvariant()}");
      }

      if(this.CaptureWhiteBalanceControl == CameraWhiteBalanceMode.Off && (this.CaptureWhiteBalanceGainBlue != 0M || this.CaptureWhiteBalanceGainRed != 0M)) {
        _ = sb.Append($" -awbg {this.CaptureWhiteBalanceGainBlue.ToString(Ci)},{this.CaptureWhiteBalanceGainRed.ToString(Ci)}");
      }

      if(this.ImageAnnotationFontSize > 0) {
        _ = sb.Append($" -ae {this.ImageAnnotationFontSize.Clamp(6, 160).ToString(Ci)}");
        _ = sb.Append($",{(this.ImageAnnotationFontColor == null ? "0xff" : this.ImageAnnotationFontColor.ToYuvHex(true))}");

        if(this.ImageAnnotationBackground != null) {
          this.ImageAnnotations |= CameraAnnotation.SolidBackground;
          _ = sb.Append($",{this.ImageAnnotationBackground.ToYuvHex(true)}");
        }
      }

      if(this.ImageAnnotations != CameraAnnotation.None) {
        _ = sb.Append($" -a {((Int32)this.ImageAnnotations).ToString(Ci)}");
      }

      if(String.IsNullOrWhiteSpace(this.ImageAnnotationsText) == false) {
        _ = sb.Append($" -a \"{this.ImageAnnotationsText.Replace("\"", "'")}\"");
      }

      return sb.ToString();
    }

    #endregion
  }
}