using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

using Spherical;

namespace STC
{
    [CLSCompliant(true)]
    public static class Adapter
    {
        /*
        public static readonly string FK5 = "FK5";
        public static readonly string ICRS = "ICRS";
        public static readonly string[] SupportedCoordSys = new string[] { Adapter.FK5, Adapter.ICRS };
        */

        #region XML conversions ...

        //public static string ToXml(regionType rt)
        //{
        //    XmlSerializer x = new XmlSerializer(rt.GetType());
        //    StringWriter w = new StringWriter();
        //    x.Serialize(w, rt);
        //    string s = w.ToString();
        //    return s;
        //}

        public static string ToXml(STCRegion rt)
        {
            XmlSerializer x = new XmlSerializer(rt.GetType());
            StringWriter w = new StringWriter();
            x.Serialize(w, rt);
            string s = w.ToString();
            return s;
        }

        //public static regionType XmlToRegionType(string xml)
        //{
        //    StringReader srdr = new StringReader(xml);
        //    XmlTextReader rdr = new XmlTextReader(srdr);
        //    XmlSerializer ser = new XmlSerializer(typeof(unionType));
        //    object o = ser.Deserialize(rdr);
        //    regionType rt = o as regionType;
        //    return rt;
        //}

        public static STCRegion XmlToStcRegion(string xml)
        {
            StringReader srdr = new StringReader(xml);
            XmlTextReader rdr = new XmlTextReader(srdr);
            XmlSerializer ser = new XmlSerializer(typeof(STCRegion));
            object o = ser.Deserialize(rdr);
            STCRegion rt = o as STCRegion;
            return rt;
        }

        #endregion
        #region Convert STC things to Cartesian ...

        public static Cartesian ToCartesian(double2Type d2)
        {
            if (d2.unitSpecified && d2.unit != posUnitType.deg)
                throw new ArgumentException
                    ("Adapter.ToCartesian(double2Type): unit specified but not degree");
            Cartesian c = new Cartesian(d2.C1.Value, d2.C2.Value);
            return c;
        }

        public static Cartesian ToCartesian(double3Type d3)
        {
            if (d3.unitSpecified)
                throw new ArgumentException
                    ("Adapter.ToCartesian(double3Type): unit specified");
            Cartesian c = new Cartesian(d3.C1.Value, d3.C2.Value, d3.C3.Value, true);
            return c;
        }

        public static Cartesian ToCartesian(astroCoordsType ct)
        {
            string typename = ct.Item.GetType().Name;
            switch (typename)
            {
                case "posVector2CoordinateType":
                    posVector2CoordinateType p2 = ct.Item as posVector2CoordinateType;
                    if (p2.Item.GetType() != typeof(double2Type))
                        throw new ArgumentException
                            ("ToCartesian(astroCoordsType): not a double2Type in posVector2CoordinateType");
                    double2Type d2 = p2.Item as double2Type;
                    return ToCartesian(d2);

                case "posVector3CoordinateType":
                    posVector3CoordinateType p3 = ct.Item as posVector3CoordinateType;
                    if (p3.Item.GetType() != typeof(double3Type))
                        throw new ArgumentException
                            ("ToCartesian(astroCoordsType): not a double3Type in posVector3CoordinateType");
                    double3Type d3 = p3.Item as double3Type;
                    return ToCartesian(d3);

                default:
                    throw new ArgumentException
                        (String.Format("ToCartesian(astroCoordsType): type '{0}' not supported", typename));
            }
        }

        public static Cartesian ToCartesian(STCCoordinate sct)
        {
            if (sct.AstroCoordSystem.id != sct.AstroCoords.coord_system_id)
                throw new ArgumentException("Adapter.ToCartesian(stc): different coordsys ids");
            if (sct.AstroCoordSystem.href != @"ivo://STClib/CoordSys#FK5-UTC-TOPO")
                throw new ArgumentException("Adapter.ToCartesian(stc): coordsys not supported");
            return Adapter.ToCartesian(sct.AstroCoords);
        }

        #endregion
        #region Convert STC things to Halfspace, Convex and Region ...

        public static Halfspace ToHalfspace(halfspaceType ht)
        {
            return new Halfspace
                (ht.Vector.C1.Value, ht.Vector.C2.Value, ht.Vector.C3.Value, 
                true, ht.Offset);
        }

        public static Convex ToConvex(convexType ct)
        {
            Convex c = new Convex(ct.Halfspace.Length);
            foreach (halfspaceType ht in ct.Halfspace)
                c.Add(Adapter.ToHalfspace(ht));
            return c;
        }

        public static Region ToRegion(regionType rt)
        {
            string typename = rt.GetType().Name;

            switch (typename)
            {
                case "allSkyType":
                    return new Region(new Halfspace(new Cartesian(0, 0, 1, false), -1));

                case "circleType":
                    circleType kt = (circleType)rt;
                    double cos0;
                    switch (kt.unit)
                    {
                        case posUnitType.deg:
                            cos0 = Math.Cos(kt.Radius * Const.Degree2Radian);
                            break;
                        case posUnitType.arcmin:
                            cos0 = Math.Cos(kt.Radius * Const.Arcmin2Radian);
                            break;
                        case posUnitType.arcsec:
                            cos0 = Math.Cos(kt.Radius * Const.Arcmin2Radian / 60);
                            break;
                        default:
                            throw new ArgumentException
                                ("ToRegion(circleType): unit not supported");
                    }
                    Halfspace h = new Halfspace(Adapter.ToCartesian(kt.Center), cos0);
                    return new Region(h);

                case "convexType":
                    convexType ct = (convexType)rt;
                    return new Region(Adapter.ToConvex(ct), false);

                case "diffType":
                    diffType dt = (diffType)rt;
                    if (dt.Item1.GetType().Name != "convexType")
                        throw new ArgumentException
                            ("Adapter.Region(diffType): region2 in diff is not a convex");
                    Region r1 = Adapter.ToRegion(dt.Item);
                    Convex c2 = Adapter.ToConvex((convexType)dt.Item1);
                    return r1.Difference(c2);

                case "intersectionType":
                    intersectionType it = (intersectionType)rt;
                    Region i = new Region(new Halfspace(new Cartesian(0, 0, 1, false), -1));
                    foreach (regionType t in it.Items)
                    {
                        Region rr = i.Intersect(Adapter.ToRegion(t));
                        rr.SimpleSimplify(Const.Tolerance);
                        i = rr;
                    }
                    return i;

                case "unionType":
                    unionType ut = (unionType)rt;
                    Region u = new Region();
                    foreach (regionType t in ut.Items)
                    {
                        u.Union(Adapter.ToRegion(t));
                    }
                    return u;

                default:
                    throw new NotSupportedException (String.Format
                        ("Adapter.Region(): type '{0}' is not supported", typename));
            }
        }

        public static Region ToRegion(STCRegion srt)
        {
            // check if known coordsys and consistency
            if (srt.AstroCoordSystem.id != srt.Item.coord_system_id)
                throw new ArgumentException("Adapter.ToRegion(stc): not the same coordsys ids");
            if (srt.AstroCoordSystem.href != @"ivo://STClib/CoordSys#FK5-UTC-TOPO")
                throw new ArgumentException("Adapter.ToRegion(stc): coordsys not supported");
            return Adapter.ToRegion(srt.Item);
        }

        #endregion
        #region Convert Cartesian to STC things ....

        /// <summary>
        /// Create a double2Type
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        public static double2Type CenterFromCartesian(Cartesian c)
        {
            double2Type tt = new double2Type();
            tt.unit = posUnitType.deg;
            tt.unitSpecified = true;
            tt.C1 = new double1Type();
            tt.C2 = new double1Type();
            double ra, dec;
            Cartesian.Cartesian2Radec(c, out ra, out dec);
            tt.C1.Value = ra;
            tt.C2.Value = dec;
            return tt;
        }

        /// <summary>
        /// Create an astroCoordsType
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        public static astroCoordsType FromCartesian(Cartesian c)
        {
            astroCoordsType ct = new astroCoordsType();
            posVector2CoordinateType p2 = new posVector2CoordinateType();
            p2.Item = Adapter.CenterFromCartesian(c);
            ct.Item = p2;
            return ct;
        }

        public static STCCoordinate StcFromCartesian(Cartesian c)
        {
            double2Type d2 = Adapter.CenterFromCartesian(c);
            d2.unitSpecified = true;
            d2.unit = posUnitType.deg;

            posVector2CoordinateType p2 = new posVector2CoordinateType();
            p2.Item = d2;

            astroCoordsType ct = new astroCoordsType();
            ct.Item = p2;

            STCCoordinate res = new STCCoordinate();
            res.AstroCoordSystem = Adapter.CoordSysFK5();

            ct.coord_system_id = res.AstroCoordSystem.id;
            res.AstroCoords = ct;
            return res;
        }

        #endregion
        #region Convert Halfspace, Convex and Region to STC ...

        /// <summary>
        /// Create STC.halfspaceType from SphericaLib.Halfspace
        /// </summary>
        /// <param name="h"></param>
        /// <returns></returns>
        public static halfspaceType FromHalfspace(Halfspace h)
        {
            halfspaceType ht = new halfspaceType();
            ht.Vector = new double3Type();
            ht.Vector.C1 = new double1Type();  ht.Vector.C1.Value = h.Vector.X;
            ht.Vector.C2 = new double1Type();  ht.Vector.C2.Value = h.Vector.Y;
            ht.Vector.C3 = new double1Type();  ht.Vector.C3.Value = h.Vector.Z;
            ht.Offset = h.Cos0;
            return ht;
        }

        /// <summary>
        /// Create STC.convexType from SphericalLib.Convex
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        public static convexType FromConvex(Convex c)
        {
            convexType ct = new convexType();
            ct.Halfspace = new halfspaceType[c.HalfspaceList.Count];
            for (int i=0; i<ct.Halfspace.Length; i++)
                ct.Halfspace[i] = Adapter.FromHalfspace(c.HalfspaceList[i]);
            if (c.Simplified)
                ct.Area = Adapter.FromArea(c.Area.Value);
            return ct;
        }

        /// <summary>
        /// Create STC.regionType from SphericalLib.Region
        /// </summary>
        /// <param name="r"></param>
        /// <returns></returns>
        public static regionType FromRegion(Region r)
        {
            unionType ut = new unionType();
            ut.Items = new regionType[r.ConvexList.Count];
            for (int i = 0; i < ut.Items.Length; i++)
                ut.Items[i] = Adapter.FromConvex(r.ConvexList[i]);
            try { ut.Area = Adapter.FromArea(r.Area.Value); }
            catch { }
            return (regionType)ut;
        }

        /// <summary>
        /// Return a coordinate system to be used in STCRegion
        /// </summary>
        /// <returns></returns>
        public static astroCoordSystemType CoordSysFK5()
        {
            astroCoordSystemType cs = new astroCoordSystemType();
            cs.id = "FK5-UTC-TOPO";
            cs.href = @"ivo://STClib/CoordSys#FK5-UTC-TOPO";
            return cs;
        }

        /// <summary>
        /// Return a properly wrapped region
        /// </summary>
        /// <param name="r"></param>
        /// <returns></returns>
        public static STCRegion StcFromRegion(Region r)
        {
            return Adapter.StcFromRegion(r,1f);
        }

        public static STCRegion StcFromRegion(Region r, float fill_factor)
        {
            regionType rt = Adapter.FromRegion(r);
            rt.fill_factor = fill_factor;
            return Adapter.StcFromRegionType(rt);
        }

        public static STCRegion StcFromRegionType(regionType rt)
        {
            STCRegion sreg = new STCRegion();
            sreg.AstroCoordSystem = Adapter.CoordSysFK5();
            sreg.Item = rt;
            sreg.Item.coord_system_id = sreg.AstroCoordSystem.id;
            return sreg;
        }

        #endregion

        /// <summary>
        /// Create STC.regionAreaType from double area in sq deg
        /// </summary>
        /// <param name="area"></param>
        /// <returns></returns>
        public static regionAreaType FromArea(double area)
        {
            regionAreaType at = new regionAreaType();
            at.linearAreaUnit = posUnitType.deg;
            at.validArea = true;
            at.Value = area;
            return at;
        }
    }
}
