//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//----------------------------------------------------------------
using System;
using System.Activities;
using System.Activities.Core.Presentation;
using System.Activities.Debugger;
using System.Activities.Presentation;
using System.Activities.Presentation.Debug;
using System.Activities.Presentation.Hosting;
using System.Activities.Presentation.Services;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using Newtonsoft.Json;
using SignalR.Client;

namespace Microsoft.Samples.VisualWorkflowTracking
{
    /// <summary>
    /// Interaction logic for WorkflowDesignerHost.xaml
    /// </summary>
    public partial class WorkflowDesignerHost 
    {
        private static Connection _connection;
        private string _busURL = "http://localhost:40253/registration";
        private bool _setup;
        private string _lastInstanceId = string.Empty;
        
        public WorkflowDesigner WorkflowDesigner { get; set; }
        public IDesignerDebugView DebuggerService { get; set; }
        private string _workflowName = @"D:\projects\VisualTrackingTalk\VBRaceEventRegistration\VBRaceEventRegistration.xamlx";
        //private string _workflowName = @"workflow.xaml";
        
        public string WorkflowName
        {
            get { return _workflowName; }
            set { 
                    _workflowName = value;
                    AddWorkflowDesigner();
                }
        }
        TextBox _textLineTextBox;
        Dictionary<int, SourceLocation> _textLineToSourceLocationMap;
        int _textLineNumber;

        public WorkflowDesignerHost()
        {
            InitializeComponent();
            RegisterMetadata();
            AddWorkflowDesigner();
            AddTrackingTextbox();
        }

        private void RegisterMetadata()
        {
            (new DesignerMetadata()).Register();
        }

        private void AddWorkflowDesigner()
        {
            WorkflowDesigner = new WorkflowDesigner();
            DebuggerService = WorkflowDesigner.DebugManagerView;
            WorkflowDesigner.Load(_workflowName);
            RehostGrid.Children.Add(WorkflowDesigner.View);

        }

        //Run the Workflow with the tracking participant
        public void StartTracking()
        {
            
            if (_connection == null)
            {
                _connection = new Connection(_busURL);
                _connection.Start().ContinueWith(task =>
                {
                    if (task.IsFaulted)
                    {
                        if (task.Exception != null)
                            Console.WriteLine("Failed to start: {0}", task.Exception.GetBaseException());
                    }
                    else
                    {
                        Console.WriteLine("Success! Connected with client connection id {0}",
                                          _connection.ConnectionId);
                        // Do more stuff here
                    }
                }).Wait();
                _connection.Send("signalr receiving started");
            }
            //Mapping between the Object and Line No.
            Dictionary<string, SourceLocation> wfElementToSourceLocationMap = UpdateSourceLocationMappingInDebuggerService();

            //As the tracking events are receive
            if (! _setup)
            {
                _connection.Received += data =>
                    {
                        if (string.IsNullOrEmpty(data)) return;
                        if (data.IndexOf("{", 0, 1, StringComparison.Ordinal) < 0) return; //not json, not interested!
                        var results = JsonConvert.DeserializeObject<dynamic>(data);
                        var activity = results.Activity;
                        string id = null;
                        string name = string.Empty;
                        string theType = string.Empty;
                        string instanceId = string.Empty;
                        if (activity != null)
                        {
                            id = activity.Id;
                            theType = activity.TypeName;
                            name = activity.Name;
                            instanceId = results.InstanceId;
                        }
                        string state = results.State;
                        TrackingEventArgs trackingEventArgs = new TrackingEventArgs(id, name, state, theType, instanceId);
                        if (trackingEventArgs.ActivityId != null)
                        {
                            if (_lastInstanceId != trackingEventArgs.InstanceId)
                            {
                                Dispatcher.Invoke(() =>
                                    {
                                        _textLineNumber = 0;
                                        _textLineTextBox.Clear();
                                        _textLineToSourceLocationMap.Clear();
                                    });
                                _lastInstanceId = trackingEventArgs.InstanceId;
                            }

                            if (wfElementToSourceLocationMap.ContainsKey(trackingEventArgs.ActivityId))
                                ShowDebug(wfElementToSourceLocationMap[trackingEventArgs.ActivityId]);

                            FireUpdateTrackingTextBox(trackingEventArgs, wfElementToSourceLocationMap);

                        }
                    };
                _setup = true;
            }

        }

        private void FireUpdateTrackingTextBox(TrackingEventArgs trackingEventArgs, Dictionary<string, SourceLocation> wfElementToSourceLocationMap)
        {
            Dispatcher.Invoke(DispatcherPriority.SystemIdle, (Action) (() =>
                {
                    //Textbox Updates
                    _textLineTextBox.AppendText(trackingEventArgs.DisplayName + " " + trackingEventArgs.State + "\n");
                    _textLineTextBox.AppendText("******************\n");
                    if (wfElementToSourceLocationMap.ContainsKey(trackingEventArgs.ActivityId))
                        _textLineToSourceLocationMap.Add(_textLineNumber,
                                                         wfElementToSourceLocationMap[trackingEventArgs.ActivityId]);
                    _textLineNumber = _textLineNumber + 2;

                    //Add a sleep so that the debug adornments are visible to the user
                    Thread.Sleep(1000);
                }));
        }

        void ShowDebug(SourceLocation srcLoc)
        {
            Dispatcher.Invoke(DispatcherPriority.Render
                , (Action)(() =>
            {
                WorkflowDesigner.DebugManagerView.CurrentLocation = srcLoc;
                
            }));
        
        }

        //Provide Debug Adornment on the Activity being executed
        void textBox1_SelectionChanged(object sender, RoutedEventArgs e)
        {

            string text = _textLineTextBox.Text;

            int index = 0;
            int lineClicked = 0;
            while (index < text.Length)
            {
                if (text[index] == '\n')
                    lineClicked++;
                if (_textLineTextBox.SelectionStart <= index)
                    break;

                index++;
            }


            Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() =>
            {
                try
                {
                    //Tell Debug Service that the Line Clicked is _______
                    WorkflowDesigner.DebugManagerView.CurrentLocation = _textLineToSourceLocationMap[lineClicked];
                }
                catch (Exception)
                {
                    //If the user clicks other than on the tracking records themselves.
                    WorkflowDesigner.DebugManagerView.CurrentLocation = new SourceLocation(_workflowName, 1, 1, 1, 10);
                }
            }));

        }

        Dictionary<string, SourceLocation> UpdateSourceLocationMappingInDebuggerService()
        {
            object rootInstance = GetRootInstance();

            Dictionary<object, SourceLocation> designerSourceLocationMapping = new Dictionary<object, SourceLocation>();

            if (rootInstance != null)
            {
                Activity documentRootElement = GetRootWorkflowElement(rootInstance);
                SourceLocationProvider.CollectMapping(documentRootElement, documentRootElement, designerSourceLocationMapping,
                   WorkflowDesigner.Context.Items.GetValue<WorkflowFileItem>().LoadedFile);

            }

            // Notify the DebuggerService of the new sourceLocationMapping.
            // When rootInstance == null, it'll just reset the mapping.
            //DebuggerService debuggerService = debuggerService as DebuggerService;
            if (DebuggerService != null)
            {
                ((DebuggerService)DebuggerService).UpdateSourceLocations(designerSourceLocationMapping);
            }
            Dictionary<string, SourceLocation> internalSourceLocation = new Dictionary<string, SourceLocation>();
            foreach (var activity in designerSourceLocationMapping.Keys)
            {
                string activityId = string.Empty;
                var act = activity as Activity;
                if (act != null)
                    activityId = act.Id;
                var native = activity as NativeActivity;
                if (native != null)
                    activityId = native.Id;
                if (!string.IsNullOrEmpty(activityId))
                    internalSourceLocation.Add(activityId, designerSourceLocationMapping[activity]);
            }
            return internalSourceLocation;
        }


        # region Helper Methods
        object GetRootInstance()
        {
            ModelService modelService = WorkflowDesigner.Context.Services.GetService<ModelService>();
            if (modelService != null)
            {
                return modelService.Root.GetCurrentValue();
            }
            return null;
        }

        // Get root WorkflowElement.  Currently only handle when the object is ActivitySchemaType or WorkflowElement.
        // May return null if it does not know how to get the root activity.
        Activity GetRootWorkflowElement(object rootModelObject)
        {
            System.Diagnostics.Debug.Assert(rootModelObject != null, "Cannot pass null as rootModelObject");

            Activity rootWorkflowElement;
            IDebuggableWorkflowTree debuggableWorkflowTree = rootModelObject as IDebuggableWorkflowTree;
            if (debuggableWorkflowTree != null)
            {
                rootWorkflowElement = debuggableWorkflowTree.GetWorkflowRoot();
            }
            else // Loose xaml case.
            {
                rootWorkflowElement = rootModelObject as Activity;
            }
            return rootWorkflowElement;
        }

       

        void AddTrackingTextbox()
        {
            _textLineTextBox = new TextBox();
            Grid.SetRow(_textLineTextBox, 1);

            Label trackRecords = new Label();
            trackRecords.FontSize = 11;
            trackRecords.FontWeight = FontWeights.Bold;
            trackRecords.Content = "Tracking Records: ";
            Grid.SetRow(trackRecords, 0);
            TrackingRecord.Children.Add(trackRecords);
            TrackingRecord.Children.Add(_textLineTextBox);

            //For Tracking Records displayed and to check which activity those records corresponds to.
            _textLineTextBox.SelectionChanged += new RoutedEventHandler(textBox1_SelectionChanged);
            _textLineToSourceLocationMap = new Dictionary<int, SourceLocation>();

        }
        # endregion
    }
}
