using System;
using System.Collections.Generic;
using System.Text;

using Swan;

namespace Unosquare.RaspberryIO.Camera {
  /// <summary>
  /// Defines a wrapper for the raspistill program and its settings (command-line arguments).
  /// </summary>
  /// <seealso cref="CameraSettingsBase" />
  public class CameraStillSettings : CameraSettingsBase {
    private Int32 _rotate;

    /// <inheritdoc />
    public override String CommandName => "raspistill";

    /// <summary>
    /// Gets or sets a value indicating whether the preview window (if enabled) uses native capture resolution
    /// This may slow down preview FPS.
    /// </summary>
    public Boolean CaptureDisplayPreviewAtResolution { get; set; } = false;

    /// <summary>
    /// Gets or sets the encoding format the hardware will use for the output.
    /// </summary>
    public CameraImageEncodingFormat CaptureEncoding { get; set; } = CameraImageEncodingFormat.Jpg;

    /// <summary>
    /// Gets or sets the quality for JPEG only encoding mode.
    /// Value ranges from 0 to 100.
    /// </summary>
    public Int32 CaptureJpegQuality { get; set; } = 90;

    /// <summary>
    /// Gets or sets a value indicating whether the JPEG encoder should add raw bayer metadata.
    /// </summary>
    public Boolean CaptureJpegIncludeRawBayerMetadata { get; set; } = false;

    /// <summary>
    /// JPEG EXIF data
    /// Keys and values must be already properly escaped. Otherwise the command will fail.
    /// </summary>
    public Dictionary<String, String> CaptureJpegExtendedInfo { get; } = new Dictionary<String, String>();

    /// <summary>
    /// Gets or sets a value indicating whether [horizontal flip].
    /// </summary>
    /// <value>
    ///   <c>true</c> if [horizontal flip]; otherwise, <c>false</c>.
    /// </value>
    public Boolean HorizontalFlip { get; set; } = false;

    /// <summary>
    /// Gets or sets a value indicating whether [vertical flip].
    /// </summary>
    /// <value>
    ///   <c>true</c> if [vertical flip]; otherwise, <c>false</c>.
    /// </value>
    public Boolean VerticalFlip { get; set; } = false;

    /// <summary>
    /// Gets or sets the rotation.
    /// </summary>
    /// <exception cref="ArgumentOutOfRangeException">Valid range 0-359.</exception>
    public Int32 Rotation {
      get => this._rotate;
      set {
        if(value < 0 || value > 359) {
          throw new ArgumentOutOfRangeException(nameof(value), "Valid range 0-359");
        }

        this._rotate = value;
      }
    }

    /// <inheritdoc />
    public override String CreateProcessArguments() {
      StringBuilder sb = new StringBuilder(base.CreateProcessArguments());
      _ = sb.Append($" -e {this.CaptureEncoding.ToString().ToLowerInvariant()}");

      // JPEG Encoder specific arguments
      if(this.CaptureEncoding == CameraImageEncodingFormat.Jpg) {
        _ = sb.Append($" -q {this.CaptureJpegQuality.Clamp(0, 100).ToString(Ci)}");

        if(this.CaptureJpegIncludeRawBayerMetadata) {
          _ = sb.Append(" -r");
        }

        // JPEG EXIF data
        if(this.CaptureJpegExtendedInfo.Count > 0) {
          foreach(KeyValuePair<String, String> kvp in this.CaptureJpegExtendedInfo) {
            if(String.IsNullOrWhiteSpace(kvp.Key) || String.IsNullOrWhiteSpace(kvp.Value)) {
              continue;
            }

            _ = sb.Append($" -x \"{kvp.Key.Replace("\"", "'")}={kvp.Value.Replace("\"", "'")}\"");
          }
        }
      }

      // Display preview settings
      if(this.CaptureDisplayPreview && this.CaptureDisplayPreviewAtResolution) {
        _ = sb.Append(" -fp");
      }

      if(this.Rotation != 0) {
        _ = sb.Append($" -rot {this.Rotation}");
      }

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

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

      return sb.ToString();
    }
  }
}