using System;
using System.IO;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Collections.Generic;
using System.Xml.Serialization;

using Spherical;
using Spherical.IO;
using Spherical.Visualizer;
using STC;

[WebService(Namespace = "http://voservices.net/region",
   Description = "These XML Web services provide functionality to deal with regions on the sky, i.e. complex footprints of astronomical surveys. The SOAP messages sent and received by this service are following the IVOA Space-Time Coordinate (STC) schema. Please send comments, suggestions and bug-reports to <i>vo-help@skysrv.pha.jhu.edu!</i><br>Written by Tamas Budavari, 2005-2006<br><br><i>Conforms to WS-I Basic Profile 1.1</i><br><br><b>Note:</b><br><i>This preview of the new framework is guaranteed to change until the STC schema evolves into an IVOA standard.</i>")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
//[SoapRpcService()]
public class StcService : System.Web.Services.WebService
{

    #region Test methods ...

    [WebMethod(Description =
        "Testing... Method supports HTTP-GET")]
    public regionAreaType test_area()
    {
        STCRegion rt = Adapter.StcFromRegion(Samples.SdssDr4());
        return this.Area(rt);
    }

    [WebMethod(Description =
        "Testing... Method supports HTTP-GET")]
    public STCRegion test_diff()
    {
        STCRegion rt = Adapter.StcFromRegion(new Region(0,0,60));
        STCRegion rt2 = Adapter.StcFromRegion(new Region(0, 0, 30));
        return this.Difference(rt, rt2);
    }

    [WebMethod(Description = 
        "Testing... Method supports HTTP-GET")]
    public STCRegion test_circle()
    {
        STCRegion rt = Adapter.StcFromRegion(Samples.SdssDr4());
        return this.BoundingCircle(rt);
    }

    [WebMethod(Description =
        "Testing... Method supports HTTP-GET")]
    public STCRegion test_circles()
    {
        STCRegion rt = Adapter.StcFromRegion(Samples.SdssDr4());
        return this.BoundingCircles(rt);
    }

    [WebMethod(Description =
        "Testing... Method supports HTTP-GET")]
    public STCRegion test_poly()
    {
        STCRegion rt = Adapter.StcFromRegion(Samples.SdssDr4());
        return this.ToPolygon(rt);
    }

    [WebMethod(Description =
        "Testing... Method supports HTTP-GET")]
    public STCRegion test_region()
    {
        return Adapter.StcFromRegion(Samples.SdssDr4());
    }

    [WebMethod(Description =
        "Testing... Method supports HTTP-GET")]
    public STCCoordinate test_coords()
    {
        return Adapter.StcFromCartesian(new Cartesian(0,0));
    }

    #endregion

    [WebMethod (Description = 
        "Convert a generic region description into the halfspace-convex-region representation.<br><b>Input:</b> region<br><b>Output:</b> region (union)")]
    public STCRegion ToHalfspace(STCRegion rt)
    {
        return Adapter.StcFromRegion(Adapter.ToRegion(rt));
    }

    [WebMethod (Description = 
        "Simplify a redundant region description<br><b>Input:</b> region<br><b>Output:</b> region")]
    public STCRegion Simplify(STCRegion rt)
    {
        Region r = Adapter.ToRegion(rt);
        r.Simplify();
        return Adapter.StcFromRegion(r);
    }

    [WebMethod (Description =
        "Convert any region to set of polygons defined by vertices. Also return the area of the region and the individual polygons in the optional area field.<br><b>Input:</b> region<br><b>Output:</b> region")]
    public STCRegion ToPolygon(STCRegion rt)
    {
        Region r = Adapter.ToRegion(rt);
        r.Simplify();
        int np = r.NumberOfPatches();

        unionType ut = new unionType();
        ut.Items = new regionType[np];
        int counter = 0;
        foreach (Convex c in r.ConvexList)
        {
            foreach (Patch p in c.PatchList)
            {
                polygonType pt = new polygonType();
                pt.Vertex = new vertexType[p.ArcList.Count];
                for (int i = 0; i < pt.Vertex.Length; i++)
                {
                    pt.Vertex[i] = new vertexType();
                    pt.Vertex[i].Position = Adapter.CenterFromCartesian(p.ArcList[i].Point2);
                    pt.Vertex[i].SmallCircle = new smallCircleType();
                    pt.Vertex[i].SmallCircle.Pole = Adapter.CenterFromCartesian(p.ArcList[i].Circle.Vector);
                }

                pt.Area = Adapter.FromArea(p.Area);
                // set
                ut.Items[counter++] = (regionType)pt;
            }
        }
        ut.Area = Adapter.FromArea(r.Area.Value);
        return Adapter.StcFromRegionType(ut);
    }

    [WebMethod(Description =
        "Grow a region by extending the boundaries by the given amount in all directions<br><b>Input1:</b> region<br><b>Input2:</b> degrees (double)<br><b>Output:</b> region")]
    public STCRegion Grow(STCRegion rt, double degrees)
    {
        Region r = Adapter.ToRegion(rt);
        r.Grow(degrees * 60);
        r.Simplify();
        return Adapter.StcFromRegion(r);
    }

    [WebMethod(Description =
        "Shrink a region by restricting the boundaries by the given amount in all directions - opposite of Grow()<br><b>Input1:</b> region<br><b>Input2:</b> degrees (double)<br><b>Output:</b> region")]
    public STCRegion Shrink(STCRegion rt, double degrees)
    {
        return this.Grow(rt, -degrees);
    }

    [WebMethod(Description =
        "Derive the union of two regions<br><b>Input1:</b> region<br><b>Input2:</b> region<br><b>Output:</b> region")]
    public STCRegion Union(STCRegion rt1, STCRegion rt2)
    {
        Region r1 = Adapter.ToRegion(rt1);
        Region r2 = Adapter.ToRegion(rt2);
        r1.Simplify();
        r2.Simplify();
        r1.Union(r2);
        r1.Simplify();
        return Adapter.StcFromRegion(r1);
    }

    [WebMethod(Description =
      "Derive the union of two regions grown by the given amounts<br><b>Input1:</b> region<br><b>Input2:</b> degrees (double)<br><b>Input3:</b> region<br><b>Input4:</b> degrees (double)<br><b>Output:</b> region")]
    public STCRegion UnionGrow(STCRegion rt1, double grow1deg, STCRegion rt2, double grow2deg)
    {
        Region r1 = Adapter.ToRegion(rt1);
        Region r2 = Adapter.ToRegion(rt2);
        r1.Grow(grow1deg * 60);  
        r1.Simplify();
        r2.Grow(grow2deg * 60);  
        r2.Simplify();
        r1.Union(r2);
        r1.Simplify();
        return Adapter.StcFromRegion(r1);
    }

    [WebMethod(Description =
        "Derive the intersection of two regions<br><b>Input1:</b> region<br><b>Input2:</b> region<br><b>Output:</b> region")]
    public STCRegion Intesection(STCRegion rt1, STCRegion rt2)
    {
        Region r1 = Adapter.ToRegion(rt1);
        Region r2 = Adapter.ToRegion(rt2);
        r1.Simplify();
        r2.Simplify();
        Region i = r1.Intersect(r2);
        i.Simplify();
        return Adapter.StcFromRegion(i);
    }

    [WebMethod(Description =
      "Derive the intersection of two regions grown by the given amounts<br><b>Input1:</b> region<br><b>Input2:</b> degrees (double)<br><b>Input3:</b> region<br><b>Input4:</b> degrees (double)<br><b>Output:</b> region")]
    public STCRegion IntesectionGrow(STCRegion rt1, double grow1deg, STCRegion rt2, double grow2deg)
    {
        Region r1 = Adapter.ToRegion(rt1);
        Region r2 = Adapter.ToRegion(rt2);
        r1.Grow(grow1deg * 60);
        r1.Simplify();
        r2.Grow(grow2deg * 60);
        r2.Simplify();
        Region i = r1.Intersect(r2);
        i.Simplify();
        return Adapter.StcFromRegion(i);
    }

    [WebMethod(Description =
        "Derive the difference of two regions<br><b>Input1:</b> region<br><b>Input2:</b> region<br><b>Output:</b> region")]
    public STCRegion Difference(STCRegion rt, STCRegion rt2)
    {
        Region r = Adapter.ToRegion(rt);
        Region r2 = Adapter.ToRegion(rt2);
        r.Simplify();
        r2.Simplify();
        foreach (Convex c in r2.ConvexList)
        {
            Region d = r.Difference(c);
            d.Simplify();
            r = d;
        }
        return Adapter.StcFromRegion(r);
    }

    [WebMethod(Description =
      "Derive the difference of two regions grown by the given amounts<br><b>Input1:</b> region<br><b>Input2:</b> degrees (double)<br><b>Input3:</b> region<br><b>Input4:</b> degrees (double)<br><b>Output:</b> region")]
    public STCRegion DifferenceGrow(STCRegion rt, double grow1deg, STCRegion rt2, double grow2deg)
    {
        Region r = Adapter.ToRegion(rt);
        r.Grow(grow1deg * 60);
        Region r2 = Adapter.ToRegion(rt2);
        r2.Grow(grow2deg * 60);
        r.Simplify();
        r2.Simplify();
        foreach (Convex c in r2.ConvexList)
        {
            Region d = r.Difference(c);
            d.Simplify();
            r = d;
        }
        return Adapter.StcFromRegion(r);
    }

#if false
    [WebMethod]
    public double FillingFactor(STCRegion rt)
    {
        return 1;
    }
#endif

    [WebMethod(Description =
        "Calculate the area of a given region<br><b>Input:</b> region<br><b>Output:</b> regionAreaType")]
    public regionAreaType Area(STCRegion rt)
    {
        Region r = Adapter.ToRegion(rt);
        r.Simplify();
        return Adapter.FromArea(r.Area.Value);
    }


    [WebMethod(Description =
        "Check to see if a point is in the given region.<br><b>Input1:</b> region<br><b>Input2:</b> point<br><b>Output:</b> boolean")]
    public bool ContainsPoint(STCRegion rt, STCCoordinate ct)
    {
        Region r = Adapter.ToRegion(rt);
        Cartesian c = Adapter.ToCartesian(ct);
        return r.Covers(c, 0);
    }

    [WebMethod(Description =
        "Check to see what the fractional overlap is of two regions. The intersection of the two regions is computed and the area ratio of the intersection and the 2nd region is returned.<br><b>Input1:</b> region<br><b>Input2:</b> shape (STCRegion)<br><b>Output:</b> ratio (double)")]
    public double ContainsRegion(STCRegion rt, STCRegion pt)
    {
        Region i, p;
        {
            Region r = Adapter.ToRegion(rt);
            p = Adapter.ToRegion(pt);
            r.Simplify();
            p.Simplify();
            i = r.Intersect(p);
            i.Simplify();
        }
        return i.Area.Value / p.Area.Value;
    }

    [WebMethod(Description =
        "Calculate a bounding circle for the given region.<br><b>Input:</b> region<br><b>Output:</b> region (circle)")]
    public STCRegion BoundingCircle(STCRegion rt)
    {
        Region r = Adapter.ToRegion(rt);
        r.Simplify();

        List<Cartesian> plist = new List<Cartesian>();
        foreach (Convex c in r.ConvexList)
            foreach (Patch p in c.PatchList)
                plist.Add(p.Mec.Vector);

        Cartesian center = Cartesian.CenterOfMass(plist, true);
        circleType ct = new circleType();
        ct.Radius = 0;
        foreach (Convex c in r.ConvexList)
        {
            foreach (Patch p in c.PatchList)
            {
                double radius = center.AngleInRadian(p.Mec.Vector) + p.Mec.RadiusInRadian;
                if (radius > ct.Radius) ct.Radius = radius;
            }
        }
        ct.Radius *= Const.Radian2Degree;
        ct.Center = Adapter.CenterFromCartesian(center);
        ct.unit = posUnitType.deg;
        return Adapter.StcFromRegionType(ct);
    }

    [WebMethod(Description =
        "Calculate bounding circles of individual patches of the given region.<br><b>Input:</b> region<br><b>Output:</b> region (union)")]
    public STCRegion BoundingCircles(STCRegion rt)
    {
        Region r = Adapter.ToRegion(rt);
        r.Simplify();

        // wrap it
        unionType ut = new unionType();
        ut.Items = new regionType[r.NumberOfPatches()];
        int i = 0;
        foreach (Convex c in r.ConvexList)
        {
            foreach (Patch p in c.PatchList)
            {
                circleType k = new circleType();
                k.unit = posUnitType.deg;
                k.Radius = p.Mec.RadiusInRadian * Const.Radian2Degree;
                k.Center = Adapter.CenterFromCartesian(p.Mec.Vector);
                ut.Items[i] = k;
                i++;
            }
        }
        return Adapter.StcFromRegionType(ut);
    }

    //[WebMethod]
    //public boxType BoundingBox(STCRegion rt)
    //{
    //    return new boxType();
    //}

    [WebMethod(Description =
      "Return a custom image showing the given region<br><b>Input1:</b> region<br><b>Input2:</b> center ra (double)<br><b>Input3:</b> center dec (double)<br><b>Input4:</b> pixel scale in '' (double)<br><b>Input5:</b> image width (int)<br><b>Input6:</b> image height (int)<br><b>Input7:</b> invert (boolean)<br><b>Input8:</b> image format (string)<br><b>Output:</b> image file (byte[])")]
    public byte[] ImageCustom(STCRegion rt, double ra, double dec, double pixscale,
        int width, int height, bool invert, string imageformat)
    {
        Region r = Adapter.ToRegion(rt);
        r.Intersect(new Halfspace(new Cartesian(ra, dec), -0.99));
        r.Simplify();

        Spherical.Visualizer.RegionCanvas canvas = new Spherical.Visualizer.RegionCanvas
            (ra, dec, "STR", width, height, 3600 / pixscale, invert);
        canvas.DrawRegion(r, System.Drawing.Color.Yellow, 1);

        return ImageBytes(canvas.Image, imageformat);
    }

    [WebMethod(Description =
        "Return a 512x512 GIF image showing the given region<br><b>Input:</b> region<br><b>Output:</b> gif file (byte[])")]
    public byte[] ImageGif(STCRegion rt)
    {
        // simplification, etc...
        Region r = Adapter.ToRegion(rt);
        r.Simplify();

        // center
        List<Cartesian> plist = new List<Cartesian>();
        foreach (Convex c in r.ConvexList)
            foreach (Patch p in c.PatchList)
                plist.Add(p.Mec.Vector);
        Cartesian center = Cartesian.CenterOfMass(plist, true);

        // 2nd simplify
        r.Intersect(new Halfspace(center, -0.99));
        r.Simplify(Const.Tolerance);

        // radius
        double max_radius = 0;
        foreach (Convex c in r.ConvexList)
        {
            foreach (Patch p in c.PatchList)
            {
                double radius = center.AngleInRadian(p.Mec.Vector) + p.Mec.RadiusInRadian;
                if (radius > max_radius) max_radius = radius;
            }
        }
        max_radius *= 1.2; // + 20% 
        if (max_radius >= Math.PI) max_radius = Math.PI - double.Epsilon;

        double scale = 2 * max_radius * Const.Radian2Degree * 3600 / 512;
        //double scale = 4*Math.Tan(max_radius/2) * Const.Radian2Degree * 3600 / 512;

        Spherical.Visualizer.RegionCanvas canvas = new Spherical.Visualizer.RegionCanvas
            (center.RA, center.Dec, "STR", 512, 512, 3600/scale, false);
        canvas.DrawRegion(r, System.Drawing.Color.Yellow, 1);

        return ImageBytes(canvas.Image, "gif");
    }

    /// <summary>
    /// Convert image to file format and return its byte array
    /// </summary>
    /// <param name="img"></param>
    /// <param name="imageformat"></param>
    /// <returns></returns>
    private byte[] ImageBytes(System.Drawing.Image img, string imageformat)
    {
        System.Drawing.Imaging.ImageFormat format;
        switch (imageformat.ToLower())
        {
            case "jpeg":
            case "jpg":
                format = System.Drawing.Imaging.ImageFormat.Jpeg;
                break;
            case "png":
                format = System.Drawing.Imaging.ImageFormat.Png;
                break;
            case "gif":
                format = System.Drawing.Imaging.ImageFormat.Gif;
                break;
            default:
                throw new ArgumentException
                    ("ImageBytes(): specified image format not supported");
        }
        MemoryStream mem = new MemoryStream();
        img.Save(mem, format);
        return mem.ToArray();
    }

}
