using System;
using System.Collections.Generic;
using System.Text;
using ScintillaNet;
using System.Windows;
using System.Windows.Forms;
using System.Drawing;


namespace MySQL.Grt.Db.Sql
{
  public class SqlEditor : Scintilla
  {
    private Sql_editor sqlEditorBE;
    private GrtManager grtManager;
    private Timer timer;
    private String lastProcessedSqlText = "";
    private List<ManagedRange> messagesRanges = new List<ManagedRange>();
    private List<String> messages = new List<String>();

    public delegate void BackgroundActionDelegate();
    public BackgroundActionDelegate BackgroundAction;

    public SqlEditor(GrtManager grtManager)
    {
      GrtManager = grtManager;
      Initialize();
    }

    public GrtManager GrtManager
    {
      get { return grtManager; }
      set { grtManager = value; }
    }

    public Sql_editor BE
    {
      get { return sqlEditorBE; }
      set
      {
        sqlEditorBE = value;
        sqlEditorBE.sql_parser_err_cb(ProcessSyntaxError);

        SuspendLayout();
        SetupEditorProperties();
        ResumeLayout();
      }
    }

    /// <summary>
    /// Here go all the settings we need for the Scintilla editor control.
    /// </summary>
    private void SetupEditorProperties()
    {
      BeginInit();

      if (sqlEditorBE != null)
      {
        ConfigurationManager.Language = sqlEditorBE.string_option("ConfigurationManager.Language");
        Indentation.IndentWidth = sqlEditorBE.int_option("Indentation.IndentWidth");
        Indentation.TabWidth = sqlEditorBE.int_option("Indentation.TabWidth");
        Indentation.UseTabs = sqlEditorBE.int_option("Indentation.UseTabs") != 0;
      }

      Caret.IsSticky = true;
      Printing.PageSettings.Color = false;
      UseFont = true;
      Font = grtManager.get_font_option("workbench.general.Editor:Font");
      Margins.Margin0.Width = 35; // line numbers
      Margins.Margin1.Width = 16; // markers
      Margins.Margin2.Width = 16; // indicators
      Indentation.BackspaceUnindents = true;
      Indentation.SmartIndentType = SmartIndent.Simple;

      // errors indication
      Indicator errInd = Indicators[0];
      errInd.Color = Color.Red;
      errInd.Style = ScintillaNet.IndicatorStyle.Squiggle;
      errInd.IsDrawnUnder = true;

      errInd = Indicators[1];
      errInd.Color = Color.Red;
      errInd.Style = ScintillaNet.IndicatorStyle.RoundBox;
      errInd.IsDrawnUnder = true;

      Marker marker = Markers[0];
      marker.BackColor = Color.Red;

      // fixes improper ViewportSize from start.
      // once row is stretched enough to make thumb appear this thumb becomes irreversible.
      Scrolling.HorizontalWidth = 1;

      NativeInterface.SetCodePage((int)Constants.SC_CP_UTF8);
      EndOfLine.Mode = EndOfLineMode.Crlf;

      DwellStart += new EventHandler<ScintillaMouseEventArgs>(OnDwellStart);
      DwellEnd += new EventHandler(OnDwellEnd);
      NativeInterface.SetMouseDwellTime(200);

      EndInit();
    }

    protected virtual void Initialize()
    {
      Dock = DockStyle.Fill;
      timer = new Timer();
      timer.Interval = 500;
      timer.Tick += new EventHandler(BackgroundActionTimer);

      BackgroundAction = CheckSyntax;

      TextDeleted += new System.EventHandler<ScintillaNet.TextModifiedEventArgs>(TextModified);
      TextInserted += new System.EventHandler<ScintillaNet.TextModifiedEventArgs>(TextModified);

      SetupEditorProperties();
    }

    public String SqlText
    {
      get { return Text; }
      set
      {
        if (Text != value)
        {
          BackgroundActionDelegate backgroundAction = BackgroundAction;
          BackgroundAction = null;

          int caretPosition = Caret.Position;
          int firstVisibleLine = NativeInterface.GetFirstVisibleLine();
          int xOffset = NativeInterface.GetXOffset();
          int lineCount = Lines.Count;

          lastProcessedSqlText = Text = value;

          if (lineCount <= Lines.Count)
            Scrolling.ScrollBy(xOffset, firstVisibleLine);

          if (caretPosition > Text.Length)
            caretPosition = Text.Length;
          Selection.Start = Selection.End = Caret.Position = caretPosition;

          Caret.EnsureVisible();

          BackgroundAction = backgroundAction;
        }
        CheckSyntax();
      }
    }

    private void TextModified(object sender, ScintillaNet.TextModifiedEventArgs e)
    {
      if (null != BackgroundAction)
      {
        timer.Stop();
        timer.Start();
      }
    }

    public void ForceBackgroundActionTimer()
    {
      BackgroundActionTimer(this, new EventArgs());
    }

    private void BackgroundActionTimer(Object obj, EventArgs args)
    {
      if (!IsDirty)
        return;

      IsDirty = false;
      if (null != BackgroundAction)
      {
        if (Text != lastProcessedSqlText)
        {
          lastProcessedSqlText = Text;
          BackgroundAction();
        }
        else
          timer.Stop();
      }
    }

    public void CheckSyntax()
    {
      ResetSyntaxErrors();
      if (null == sqlEditorBE)
        return;
      sqlEditorBE.sql(Text);
      sqlEditorBE.check_sql();
    }

    public int ProcessSyntaxError(int line, int errTokLinePos, int errTokLen, String msg)
    {
      line -= 1;
      try
      {
        errTokLinePos += Lines[line].Range.Start;
        ManagedRange range = new ManagedRange(errTokLinePos, errTokLinePos + errTokLen, this);
        
        SuspendLayout();
        range.SetIndicator(0);
        range.SetIndicator(1);
        Lines[line].AddMarker(0);
        ResumeLayout();

        messages.Add(msg);
        messagesRanges.Add(range);
        ManagedRanges.Add(range);
      }
      catch (Exception) { }
      return 0;
    }

    public void ResetSyntaxErrors()
    {
      timer.Stop();
      foreach (ManagedRange managedRange in messagesRanges)
        if (!managedRange.IsDisposed)
          managedRange.Dispose();
      messagesRanges.Clear();
      messages.Clear();
      Range range = new Range(0, RawText.Length, this);

      SuspendLayout();
      range.ClearIndicator(0);
      range.ClearIndicator(1);
      Markers.DeleteAll(Markers[0]);
      ResumeLayout();
    }

    void OnDwellStart(object sender, ScintillaMouseEventArgs e)
    {
      int pos = PositionFromPoint(e.X, e.Y);
      if (pos == -1)
        return;
      int messageIndex = -1;
      if (NativeInterface.IndicatorValueAt(0, pos) == 1)
      {
        for (int n = 0; n < messagesRanges.Count; ++n)
        {
          if (messagesRanges[n].PositionInRange(pos))
          {
            messageIndex = n;
            break;
          }
        }
      }
      if ((messageIndex >= 0) && (messageIndex < messages.Count))
        CallTip.Show(messages[messageIndex], pos);
    }

    void OnDwellEnd(object sender, EventArgs e)
    {
      CallTip.Cancel();
    }
  }
}
