using QuikDawEditor.EditingClasses;
using QuikDawEditor.Undo;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using static QuikDawEditor.EDITING.MiscMethods;
using static QuikDawEditor.EDITING.StaticProperties;

namespace QuikDawEditor;

public partial class AutomationLaneControl : UserControl, INotifyPropertyChanged
{
    public string RelRes() { ReleaseResources(); return "Resources released"; }

    private AutomationLane thisAutoLane { get { return (AutomationLane)this.DataContext; } }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } }

    public AutomationLaneControl() { InitializeComponent(); }

    private void AutoClipControl_Loaded(object sender, RoutedEventArgs e) { }

    private int GetAutoLaneIndex { get { return thisAutoLane.myTrack == null ? editingProject.MasterTrack.AutomationLanes.IndexOf(thisAutoLane) : thisAutoLane.myTrack.AutomationLanes.IndexOf(thisAutoLane); } }

    private void AutoPointThumb_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
    {

        thisAP = (AutoPoint)((System.Windows.Controls.Primitives.Thumb)sender).DataContext;
        
        undoActions.Add(new AutoPointMovedUndoClass(thisAutoLane.myTrackID, GetAutoLaneIndex, thisAutoLane.autoPoints.IndexOf(thisAP), thisAP.sourcePointms, thisAP.AutoValue));

        double thisWidthMs = PixelsToMsec(this.ActualWidth);

        AutoPoint prevGP = thisAutoLane.autoPoints.LastOrDefault(gp => gp.sourcePointms < thisAP.sourcePointms);
        AutoPoint nextGP = thisAutoLane.autoPoints.FirstOrDefault(gp => gp.sourcePointms > thisAP.sourcePointms);
        RightMax = thisAP.IsLeftEdgeAutoPoint ? 0 : (thisAP.IsRightEdgeAutoPoint ? thisWidthMs : nextGP.sourcePointms - 1);
        LeftMin = thisAP.IsRightEdgeAutoPoint ? thisWidthMs : (thisAP.IsLeftEdgeAutoPoint ? 0 : prevGP.sourcePointms + 1);
        
         thisAP.AutoPointValVisibility = Visibility.Visible;

    }

    private void AutoPointThumb_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
    {
        thisAP.AutoPointValVisibility = Visibility.Hidden;
    }

    AutoPoint thisAP;
    double LeftMin;
    double RightMax;

    private void AutoPointThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
    {

        thisAP.sourcePointms = Math.Min(Math.Max(thisAP.sourcePointms + PixelsToMsec(e.HorizontalChange), LeftMin), RightMax);
        
        float aval = (float)(thisAP.AutoValue +  thisAutoLane.ValueRange * (-e.VerticalChange /  UnitHeight));
        thisAP.AutoValue = Math.Max(Math.Min(aval, thisAutoLane.MaxValue), thisAutoLane.MinValue);

        thisAutoLane.NotifyAutoPointsChanged();

        //Automation type-specific displaying here
        string prefix = "";
        if (thisAutoLane.AutomationType == "Pan")
            prefix = Math.Abs(thisAP.AutoValue) < 0.2 ? " C" : (Math.Sign(thisAP.AutoValue) == 1 ? " R" : " L");
        thisAP.UpdateAutoString(prefix);

    }
    
    

    bool mouseOverAutoPoint = false;
    
    private void AutoPointThumb_MouseEnter(object sender, MouseEventArgs e)
    {
        mouseOverAutoPoint = true;
        thisAP = (AutoPoint)((System.Windows.Controls.Primitives.Thumb)sender).DataContext;
    }

    private void AutoPointThumb_MouseLeave(object sender, MouseEventArgs e)
    {
        mouseOverAutoPoint = false;
    }


    private void ClearAllAutoPointsMenuItem_Click(object sender, RoutedEventArgs e)
    {
        List<AutoPoint> removedAutoPoints = new List<AutoPoint>();

        for (int gpno = thisAutoLane.autoPoints.Count - 2; gpno > 0; gpno -= 1)
        {
            removedAutoPoints.Add(thisAutoLane.autoPoints[gpno]);
            thisAutoLane.autoPoints.RemoveAt(gpno);
        }
        
        undoActions.Add(new AutoPointsClearedUndoClass(thisAutoLane.myTrackID, GetAutoLaneIndex, removedAutoPoints, thisAutoLane.ClipStartAutoPoint.AutoValue, thisAutoLane.ClipEndAutoPoint.AutoValue));

        thisAutoLane.ClipStartAutoPoint.AutoValue = thisAutoLane.MidValue;
        thisAutoLane.ClipEndAutoPoint.AutoValue = thisAutoLane.MidValue;

        thisAutoLane.NotifyAutoPointsChanged();

        editingProject.NeedsSaving = true;

    }

    private void FadeInFromStartMenuItem_Click(object sender, RoutedEventArgs e)
    {
        double endPointms = PixelsToMsec(MouseDownX);
        double thisWidthMs = PixelsToMsec(this.ActualWidth) / editingProject.ViewXZoomFac;
        float relAutoValue = CalculateValBetweenAutoPoints(endPointms, thisAutoLane);

        //Remove gain points between new points            
        List<AutoPoint> deletedAutoPoints = new List<AutoPoint>(thisAutoLane.autoPoints.Where(gp => !gp.IsLeftEdgeAutoPoint && gp.sourcePointms <= endPointms));
        thisAutoLane.autoPoints = new ObservableCollection<AutoPoint>(thisAutoLane.autoPoints.Except(deletedAutoPoints));

        int insertAPIdx = thisAutoLane.autoPoints.IndexOf(thisAutoLane.autoPoints.FirstOrDefault(ap=>ap.sourcePointms > endPointms));
        thisAutoLane.autoPoints.Insert(insertAPIdx, new AutoPoint() { sourcePointms = endPointms, AutoValue = relAutoValue });

        undoActions.Add(new AutoPointsFadeInUndoClass(thisAutoLane.myTrackID, GetAutoLaneIndex, insertAPIdx, deletedAutoPoints, thisAutoLane.ClipStartAutoPoint.AutoValue));

        thisAutoLane.ClipStartAutoPoint.AutoValue = thisAutoLane.MinValue;
        thisAutoLane.NotifyAutoPointsChanged();

        editingProject.NeedsSaving = true;
    }

    private void FadeOutToEndMenuItem_Click(object sender, RoutedEventArgs e)
    {
        double startpointms = PixelsToMsec(MouseDownX);
        double thisWidthMs = PixelsToMsec(this.ActualWidth) / editingProject.ViewXZoomFac;
        float relAutoValue = CalculateValBetweenAutoPoints(startpointms, thisAutoLane);

        //Remove gain points between new points
        List<AutoPoint> deletedAutoPoints = new List<AutoPoint>(thisAutoLane.autoPoints.Where(gp => gp.sourcePointms >= startpointms && !gp.IsRightEdgeAutoPoint));
        thisAutoLane.autoPoints = new ObservableCollection<AutoPoint>(thisAutoLane.autoPoints.Except(deletedAutoPoints));

        int insertAPIdx = thisAutoLane.autoPoints.IndexOf(thisAutoLane.autoPoints.FirstOrDefault(ap => ap.sourcePointms > startpointms));
        thisAutoLane.autoPoints.Insert(insertAPIdx, new AutoPoint() { sourcePointms = startpointms, AutoValue =relAutoValue });

        undoActions.Add(new AutoPointsFadeOutUndoClass(thisAutoLane.myTrackID, GetAutoLaneIndex, insertAPIdx, deletedAutoPoints, thisAutoLane.ClipEndAutoPoint.AutoValue));

        thisAutoLane.ClipEndAutoPoint.AutoValue = thisAutoLane.MinValue;
        
        thisAutoLane.NotifyAutoPointsChanged();

        editingProject.NeedsSaving = true;
    }

 
    private void MuteSelectionMenuItem_Click(object sender, RoutedEventArgs e)
    {
        InsertMutedAutoPoints();
    }


    private void InsertMutedAutoPoints()
    {
        double rectStartMs = PixelsToMsec(SelectionRect.Margin.Left);
        double rectEndMs = PixelsToMsec(SelectionRect.Margin.Left + SelectionRect.Width);

        
        AutoPoint prevAutoPoint = thisAutoLane.autoPoints.LastOrDefault(gp => gp.sourcePointms < rectStartMs);
        AutoPoint nextAutoPoint = thisAutoLane.autoPoints.FirstOrDefault(gp => gp.sourcePointms > rectEndMs);
        if (prevAutoPoint == null) prevAutoPoint = thisAutoLane.ClipStartAutoPoint;
        if (nextAutoPoint == null) nextAutoPoint = thisAutoLane.ClipEndAutoPoint;

        List<AutoPoint> deletedAutoPoints = new List<AutoPoint>(thisAutoLane.autoPoints.Where(gp => gp.sourcePointms >= rectStartMs && gp.sourcePointms <= rectEndMs));
        int deletedStart = deletedAutoPoints.Count > 0 ? thisAutoLane.autoPoints.IndexOf(deletedAutoPoints[0]) : -1;

        if (deletedStart > -1)
            for (int n = 0; n < deletedAutoPoints.Count; n++) thisAutoLane.autoPoints.RemoveAt(deletedStart);

        double percOfSectionStart = (rectStartMs - prevAutoPoint.sourcePointms) / (nextAutoPoint.sourcePointms - prevAutoPoint.sourcePointms);
        float gainValAtStart = (float)(percOfSectionStart * (nextAutoPoint.AutoValue - prevAutoPoint.AutoValue)) + prevAutoPoint.AutoValue;
        double percOfSectionEnd = (rectEndMs - prevAutoPoint.sourcePointms) / (nextAutoPoint.sourcePointms - prevAutoPoint.sourcePointms);
        float gainValAtEnd = (float)(percOfSectionEnd * (nextAutoPoint.AutoValue - prevAutoPoint.AutoValue)) + prevAutoPoint.AutoValue;

        int insertIdx = thisAutoLane.autoPoints.IndexOf(nextAutoPoint);

        AutoPoint startPair = new AutoPoint() { sourcePointms = rectStartMs, AutoValue = thisAutoLane.MinValue };
        AutoPoint endPair = new AutoPoint() { sourcePointms = rectEndMs, AutoValue = thisAutoLane.MinValue };

        List<AutoPoint> newAutoPoints = new List<AutoPoint>()
        {
            new AutoPoint() { sourcePointms = endPair.sourcePointms + 1, AutoValue = gainValAtEnd },
            endPair,
            startPair,
            new AutoPoint() { sourcePointms = startPair.sourcePointms - 1, AutoValue = gainValAtStart }
        };

        foreach (AutoPoint gp in newAutoPoints)
            thisAutoLane.autoPoints.Insert(insertIdx, gp);

        undoActions.Add(new AutoPointsSelectionMutedUndoClass(thisAutoLane.myTrackID, GetAutoLaneIndex, insertIdx, deletedAutoPoints));



        editingProject.NeedsSaving = true;
        SelectionRect.Visibility = Visibility.Hidden;
        MouseDownOnAutomationLane = false;
        thisAutoLane.NotifyAutoPointsChanged();

    }

    private void ClearSelectionGainPointsMenuItem_Click(object sender, RoutedEventArgs e)
    {
        ClearSelection(false);
    }

    private void ResetSelectionGainPointsMenuItem_Click(object sender, RoutedEventArgs e)
    {
        ClearSelection(true);
    }
    
    private void ClearSelection(bool addends)
    {
        double rectStartMs = PixelsToMsec(SelectionRect.Margin.Left);
        double rectEndMs = PixelsToMsec(SelectionRect.Margin.Left + SelectionRect.Width);

        //Remove gain points between new points
        List<AutoPoint> removeAPs = thisAutoLane.autoPoints.Where(ap => ap.sourcePointms >= rectStartMs && ap.sourcePointms <= rectEndMs).ToList();
        for (int apno = removeAPs.Count - 1; apno > -1; apno -=1)
              thisAutoLane.autoPoints.Remove(removeAPs[apno]);

        int insertPtsIdx = thisAutoLane.autoPoints.IndexOf(thisAutoLane.autoPoints.FirstOrDefault(ap => ap.sourcePointms > rectEndMs)); ;
        if (addends)
        {
            thisAutoLane.autoPoints.Insert(insertPtsIdx, new AutoPoint() { sourcePointms = rectEndMs, AutoValue = thisAutoLane.MidValue });
            thisAutoLane.autoPoints.Insert(insertPtsIdx, new AutoPoint() { sourcePointms = rectStartMs, AutoValue = thisAutoLane.MidValue });
        }

        undoActions.Add(new AutoPointsSelectionClearedOrResetUndoClass(thisAutoLane.myTrackID, GetAutoLaneIndex, insertPtsIdx, removeAPs, addends));

        thisAutoLane.NotifyAutoPointsChanged();
        SelectionRect.Visibility = Visibility.Hidden;
        MouseDownOnAutomationLane = false;
        editingProject.NeedsSaving = true;
    }

    private void SelectionRect_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        switch (e.Key)
        {
            //case Key.M:
            //    InsertMutedAutoPoints();
            //    break;

            //case Key.C:
            //    ClearSelection(false);
            //    break;
        }
    }

    private void SelectionRect_PreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
        if (e.ChangedButton == MouseButton.Right)
            SelectionRect.ContextMenu.IsOpen = true;
        else
            SelectionRect.Visibility = Visibility.Hidden;
        
        e.Handled = true;

    }

    private void SelectionRect_PreviewMouseUp(object sender, MouseButtonEventArgs e)
    {
        SelectionRect.Focus();
    }

    private void AutoPath_PreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
        e.Handled = true;
        if (e.ChangedButton == MouseButton.Right) return;

        if (SelectionRect.IsVisible) return;

        double newPointMs = PixelsToMsec(e.GetPosition(this).X);

        AutoPoint lastAutoPoint = thisAutoLane.autoPoints.LastOrDefault(gp => gp.sourcePointms < newPointMs);
        int insertIdx = lastAutoPoint == null ? 0 : thisAutoLane.autoPoints.IndexOf(lastAutoPoint) + 1;
        AutoPoint newAutoPoint = new AutoPoint() { sourcePointms = newPointMs };

        float aval = thisAutoLane.ValueRange + thisAutoLane.MinValue - thisAutoLane.ValueRange * (float)((e.GetPosition(this).Y - 2) / UnitHeight);  //Mouse click places it too low
        newAutoPoint.AutoValue = aval;

        thisAutoLane.autoPoints.Insert(insertIdx, newAutoPoint);
        thisAutoLane.NotifyAutoPointsChanged();

        undoActions.Add(new AutoPointAddedUndoClass(thisAutoLane.myTrackID, GetAutoLaneIndex, insertIdx));

        editingProject.NeedsSaving = true;

    }

    private void AutoPath_PreviewMouseMove(object sender, MouseEventArgs e)
    {
        //Debug.WriteLine("In path: " + e.GetPosition(GainPath).X.ToString());
    }


    private void thisAutoLaneControl_MouseEnter(object sender, MouseEventArgs e)
    {
        thisAutoLane.SetAutoPointLayoutTransform();
        thisAutoLane.AutoPointsVisible = thisAutoLane.IsActive;
    }

    private void thisAutoLaneControl_MouseLeave(object sender, MouseEventArgs e)
    {
        Cursor = Cursors.Arrow;
         thisAutoLane.AutoPointsVisible = false;
    }

    private void AutoPointDeleteMenuItem_Click(object sender, RoutedEventArgs e)
    {

        if (thisAP.IsLeftOrRightEdgeAutoPoint) return;
        undoActions.Add(new AutoPointDeletedUndoClass(thisAutoLane.myTrackID, GetAutoLaneIndex, thisAutoLane.autoPoints.IndexOf(thisAP), thisAP));
        thisAutoLane.autoPoints.Remove(thisAP);
        thisAutoLane.NotifyAutoPointsChanged();
        editingProject.NeedsSaving = true;

    }


    private void ClipContextMenu_Opened(object sender, RoutedEventArgs e)
    {
        thisAutoLane.AutoPointsVisible = true;
    }

    private void AutoPath_PreviewDragOver(object sender, DragEventArgs e)
    {
        e.Effects = DragDropEffects.None;
        e.Handled = true;
    }

    private void thisAutoLaneControl_PreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
        TrackContentControlChildBeingModified = true;
        if (e.ChangedButton == MouseButton.Right) return;

        MouseDownOnAutomationLane = true;
        MouseDownX = e.GetPosition(this).X;

        SelectionRect.Visibility = Visibility.Hidden;
      
        if (!mouseOverAutoPoint)
            this.CaptureMouse();

    }

    private double MouseDownX = 0;
    private bool MouseDownOnAutomationLane = false;

    private void thisAutoLaneControl_PreviewMouseUp(object sender, MouseButtonEventArgs e)
    {
        TrackContentControlChildBeingModified = false;

        if (e.ChangedButton == MouseButton.Right)
        {
            if (mouseOverAutoPoint) return;
            e.Handled = true;
            MouseDownX = e.GetPosition(this).X;
            this.ContextMenu.IsOpen = true;
        }

        MouseDownOnAutomationLane = false;

         this.ReleaseMouseCapture();

    }

    private void thisAutoLaneControl_PreviewMouseMove(object sender, MouseEventArgs e)
    {
        if (mouseOverAutoPoint) return;
  
        if (MouseDownOnAutomationLane)
        {
            double rwidth = e.GetPosition(this).X - MouseDownX;
            if (rwidth > 5)
            {
                SelectionRect.Margin = new Thickness(MouseDownX, 0, 0, 0);
                SelectionRect.Visibility = Visibility.Visible;
                SelectionRect.Width = rwidth;
            }
        }
    }

    private void SelRectContextMenu_Opened(object sender, RoutedEventArgs e)
    {
        thisAutoLane.AutoPointsVisible = true;

    }

    private void ThumbContextMenu_Opened(object sender, RoutedEventArgs e)
    {
        thisAutoLane.AutoPointsVisible = true;
    }

    
}