//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
//
// WFX satellite library | Author: Sergei Y.Kitáev
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
using System;
using System.Text;
using System.Collections.Generic;

using WF = System.Windows.Forms;
using DRW = System.Drawing;

using CommonHelpers;

namespace WFX {

//**************************************************************************************************
/// <summary>
/// </summary>
partial class ConsolePictureBox: WF.PictureBox {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

public const int
   MaxLinesDefault = 500;
public const ConsoleColor
   DefaultForegroundColor = HProxyConsoleSettings.DefaultForegroundColor,
   DefaultBackgroundColor = ConsoleColor.Black;

//--------------------------------------------------------------------------------------------------

/// <exception cref="InvalidOperationException" />
public ConsoleColor ForegroundColor
{
   get { return _ForegroundColor_; }
   set { VerifyNotInitialized(); _ForegroundColor_ = value; }
}

/// <exception cref="InvalidOperationException" />
public ConsoleColor BackgroundColor
{
   get
   {
      return _BackgroundColor_;
   }
   set
   {
      VerifyNotInitialized();
      BackColor = _ParentPanel_.BackColor =
         ConsoleColorConverter.ToDrawingColor( _BackgroundColor_ = value );
   }
}

/// <exception cref="InvalidOperationException" />
public DRW.Font FixedFont
{
   get { return base.Font; }
   set { VerifyNotInitialized(); base.Font = value; }
}

/// <exception cref="InvalidOperationException" />
/// <exception cref="ArgumentOutOfRangeException" />
public int MaxLines
{
   get
   {
      return _MaxLines_;
   }
   set
   {
      VerifyNotInitialized();
      if ( _MaxLines_ <= 2 )
         throw new ArgumentOutOfRangeException();
      _MaxLines_ = value;
   }
}

//--------------------------------------------------------------------------------------------------

/// <exception cref="InvalidOperationException" />
public int RecommendedContainerWidth
{
   get
   {
      VerifyInitialized();
      return _ConsoleWidth_ * _SymbolWidth_ +
         WF.SystemInformation.VerticalScrollBarWidth +
         HArith.RoundDiv( _SymbolWidth_, 2 ) + _RightGap_;
   }
}

/// <exception cref="InvalidOperationException" />
public int RecommendedContainerWidthCorrection
{
   get { return RecommendedContainerWidth - ContainerWidth; }
}

/// <exception cref="InvalidOperationException" />
public int AccurateHeightIncrement
{
   get { return AccurateHeightDecrement % _LineHeight_; }
}

/// <exception cref="InvalidOperationException" />
public int AccurateHeightDecrement
{
   get
   {
      VerifyInitialized();
      return HArith.ConfinePositiveOrZero( _ParentPanel_.ClientSize.Height % _LineHeight_ );
   }
}

//--------------------------------------------------------------------------------------------------

[HLockFree]
public bool IsInitialized
{
   get { return _Console_ != null; }
}

/// <exception cref="InvalidOperationException" />
[HLockFree]
public HConsole Console
{
   get
   {
      var console = _Console_;
      if ( console == null )
         throw InvalidOperationException_NotInitialized;
      return console;
   }
}

//==================================================================================================

public ConsolePictureBox(ConsoleBox container)
{
   Margin = WF.Padding.Empty;
   Width = Height = 0;
   Parent = _ParentPanel_ = container;
   FixedFont = new DRW.Font( "Lucida Console", 10, DRW.FontStyle.Bold, DRW.GraphicsUnit.Point );
   BackgroundColor = DefaultBackgroundColor;
   SetStyle( WF.ControlStyles.ResizeRedraw, true );
}

//==================================================================================================

/// <exception cref="InvalidOperationException" />
[HThreadSafe]
public void VerifyInitialized()
{
   if ( !IsInitialized )
      throw InvalidOperationException_NotInitialized;
}

/// <exception cref="InvalidOperationException" />
[HThreadSafe]
public void VerifyNotInitialized()
{
   if ( IsInitialized )
      throw InvalidOperationException_NotAccessibleAfterInitialization;
}

//--------------------------------------------------------------------------------------------------

/// <exception cref="InvalidOperationException" />
public HProxyConsoleSettings PrepareProxyConsoleSettings()
{
   VerifyNotInitialized();
 
   var proxySettings = new HProxyConsoleSettings( _ProxyConsoleSettingsMarker_ );

   proxySettings.ForegroundColor = ForegroundColor;

   proxySettings.WriteCallback = delegate (ConsoleColor consoleColor,string str) {
      OutputString( consoleColor, str, false ); };
   proxySettings.WriteLineCallback = delegate (ConsoleColor consoleColor,string str) {
      OutputString( consoleColor, str, true ); };
   proxySettings.ClearCallback = delegate {
      DoClear(); Refresh(); };

   return proxySettings;
}

/// <exception cref="ArgumentNullException" />
/// <exception cref="ArgumentException" />
/// <exception cref="HNullValueException" />
/// <exception cref="InvalidOperationException" />
public void Initialize(HConsole preparedProxyConsole)
{
   HArgChecking.VerifyNotNull( preparedProxyConsole );

   preparedProxyConsole.VerifyReliesOnProxy();
   var proxySettings = preparedProxyConsole.GetProxySettings();
   if ( proxySettings.Marker != _ProxyConsoleSettingsMarker_ )
      throw new ArgumentException( "Prepared console has wrong proxy settings instance." );
   if ( proxySettings.WriteCallback == null ||
        proxySettings.WriteLineCallback == null ||
        proxySettings.ClearCallback == null )
      throw new HNullValueException( "Proxy console callbacks must not be nulls." );

   VerifyNotInitialized();

   ForegroundColor = proxySettings.ForegroundColor;

   using ( var grfx = CreateGraphics() )
   {
      const int estimationLen = 10;
      var multiSymbolSize = grfx.MeasureString(
         new string( HUnicode.LightHorizontal, estimationLen ),
         FixedFont );
      _ParentPanel_.HorizontalScroll.SmallChange =
         _SymbolWidth_ = HArith.Round( multiSymbolSize.Width / estimationLen );
      _ParentPanel_.VerticalScroll.SmallChange =
         _LineHeight_ = HArith.Round( grfx.MeasureString(
            HUnicode.LightVertical.ToString(), FixedFont, _SymbolWidth_,
            new DRW.StringFormat(){
               FormatFlags = DRW.StringFormatFlags.DirectionVertical } ).Height );
      _RightGap_ = _SymbolWidth_ / 2;
   }
   _Lines_ = new List<ConsoleLineFragment[]>( MaxLines );
   _ColorToBrushDictionary_ = new HSimpleDictionary<DRW.Color,DRW.SolidBrush>();
   _ConsoleWidth_ = preparedProxyConsole.Width;
   _Console_ = preparedProxyConsole;

   DoClear();
}

//--------------------------------------------------------------------------------------------------

public void ApplySize()
{
   if ( IsInitialized )
   {
      ApplyWidth(); ApplyHeight();
   }
}

//==================================================================================================

protected override void OnPaint(WF.PaintEventArgs pe)
{
   base.OnPaint( pe );
   if ( !IsInitialized ) return;
   var grfx = pe.Graphics;
   for (int lineIdx=0; lineIdx<_Lines_.Count; lineIdx++)
   {
      int top = lineIdx * _LineHeight_;
      if ( top + _LineHeight_ < (int)grfx.ClipBounds.Top ||
           top > (int)grfx.ClipBounds.Bottom )
         continue;
      var fragments = _Lines_[ lineIdx ];
      for (int fragIdx=0,left=0; fragIdx<fragments.Length; fragIdx++)
      {
         var fragment = fragments[ fragIdx ];
         left += DrawString( grfx, fragment.Color, fragment.String, left, top );
      }
   }
}

//==================================================================================================

int ContainerWidth
{
   get { return _ParentPanel_.Width; }
}

int ContainerHeight
{
   get { return _ParentPanel_.Height; }
}

//--------------------------------------------------------------------------------------------------

void ApplyWidth()
{
   if ( IsInitialized ) Width = _Width_ + _RightGap_;
}

void ApplyHeight()
{
   if ( !IsInitialized ) return;
   int
      vertSpace = _ParentPanel_.ClientSize.Height,
      addition = 0;
   if ( _Height_ > vertSpace )
   {
      int remainder = vertSpace % _LineHeight_;
      addition = remainder % _LineHeight_;
   }
   Height = _Height_ + addition;
}

//--------------------------------------------------------------------------------------------------

bool ScrollToLeftBottom()
{
   var position = _ParentPanel_.AutoScrollPosition;
   var leftBottomPosition = new DRW.Point( 0, Height );
   if ( position != leftBottomPosition )
   {
      _ParentPanel_.AutoScrollPosition = leftBottomPosition;
      Invalidate();
      return true;
   }
   return false;
}

//--------------------------------------------------------------------------------------------------

/// <exception cref="InvalidOperationException" />
void DoClear()
{
   VerifyInitialized();
   _Lines_.Clear();
   _Lines_.Add( new ConsoleLineFragment[ 0 ] );
   _CursorLeft_ = _CursorTop_ = _CursorColumn_ = 0;
   _Width_ = 0; _Height_ = _LineHeight_;
   ApplySize();
   using ( var grfx = CreateGraphics() )
      grfx.FillRectangle( GetBrush( BackColor ), ClientRectangle );
   ScrollToLeftBottom();
   Invalidate();
}

//--------------------------------------------------------------------------------------------------

/// <exception cref="ArgumentNullException" />
/// <exception cref="InvalidOperationException" />
void OutputString(ConsoleColor consoleColor,string str,bool feedLine)
{
   HArgChecking.VerifyNotNull( str );
   Dispatcher.Synchronize( _ExceptionTypesForInterception_, delegate{
      VerifyInitialized(); DoOutputString( consoleColor, str, feedLine ); });
}

//--------------------------------------------------------------------------------------------------

void DoOutputString(ConsoleColor consoleColor,string str,bool feedLine)
{
   bool invalidation = ScrollToLeftBottom(), multiLine = false;
   using ( var grfx = CreateGraphics() )
   {
      var sb = new StringBuilder( str );
      while ( _CursorColumn_ + sb.Length >= _ConsoleWidth_ )
      {
         multiLine = true;
         int len = _ConsoleWidth_ - _CursorColumn_;
         DoOutputStringAsLine( grfx, consoleColor, sb.ToString( 0, len ), true, ref invalidation );
         sb.Remove( 0, len ); if ( sb.Length > 0 && sb[ 0 ] == ' ' ) sb.Remove( 0, 1 );
      }
      if ( sb.Length > 0 || feedLine && !multiLine )
         DoOutputStringAsLine( grfx, consoleColor, sb.ToString(), feedLine, ref invalidation );
   }
   if ( feedLine || multiLine )
      invalidation |= ScrollToLeftBottom();
   if ( invalidation ) Refresh();
}

void DoOutputStringAsLine( DRW.Graphics grfx, ConsoleColor consoleColor,
                           string str, bool feedLine, ref bool invalidation )
{
   DRW.Color color = ConsoleColorConverter.ToDrawingColor( consoleColor );
   int idx = _Lines_.Count - 1;
   _Lines_[ idx ] = HArray.Append( _Lines_[ idx ], new ConsoleLineFragment( color, str ) );
   _CursorLeft_ += DrawString( grfx, color, str, _CursorLeft_, _CursorTop_ );
   _CursorColumn_ += str.Length;
   if ( _CursorLeft_ >= _Width_ )
   {
      _Width_ = _CursorLeft_;
      ApplyWidth();
   }
   if ( feedLine )
   {
      _CursorLeft_ = 0;
      _Lines_.Add( new ConsoleLineFragment[ 0 ] );
      if ( _Lines_.Count > _MaxLines_ )
      {
         _Lines_.RemoveAt( 0 );
         int maxWidth = GetMaxLineWidth();
         if ( maxWidth < _Width_ )
         {
            _Width_ = maxWidth;
            ApplyWidth();
         }
         invalidation = true;
      }
      else
      {
         _CursorTop_ += _LineHeight_;
         _Height_ = _CursorTop_ + _LineHeight_;
         ApplyHeight();
      }
      _CursorColumn_ = 0;
   }
}

//--------------------------------------------------------------------------------------------------

int DrawString(DRW.Graphics grfx,DRW.Color color,string str,int left,int top)
{
   WF.TextRenderer.DrawText( grfx, str, FixedFont, new DRW.Point( left, top ), color,
      WF.TextFormatFlags.SingleLine );
   return _SymbolWidth_ * str.Length;
}

DRW.SolidBrush GetBrush(DRW.Color color)
{
   return  _ColorToBrushDictionary_[ color ] ??
      ( _ColorToBrushDictionary_[ color ] = new DRW.SolidBrush( color ) );
}

//--------------------------------------------------------------------------------------------------

int GetLineWidth(int idx)
{
   var line = _Lines_[ idx ];
   int charCount = 0;
   foreach (var fragment in line)
      charCount += fragment.String.Length;
   return charCount * _SymbolWidth_;
}

int GetMaxLineWidth()
{
   int maxWidth = 0;
   for (int idx=0; idx<_Lines_.Count; idx++)
      maxWidth = Math.Max( GetLineWidth( idx ), maxWidth );
   return maxWidth;
}

//==================================================================================================

static InvalidOperationException InvalidOperationException_NotInitialized
{
   get
   {
      return new InvalidOperationException( "\"ConsoleBox\"-control was not initialized." );
   }
}

static InvalidOperationException InvalidOperationException_NotAccessibleAfterInitialization
{
   get
   {
      return new InvalidOperationException(
         "Requested operation of \"ConsoleBox\"-control is not accessable after initialization." );
   }
}

//==================================================================================================

protected override void Dispose(bool disposing)
{
   if ( _DisposeFlag_ ) return;
   _DisposeFlag_ = true;
   if ( disposing )
      if ( _ColorToBrushDictionary_ != null )
         foreach (var brush in _ColorToBrushDictionary_.Values)
            HDisposable.TryDispose( brush );
   base.Dispose( disposing );
}

//==================================================================================================

internal int LineHeight
{
   get { return _LineHeight_; }
}

//==================================================================================================

static readonly object
   _ProxyConsoleSettingsMarker_ = new object();

//--------------------------------------------------------------------------------------------------

static readonly Type[]
   _ExceptionTypesForInterception_ = new Type[]{ typeof(InvalidOperationException) };
volatile HConsole
   _Console_;
WF.Panel
   _ParentPanel_;
ConsoleColor
   _ForegroundColor_ = DefaultForegroundColor,
   _BackgroundColor_;
int
   _MaxLines_ = MaxLinesDefault,
   _Width_, _Height_, _RightGap_,
   _SymbolWidth_, _LineHeight_,
   _CursorLeft_, _CursorTop_,
   _ConsoleWidth_, _CursorColumn_;
List<ConsoleLineFragment[]>
   _Lines_;
HSimpleDictionary<DRW.Color,DRW.SolidBrush>
   _ColorToBrushDictionary_;
volatile bool
   _DisposeFlag_;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} // ConsolePictureBox
//**************************************************************************************************

} // WFX
