using System;
using System.Collections.Generic;
using CrossCutting;

namespace InterpreterLibrary.Models.Transform
{
    public static class AffineTransform
    {
        public static Registration SetupTransformation(IEnumerable<Tuple<PointF, PointF>> pointPairs)
        {
            var k = 0;
            float m_xa = 0f, m_ya = 0f, m_xb = 0f, m_yb = 0f, lAPlusB = 0f, lAMinusB = 0f, lA = 0f;
            foreach (var pair in pointPairs)
            {
                m_xa += pair.Item1.X;
                m_ya += pair.Item1.Y;
                m_xb += pair.Item2.X;
                m_yb += pair.Item2.Y;
                lAPlusB += pair.Item1.X * pair.Item2.X + pair.Item1.Y * pair.Item2.Y;
                lAMinusB += pair.Item1.X * pair.Item2.Y - pair.Item1.Y * pair.Item2.X;
                lA += (float) (Math.Pow(pair.Item1.X, 2) + Math.Pow(pair.Item1.Y, 2));
                ++k;
            }
            var det = (float) (k * lA - Math.Pow(m_xa, 2) - Math.Pow(m_ya, 2));
            var r = new Registration
            {
                tx = (lA * m_xb - m_xa * lAPlusB + m_ya * lAMinusB) / det,
                ty = (lA * m_yb - m_ya * lAPlusB - m_xa * lAMinusB) / det,
                sct = (-m_xa * m_xb - m_ya * m_yb + k * lAPlusB) / det,
                sst = (m_ya * m_xb - m_xa * m_yb + k * lAMinusB) / det
            };
            return r;
        }

        
        public static IEnumerable<PointF> Transform(List<PointF> A, Registration r)
        {
            var T = new List<PointF>();
            foreach (var a in A)
            {
                var t = new PointF
                (
                    r.tx + a.X * r.sct - a.Y * r.sst,
                    r.ty + a.Y * r.sct + a.X * r.sst
                );
                T.Add(t);
            }
            return T;
        }

        public static PointF Transform(PointF a, Registration r)
        {
            return new PointF
            (
                r.tx + a.X * r.sct - a.Y * r.sst,
                r.ty + a.Y * r.sct + a.X * r.sst
            );
        }

        public static PointF Transform(System.Drawing.PointF a, Registration r)
        {
            return new PointF
            (
                r.tx + a.X * r.sct - a.Y * r.sst,
                r.ty + a.Y * r.sct + a.X * r.sst
            );
        }

        
        public static double E(IList<PointF> A, IList<PointF> B)
        {
            var k = A.Count;
            double e = 0;
            for (var i = 0; i < k; i++)
                e += Math.Sqrt(Math.Pow(A[i].X - B[i].X, 2) + Math.Pow(A[i].Y - B[i].Y, 2));
            return e;
        }

        
        public class Registration
        {
            public float tx { get; internal set; }
            public float ty { get; internal set; }
            public float sct { get; internal set; }
            public float sst { get; internal set; }


            
            private double getScaling()
            {
                return Math.Sqrt(Math.Pow(sct, 2) + Math.Pow(sst, 2));
            }

            
            private float getRotation()
            {
                const double RadiansToDegrees = 57.2957795;
                return (float) (RadiansToDegrees * Math.Acos(sct / getScaling()));
            }

            public override string ToString()
            {
                return $"{tx},{ty},{getScaling()},{getRotation()}";
            }
        }
    }
}
