using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GeometricObjectsSolution
{
    [Serializable]
    public abstract class GeometricObject : IComparable, IComparable<GeometricObject>, INotifyPropertyChanged
    {
        #region Felder
        protected Point _Center = new Point();
        #endregion

        #region Ereignisse
        public event PropertyChangedEventHandler PropertyChanged;
        public event InvalidMeasureEventHandler InvalidMeasure;
        public event MovingEventHandler Moving;
        public event MovedEventHandler Moved;
        #endregion

        #region Konstruktor
        public GeometricObject() => CountGeometricObjects++;
        #endregion

        #region Eigenschaften
        private double _XCoordinate;
        public virtual double XCoordinate
        {
            get => _Center.X;
            set
            {
                _Center.X = value;
                OnPropertyChanged("XCoordinate");
            }
        }

        private double _YCoordinate;
        public virtual double YCoordinate
        {
            get => _Center.Y;
            set
            {
                _Center.Y = value;
                OnPropertyChanged("YCoordinate");
            }
        }
        #endregion

        #region Instanzmethoden

        public int CompareTo(GeometricObject geo)
        {
            if (geo == null) return 1;
            if (GetArea() > geo.GetArea()) return 1;
            if (GetArea() == geo.GetArea()) return 0;
            return -1;
        }

        public virtual int CompareTo(Object obj)
        {
            GeometricObject geoObject = obj as GeometricObject;
            if (geoObject != null)
            {
                if (GetArea() < geoObject.GetArea()) return -1;
                if (GetArea() == geoObject.GetArea()) return 0;
                return 1;
            }
            // Auslösen einer Ausnahme
            throw new ArgumentException("Es wird der Typ 'GeometricObject' erwartet.");
        }

        public virtual void Move(double dx, double dy)
        {
            MovingEventArgs e = new MovingEventArgs();
            OnMoving(e);
            if (e.Cancel == true) return;

            XCoordinate += dx;
            YCoordinate += dy;

            OnMoved(new EventArgs());
        }


        public virtual void Move(Point center)
        {
            MovingEventArgs e = new MovingEventArgs();
            OnMoving(e);
            if (e.Cancel == true) return;
            _Center = center;
            OnMoved(new EventArgs());
        }

        protected virtual void OnInvalidMeasure(InvalidMeasureEventArgs e)
        {
            if (InvalidMeasure != null)
                InvalidMeasure(this, e);
            else
                throw e.Error;
        }

        protected virtual void OnMoving(MovingEventArgs e) => Moving?.Invoke(this, e);
        
        protected virtual void OnMoved(EventArgs e) => Moved?.Invoke(this, e);

        protected virtual void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));        
        #endregion

        #region Abstrakte Methoden
        public abstract double GetArea();
        public abstract double GetPerimeter();
        #endregion

        #region Klasseneigenschaft
        public static int CountGeometricObjects { get; protected set; }
        #endregion

        #region Klassenmethode
        public static int Bigger(GeometricObject geo1, GeometricObject geo2)
        {
            if (geo1 == null && geo2 == null) return 0;
            if (geo1 == null) return -1;
            if (geo1 == null) return 1;
            if (geo1.GetArea() > geo2.GetArea()) return 1;
            if (geo1.GetArea() < geo2.GetArea()) return -1;
            return 0;
        }

        #endregion

        #region Operatorüberladungen
        public static bool operator >(GeometricObject geo1, GeometricObject geo2)
        {
            if (geo1 == null || geo2 == null)
                throw new InvalidOperationException();
            return geo1.GetArea() > geo2.GetArea() ? true : false;
        }

        public static bool operator <(GeometricObject geo1, GeometricObject geo2)
        {
            if (geo1 == null || geo2 == null)
                throw new InvalidOperationException();
            return geo1.GetArea() < geo2.GetArea() ? true : false;
        }

        public static bool operator >=(GeometricObject geo1, GeometricObject geo2)
        {
            if (geo1 == null || geo2 == null)
                throw new InvalidOperationException();
            return geo1.GetArea() >= geo2.GetArea() ? true : false;
        }

        public static bool operator <=(GeometricObject geo1, GeometricObject geo2)
        {
            if (geo1 == null || geo2 == null)
                throw new InvalidOperationException();
            return geo1.GetArea() <= geo2.GetArea() ? true : false;
        }

        public static bool operator ==(GeometricObject geo1, GeometricObject geo2)
        {
            if (ReferenceEquals(geo1, geo2)) return true;
            if (ReferenceEquals(geo1, null)) return false;
            return geo1.Equals(geo2);
        }

        public override bool Equals(object obj)
        {
            if (obj == null) return false;
            if (GetArea() == ((GeometricObject)obj).GetArea()) return true;
            return false;
        }

        public static bool operator !=(GeometricObject obj1, GeometricObject obj2) => !(obj1 == obj2);

        public override int GetHashCode() => base.GetHashCode();
        #endregion

    }
}
