using System;

namespace OpenCvSharp
{
    // ReSharper disable InconsistentNaming

    /// <summary>
    /// The Base Class for Background/Foreground Segmentation.
    /// The class is only used to define the common interface for
    /// the whole family of background/foreground segmentation algorithms.
    /// </summary>
    public class BackgroundSubtractorMOG2 : BackgroundSubtractor
    {
        /// <summary>
        /// cv::Ptr&lt;T&gt;
        /// </summary>
        private Ptr? objectPtr;

        #region Init & Disposal

        /// <summary>
        /// Creates MOG2 Background Subtractor.
        /// </summary>
        /// <param name="history">Length of the history.</param>
        /// <param name="varThreshold">Threshold on the squared Mahalanobis distance between the pixel and the model
        /// to decide whether a pixel is well described by the background model. This parameter does not affect the background update.</param>
        /// <param name="detectShadows">If true, the algorithm will detect shadows and mark them. It decreases the speed a bit,
        /// so if you do not need this feature, set the parameter to false.</param>
        /// <returns></returns>
        public static BackgroundSubtractorMOG2 Create(
            int history = 500, double varThreshold = 16, bool detectShadows = true)
        {
            NativeMethods.HandleException(
                NativeMethods.video_createBackgroundSubtractorMOG2(
                    history, varThreshold, detectShadows ? 1 : 0, out var ptr));
            return new BackgroundSubtractorMOG2(ptr);
        }

        internal BackgroundSubtractorMOG2(IntPtr ptr)
        {
            objectPtr = new Ptr(ptr);
            this.ptr = objectPtr.Get(); 
        }

        /// <summary>
        /// Releases managed resources
        /// </summary>
        protected override void DisposeManaged()
        {
            objectPtr?.Dispose();
            objectPtr = null;
            ptr = IntPtr.Zero;
            base.DisposeManaged();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the number of last frames that affect the background model.
        /// </summary>
        public int History
        {
            get
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_getHistory(objectPtr.CvPtr, out var ret));
                GC.KeepAlive(this);
                return ret;
            }
            set
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_setHistory(objectPtr.CvPtr, value));
                GC.KeepAlive(this);
            }
        }

        /// <summary>
        /// Gets or sets the number of gaussian components in the background model.
        /// </summary>
        public int NMixtures
        {
            get
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_getNMixtures(objectPtr.CvPtr, out var ret));
                GC.KeepAlive(this);
                return ret;
            }
            set
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_setNMixtures(objectPtr.CvPtr, value));
                GC.KeepAlive(this);
            }
        }

        /// <summary>
        /// Gets or sets the "background ratio" parameter of the algorithm.
        /// If a foreground pixel keeps semi-constant value for about backgroundRatio\*history frames, it's
        /// considered background and added to the model as a center of a new component. It corresponds to TB
        /// parameter in the paper.
        /// </summary>
        public double BackgroundRatio
        {
            get
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_getBackgroundRatio(objectPtr.CvPtr, out var ret));
                GC.KeepAlive(this);
                return ret;
            }
            set
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_setBackgroundRatio(objectPtr.CvPtr, value));
                GC.KeepAlive(this);
            }
        }

        /// <summary>
        /// Gets or sets the variance threshold for the pixel-model match.
        /// The main threshold on the squared Mahalanobis distance to decide if the sample is well described by
        /// the background model or not. Related to Cthr from the paper.
        /// </summary>
        public double VarThreshold
        {
            get
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_getVarThreshold(objectPtr.CvPtr, out var ret));
                GC.KeepAlive(this);
                return ret;
            }
            set
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_setVarThreshold(objectPtr.CvPtr, value));
                GC.KeepAlive(this);
            }
        }

        /// <summary>
        /// Gets or sets the variance threshold for the pixel-model match used for new mixture component generation. 
        /// Threshold for the squared Mahalanobis distance that helps decide when a sample is close to the
        /// existing components (corresponds to Tg in the paper). If a pixel is not close to any component, it
        /// is considered foreground or added as a new component. 3 sigma =\> Tg=3\*3=9 is default. A smaller Tg
        /// value generates more components. A higher Tg value may result in a small number of components but they can grow too large.
        /// </summary>
        public double VarThresholdGen
        {
            get
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_getVarThresholdGen(objectPtr.CvPtr, out var ret));
                GC.KeepAlive(this);
                return ret;
            }
            set
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_setVarThresholdGen(objectPtr.CvPtr, value));
                GC.KeepAlive(this);
            }
        }

        /// <summary>
        /// Gets or sets the initial variance of each gaussian component.
        /// </summary>
        public double VarInit
        {
            get
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_getVarInit(objectPtr.CvPtr, out var ret));
                GC.KeepAlive(this);
                return ret;
            }
            set
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_setVarInit(objectPtr.CvPtr, value));
                GC.KeepAlive(this);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public double VarMin
        {
            get
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_getVarMin(objectPtr.CvPtr, out var ret));
                GC.KeepAlive(this);
                return ret;
            }
            set
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_setVarMin(objectPtr.CvPtr, value));
                GC.KeepAlive(this);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public double VarMax
        {
            get
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_getVarMax(objectPtr.CvPtr, out var ret));
                GC.KeepAlive(this);
                return ret;
            }
            set
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_setVarMax(objectPtr.CvPtr, value));
                GC.KeepAlive(this);
            }
        }

        /// <summary>
        /// Gets or sets the complexity reduction threshold.
        /// This parameter defines the number of samples needed to accept to prove the component exists. CT=0.05 
        /// is a default value for all the samples. By setting CT=0 you get an algorithm very similar to the standard Stauffer&amp;Grimson algorithm.
        /// </summary>
        public double ComplexityReductionThreshold
        {
            get
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_getComplexityReductionThreshold(objectPtr.CvPtr, out var ret));
                GC.KeepAlive(this);
                return ret;
            }
            set
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_setComplexityReductionThreshold(objectPtr.CvPtr, value));
                GC.KeepAlive(this);
            }
        }

        /// <summary>
        /// Gets or sets the shadow detection flag.
        /// If true, the algorithm detects shadows and marks them. See createBackgroundSubtractorKNN for details.
        /// </summary>
        public bool DetectShadows
        {
            get
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_getDetectShadows(objectPtr.CvPtr, out var ret));
                GC.KeepAlive(this);
                return ret != 0;
            }
            set
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_setDetectShadows(objectPtr.CvPtr, value ? 1 : 0));
                GC.KeepAlive(this);
            }
        }

        /// <summary>
        /// Gets or sets the shadow value.
        /// Shadow value is the value used to mark shadows in the foreground mask. Default value is 127.
        /// Value 0 in the mask always means background, 255 means foreground.
        /// </summary>
        public int ShadowValue
        {
            get
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_getShadowValue(objectPtr.CvPtr, out var ret));
                GC.KeepAlive(this);
                return ret;
            }
            set
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_setShadowValue(objectPtr.CvPtr, value));
                GC.KeepAlive(this);
            }
        }

        /// <summary>
        /// Gets or sets the shadow threshold. 
        /// A shadow is detected if pixel is a darker version of the background. The shadow threshold (Tau in
        /// the paper) is a threshold defining how much darker the shadow can be. Tau= 0.5 means that if a pixel
        /// is more than twice darker then it is not shadow. See Prati, Mikic, Trivedi and Cucchiara,
        /// *Detecting Moving Shadows...*, IEEE PAMI,2003.
        /// </summary>
        public double ShadowThreshold
        {
            get
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_getShadowThreshold(objectPtr.CvPtr, out var ret));
                GC.KeepAlive(this);
                return ret;
            }
            set
            {
                ThrowIfDisposed();
                if (objectPtr == null)
                    throw new NotSupportedException("objectPtr == null");
                NativeMethods.HandleException(
                    NativeMethods.video_BackgroundSubtractorMOG2_setShadowThreshold(objectPtr.CvPtr, value));
                GC.KeepAlive(this);
            }
        }

        #endregion

        internal class Ptr : OpenCvSharp.Ptr
        {
            public Ptr(IntPtr ptr) : base(ptr)
            {
            }

            public override IntPtr Get()
            {
                NativeMethods.HandleException(
                    NativeMethods.video_Ptr_BackgroundSubtractorMOG2_get(ptr, out var ret));
                GC.KeepAlive(this);
                return ret;
            }

            protected override void DisposeUnmanaged()
            {
                NativeMethods.HandleException(
                    NativeMethods.video_Ptr_BackgroundSubtractorMOG2_delete(ptr));
                base.DisposeUnmanaged();
            }
        }
    }
}