using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Collections.ObjectModel;
using ThinkGeo.MapSuite.Core;
using ThinkGeo.MapSuite.DesktopEdition;

namespace PacificRim
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            winformsMap1.MapUnit = GeographyUnit.DecimalDegree;

            // We are loading twice the same shapefile. Once unprojected and a second time projecting
            //all the features 360 to the left.
            ShapeFileFeatureLayer worldLayer = new ShapeFileFeatureLayer(@"..\..\Data\cntry02.shp");
            worldLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle = AreaStyles.Country1;
            worldLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
            winformsMap1.StaticOverlay.Layers.Add("WorldLayer", worldLayer);
          
            OffsetProjection offsetProj = new OffsetProjection();

            //Run this code only once to build the spatial index for the projected layer.
            //worldLayer.Open();
            //Collection<Feature> features = worldLayer.QueryTools.GetAllFeatures(ReturningColumnsType.NoColumns);
            //worldLayer.Close();
            //ShapeFileFeatureLayer.BuildIndexFile(features, @"..\..\Data\cntry02_offset.idx", BuildIndexMode.DoNotRebuild);
            
            //Loads the country layer with the Projection offsetting all the feature 360 to the left.
            ShapeFileFeatureLayer worldLayerOffset = new ShapeFileFeatureLayer(@"..\..\Data\cntry02.shp", @"..\..\Data\cntry02_offset.idx");
            worldLayerOffset.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle = AreaStyles.Country1;
            worldLayerOffset.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
            worldLayerOffset.FeatureSource.Projection = offsetProj;
            winformsMap1.StaticOverlay.Layers.Add("World Offset", worldLayerOffset);

            //Logic for displaying InMemoryFeatureLayers for cities and the Great Circle between them.
            LoadCityFeatures(offsetProj);

            winformsMap1.MapBackground.BackgroundBrush = new GeoSolidBrush(GeoColor.GeographicColors.ShallowOcean);

            //Notice that the current extent has to be expressed in the projected values to have the map
            //centered on the Pacific.
            //80 - 360 = -280
            winformsMap1.CurrentExtent = new RectangleShape(-280, 58, -70, -20);
            winformsMap1.Refresh();

        }

        private void LoadCityFeatures(OffsetProjection offsetProj)
        {
            InMemoryFeatureLayer cityPoints = new InMemoryFeatureLayer();
            cityPoints.Columns.Add(new FeatureSourceColumn("LabelName"));
            cityPoints.ZoomLevelSet.ZoomLevel01.DefaultPointStyle = PointStyles.City1;
            cityPoints.ZoomLevelSet.ZoomLevel01.DefaultTextStyle = new TextStyle("LabelName", new GeoFont("Arial", 10), new GeoSolidBrush(GeoColor.SimpleColors.Black));
            cityPoints.ZoomLevelSet.ZoomLevel01.DefaultTextStyle.XOffsetInPixel = 15;
            cityPoints.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

            winformsMap1.DynamicOverlay.Layers.Add("City Points", cityPoints);

            //Display the feature for Los Angeles as is (without projection) because it is on the unprojected layer.
            PointShape LA_Point = new PointShape(-118.55, 34.08);
            Feature LA_feature = new Feature(LA_Point);
            LA_feature.ColumnValues.Add("LabelName", "Los Angeles");
            cityPoints.InternalFeatures.Add("LA",LA_feature);

            //Apply the projection to the feature representing Shanghai because it on the projected layer.
            offsetProj.Open();
            PointShape Shanghai_Point = new PointShape(offsetProj.ConvertToExternalProjection(121.78, 31.01));
            offsetProj.Close();

            Feature Shanghai_feature = new Feature(Shanghai_Point);
            Shanghai_feature.ColumnValues.Add("LabelName", "Shanghai");
            cityPoints.InternalFeatures.Add("Shanghai",Shanghai_feature);

            InMemoryFeatureLayer GreatCircle = new InMemoryFeatureLayer();
            GreatCircle.ZoomLevelSet.ZoomLevel01.DefaultLineStyle = LineStyles.CreateSimpleLineStyle(GeoColor.SimpleColors.Green, 3, true);
            GreatCircle.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

            winformsMap1.DynamicOverlay.Layers.Add("Great Circle", GreatCircle);

            //The great circle requires to have the part being in the eastern hemisphere to be offset 360 to the left
            // so that it display on the projected layer.
            MultilineShape greatCircleMultiLineShape = GetGreatCircle(LA_Point,Shanghai_Point,offsetProj);
            Feature GreatCirclefeature = new Feature(greatCircleMultiLineShape);

            GreatCircle.InternalFeatures.Add("Great Circle",GreatCirclefeature);

        }

        private MultilineShape GetGreatCircle(PointShape pointShape1, PointShape pointShape2, OffsetProjection offsetProj)
        {
            offsetProj.Open();
            if (pointShape1.X < -180 )
            {
                pointShape1 = new PointShape(offsetProj.ConvertToInternalProjection(pointShape1.X, pointShape1.Y));
            }
            if (pointShape2.X < -180)
            {
                pointShape2 = new PointShape(offsetProj.ConvertToInternalProjection(pointShape2.X, pointShape2.Y));
            }
           
            MultilineShape greatCircleMultiLineShape = pointShape1.GetShortestLineTo(pointShape2, winformsMap1.MapUnit);

            MultilineShape resultGreatMultiLineShape = new MultilineShape();
            foreach (LineShape lineShape in greatCircleMultiLineShape.Lines)
            {
                if (lineShape.Vertices[0].X < 0)
                {
                    resultGreatMultiLineShape.Lines.Add(lineShape);
                }
                else
                {
                    LineShape resultLineShape = new LineShape();
                    foreach (Vertex vertex in lineShape.Vertices)
                    {
                        Vertex projVertex = offsetProj.ConvertToExternalProjection(vertex.X, vertex.Y);
                        resultLineShape.Vertices.Add(projVertex);
                    }
                    resultGreatMultiLineShape.Lines.Add(resultLineShape);
                }
            }

            return resultGreatMultiLineShape;
        }

        private void winformsMap1_MouseMove(object sender, MouseEventArgs e)
        {
            //To have the correct Longitude and Latitude for the mouse pointer.
            PointShape WorldPointShape = ExtentHelper.ToWorldCoordinate(winformsMap1.CurrentExtent, e.X, e.Y, winformsMap1.Width, winformsMap1.Height);
            if (WorldPointShape.X > -180)
                {
                    toolStripStatusLabel1.Text = @"World X: " + String.Format("{0:00.0000}", Math.Round(WorldPointShape.X, 4));
                    toolStripStatusLabel2.Text = @"World Y: " + String.Format("{0:00.0000}", Math.Round(WorldPointShape.Y, 4));
                    toolStripStatusLabel3.Text = @"Long: " + DecimalDegreesHelper.GetDegreesMinutesSecondsStringFromDecimalDegree(WorldPointShape.X);
                    toolStripStatusLabel4.Text = @"Lat: " + DecimalDegreesHelper.GetDegreesMinutesSecondsStringFromDecimalDegree(WorldPointShape.Y);

                }
                else
                {
                    OffsetProjection offsetProj = new OffsetProjection();
                    offsetProj.Open();
                    Vertex myVertex = offsetProj.ConvertToInternalProjection(WorldPointShape.X, WorldPointShape.Y);
                    toolStripStatusLabel1.Text = @"World X: " + String.Format("{0:00.0000}", Math.Round(myVertex.X, 4));
                    toolStripStatusLabel2.Text = @"World Y: " + String.Format("{0:00.0000}", Math.Round(myVertex.Y, 4));
                    toolStripStatusLabel3.Text = @"Long: " + DecimalDegreesHelper.GetDegreesMinutesSecondsStringFromDecimalDegree(myVertex.X);
                    toolStripStatusLabel4.Text = @"Lat: " + DecimalDegreesHelper.GetDegreesMinutesSecondsStringFromDecimalDegree(myVertex.Y);
                    offsetProj.Close();
                }
            }
       }

    //This class inheriting from Projection applies the very simple projection of offsetting all the points
    //to 360 to the left.
    public class OffsetProjection : Projection, IDisposable
    {
        protected override Vertex[] ConvertToExternalProjectionCore(double[] x, double[] y)
        {

            Vertex[] vertices = new Vertex[x.Length];

            for (int i = 0; i < vertices.Length; i++)
            {
                vertices[i] = new Vertex(x[i] - 360, y[i]);
            }
            return vertices;
        }

        protected override Vertex[] ConvertToInternalProjectionCore(double[] x, double[] y)
        {
            Vertex[] vertices = new Vertex[x.Length];

            for (int i = 0; i < vertices.Length; i++)
            {
                vertices[i] = new Vertex(x[i] + 360, y[i]);
            }
            return vertices;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            Close();
        }
    }
}
