// Copyright (c) 2007-2012  Michael Chapman
// http://ipaddresscontrollib.googlecode.com

// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:

// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms; 
using System.Windows.Forms.VisualStyles;


namespace IPAddressControlLib
{
   [DesignerAttribute( typeof( IPAddressControlDesigner ) )]
   public class IPAddressControl : ContainerControl
   {
      #region Public Constants

      public const int FieldCount = 4;

      #endregion // Public Constants

      #region Public Events

      public event EventHandler<FieldChangedEventArgs> FieldChangedEvent;

      #endregion //Public Events

      #region Public Properties

      [Browsable( true )]
      public bool AllowInternalTab
      {
         get
         {
            foreach ( FieldControl fc in _fieldControls )
            {
               return fc.TabStop;
            }

            return false;
         }
         set
         {
            foreach ( FieldControl fc in _fieldControls )
            {
               fc.TabStop = value;
            }
         }
      }

      [Browsable( true )]
      public bool AnyBlank
      {
         get
         {
            foreach ( FieldControl fc in _fieldControls )
            {
               if ( fc.Blank )
               {
                  return true;
               }
            }

            return false;
         }
      }

      [Browsable( true )]
      public bool AutoHeight
      {
         get { return _autoHeight; }
         set
         {
            _autoHeight= value;

            if ( _autoHeight )
            {
               AdjustSize();
            }
         }
      }

      [Browsable( false )]
      public int Baseline
      {
         get 
         {
            NativeMethods.TEXTMETRIC textMetric = GetTextMetrics( Handle, Font );

            int offset = textMetric.tmAscent + 1;

            switch ( BorderStyle )
            {
               case BorderStyle.Fixed3D:
                  offset += Fixed3DOffset.Height;
                  break;
               case BorderStyle.FixedSingle:
                  offset += FixedSingleOffset.Height;
                  break;
            }

            return offset;
         }
      }

      [Browsable( true )]
      public bool Blank
      {
         get
         {
            foreach ( FieldControl fc in _fieldControls )
            {
               if ( !fc.Blank )
               {
                  return false;
               }
            }

            return true;
         }
      }

      [Browsable( true )]
      public BorderStyle BorderStyle
      {
         get { return _borderStyle; }
         set
         {
            _borderStyle = value;
            AdjustSize();
            Invalidate();
         }
      }

      [Browsable( false )]
      public override bool Focused
      {
         get
         {
            foreach ( FieldControl fc in _fieldControls )
            {
               if ( fc.Focused )
               {
                  return true;
               }
            }

            return false;
         }
      }

      [Browsable( false ), DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )]
      public IPAddress IPAddress
      {
         get { return new IPAddress( GetAddressBytes() ); }
         set
         {
            Clear();

            if ( null == value ) { return; }

            if ( value.AddressFamily == AddressFamily.InterNetwork )
            {
               SetAddressBytes( value.GetAddressBytes() );
            }
         }
      }

      [Browsable( true )]
      public override Size MinimumSize
      {
         get { return CalculateMinimumSize(); }
      }

      [Browsable( true )]
      public bool ReadOnly
      {
         get { return _readOnly; }
         set
         {
            _readOnly = value;

            foreach ( FieldControl fc in _fieldControls )
            {
               fc.ReadOnly = _readOnly;
            }

            foreach ( DotControl dc in _dotControls )
            {
               dc.ReadOnly = _readOnly;
            }

            Invalidate();
         }
      }

      [Bindable(true)]
      [Browsable(true)]
      [DesignerSerializationVisibility( DesignerSerializationVisibility.Visible )]
      public override string Text
      {
         get
         {
            StringBuilder sb = new StringBuilder(); ;

            for ( int index = 0; index < _fieldControls.Length; ++index )
            {
               sb.Append( _fieldControls[ index ].Text );

               if ( index < _dotControls.Length )
               {
                  sb.Append( _dotControls[ index ].Text );
               }
            }

            return sb.ToString();
         }
         set
         {
            Parse( value );
         }
      }

      #endregion // Public Properties

      #region Public Methods

      public void Clear()
      {
         foreach ( FieldControl fc in _fieldControls )
         {
            fc.Clear();
         }
      }

      public byte[] GetAddressBytes()
      {
         byte[] bytes = new byte[FieldCount];

         for ( int index = 0; index < FieldCount; ++index )
         {
            bytes[index] = _fieldControls[index].Value;
         }

         return bytes;
      }

      [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", Justification = "Using Bytes seems more informative than SetAddressValues." )]
      public void SetAddressBytes( byte[] bytes )
      {
         Clear();

         if ( bytes == null )
         {
            return;
         }

         int length = Math.Min( FieldCount, bytes.Length );

         for ( int i = 0; i < length; ++i )
         {
            _fieldControls[i].Text = bytes[i].ToString( CultureInfo.InvariantCulture );
         }
      }

      public void SetFieldFocus( int fieldIndex )
      {
         if ( ( fieldIndex >= 0 ) && ( fieldIndex < FieldCount ) )
         {
            _fieldControls[fieldIndex].TakeFocus( Direction.Forward, Selection.All );
         }
      }

      public void SetFieldRange( int fieldIndex, byte rangeLower, byte rangeUpper )
      {
         if ( ( fieldIndex >= 0 ) && ( fieldIndex < FieldCount ) )
         {
            _fieldControls[fieldIndex].RangeLower = rangeLower;
            _fieldControls[fieldIndex].RangeUpper = rangeUpper;
         }
      }

      public override string ToString()
      {
         StringBuilder sb = new StringBuilder();

         for ( int index = 0; index < FieldCount; ++index )
         {
            sb.Append( _fieldControls[index].ToString() );

            if ( index < _dotControls.Length )
            {
               sb.Append( _dotControls[index].ToString() );
            }
         }

         return sb.ToString();
      }

      #endregion Public Methods

      #region Constructors

      public IPAddressControl()
      {
         BackColor = SystemColors.Window;

         ResetBackColorChanged();

         for ( int index = 0; index < _fieldControls.Length; ++index )
         {
            _fieldControls[index] = new FieldControl();

            _fieldControls[index].CreateControl();

            _fieldControls[index].FieldIndex = index;
            _fieldControls[index].Name = "FieldControl" + index.ToString( CultureInfo.InvariantCulture );
            _fieldControls[index].Parent = this;

            _fieldControls[index].CedeFocusEvent += new EventHandler<CedeFocusEventArgs>( OnCedeFocus );
            _fieldControls[index].Click += new EventHandler( OnSubControlClicked );
            _fieldControls[index].DoubleClick += new EventHandler( OnSubControlDoubleClicked );
            _fieldControls[index].GotFocus += new EventHandler( OnFieldGotFocus );
            _fieldControls[index].KeyDown += new KeyEventHandler( OnFieldKeyDown );
            _fieldControls[index].KeyPress += new KeyPressEventHandler( OnFieldKeyPressed );
            _fieldControls[index].KeyUp += new KeyEventHandler( OnFieldKeyUp );
            _fieldControls[index].LostFocus += new EventHandler( OnFieldLostFocus );
            _fieldControls[index].MouseClick += new MouseEventHandler( OnSubControlMouseClicked );
            _fieldControls[index].MouseDoubleClick += new MouseEventHandler( OnSubControlMouseDoubleClicked );
            _fieldControls[index].MouseEnter += new EventHandler( OnSubControlMouseEntered );
            _fieldControls[index].MouseHover += new EventHandler( OnSubControlMouseHovered );
            _fieldControls[index].MouseLeave += new EventHandler( OnSubControlMouseLeft );
            _fieldControls[index].MouseMove += new MouseEventHandler( OnSubControlMouseMoved );
            _fieldControls[index].PreviewKeyDown += new PreviewKeyDownEventHandler( OnFieldPreviewKeyDown );
            _fieldControls[index].TextChangedEvent += new EventHandler<TextChangedEventArgs>( OnFieldTextChanged );

            Controls.Add( _fieldControls[index] );

            if ( index < ( FieldCount - 1 ) )
            {
               _dotControls[index] = new DotControl();

               _dotControls[index].CreateControl();

               _dotControls[index].Name = "DotControl" + index.ToString( CultureInfo.InvariantCulture );
               _dotControls[index].Parent = this;

               _dotControls[index].Click += new EventHandler( OnSubControlClicked );
               _dotControls[index].DoubleClick += new EventHandler( OnSubControlDoubleClicked );
               _dotControls[index].MouseClick += new MouseEventHandler( OnSubControlMouseClicked );
               _dotControls[index].MouseDoubleClick += new MouseEventHandler( OnSubControlMouseDoubleClicked );
               _dotControls[index].MouseEnter += new EventHandler( OnSubControlMouseEntered );
               _dotControls[index].MouseHover += new EventHandler( OnSubControlMouseHovered );
               _dotControls[index].MouseLeave += new EventHandler( OnSubControlMouseLeft );
               _dotControls[index].MouseMove += new MouseEventHandler( OnSubControlMouseMoved );

               Controls.Add( _dotControls[index] );
            }
         }

         SetStyle( ControlStyles.AllPaintingInWmPaint, true );
         SetStyle( ControlStyles.ContainerControl, true );
         SetStyle( ControlStyles.OptimizedDoubleBuffer, true );
         SetStyle( ControlStyles.ResizeRedraw, true );
         SetStyle( ControlStyles.UserPaint, true );
         SetStyle( ControlStyles.FixedWidth, true );
         SetStyle( ControlStyles.FixedHeight, true );

         _referenceTextBox.AutoSize = true;

         Cursor = Cursors.IBeam;

         AutoScaleDimensions = new SizeF( 96F, 96F );
         AutoScaleMode = AutoScaleMode.Dpi;

         Size = MinimumSize;

         DragEnter += new DragEventHandler( IPAddressControl_DragEnter );
         DragDrop += new DragEventHandler( IPAddressControl_DragDrop );
      }

      #endregion // Constructors

      #region Protected Methods

      protected override void Dispose( bool disposing )
      {
         if ( disposing ) { Cleanup(); }
         base.Dispose( disposing );
      }

      protected override void OnBackColorChanged( EventArgs e )
      {
         base.OnBackColorChanged( e );
         _backColorChanged = true;
      }

      protected override void OnFontChanged( EventArgs e )
      {
         base.OnFontChanged( e );
         AdjustSize();
      }

      protected override void OnGotFocus( EventArgs e )
      {
         base.OnGotFocus( e );
         _focused = true;
         _fieldControls[0].TakeFocus( Direction.Forward, Selection.All );
      }

      protected override void OnLostFocus( EventArgs e )
      {
         if ( !Focused )
         {
            _focused = false;
            base.OnLostFocus( e );
         }
      }

      protected override void OnMouseEnter( EventArgs e )
      {
         if ( !_hasMouse )
         {
            _hasMouse = true;
            base.OnMouseEnter( e );
         }
      }

      protected override void OnMouseLeave( EventArgs e )
      {
         if ( !HasMouse )
         {
            base.OnMouseLeave( e );
            _hasMouse = false;
         }
      }

      protected override void OnPaint( PaintEventArgs e )
      {
         if ( null == e ) { throw new ArgumentNullException( "e" ); }

         base.OnPaint( e );

         Color backColor = BackColor;

         if ( !_backColorChanged )
         {
            if ( !Enabled || ReadOnly )
            {
               backColor = SystemColors.Control;
            }
         }

         using ( SolidBrush backgroundBrush = new SolidBrush( backColor ) )
         {
            e.Graphics.FillRectangle( backgroundBrush, ClientRectangle );
         }

         Rectangle rectBorder = new Rectangle( ClientRectangle.Left, ClientRectangle.Top,
            ClientRectangle.Width - 1, ClientRectangle.Height - 1 );

         switch ( BorderStyle )
         {
            case BorderStyle.Fixed3D:

               if ( Application.RenderWithVisualStyles )
               {
                  using ( Pen pen = new Pen( VisualStyleInformation.TextControlBorder ) )
                  {
                     e.Graphics.DrawRectangle( pen, rectBorder );
                  }
                  rectBorder.Inflate( -1, -1 );
                  e.Graphics.DrawRectangle( SystemPens.Window, rectBorder );
               }
               else
               {
                  ControlPaint.DrawBorder3D( e.Graphics, ClientRectangle, Border3DStyle.Sunken );
               }
               break;

            case BorderStyle.FixedSingle:

               ControlPaint.DrawBorder( e.Graphics, ClientRectangle,
                  SystemColors.WindowFrame, ButtonBorderStyle.Solid );
               break;
         }
      }

      protected override void OnSizeChanged( EventArgs e )
      {
         base.OnSizeChanged( e );
         AdjustSize();
      }

      #endregion // Protected Methods

      #region Private Properties

      private bool HasMouse
      {
         get
         {
            return DisplayRectangle.Contains( PointToClient( MousePosition ) );
         }
      }

      #endregion  // Private Properties

      #region Private Methods

      private void AdjustSize()
      {
         Size newSize = MinimumSize;

         if ( Width > newSize.Width )
         {
            newSize.Width = Width;
         }

         if ( Height > newSize.Height )
         {
            newSize.Height = Height;
         }

         if ( AutoHeight )
         {
            Size = new Size( newSize.Width, MinimumSize.Height );
         }
         else
         {
            Size = newSize;
         }

         LayoutControls();
      }

      private Size CalculateMinimumSize()
      {
         Size minimumSize = new Size( 0, 0 );

         foreach ( FieldControl fc in _fieldControls )
         {
            minimumSize.Width += fc.Width;
            minimumSize.Height = Math.Max( minimumSize.Height, fc.Height );
         }

         foreach ( DotControl dc in _dotControls )
         {
            minimumSize.Width += dc.Width;
            minimumSize.Height = Math.Max( minimumSize.Height, dc.Height );
         }

         switch ( BorderStyle )
         {
            case BorderStyle.Fixed3D:
               minimumSize.Width += 6;
               minimumSize.Height += ( GetSuggestedHeight() - minimumSize.Height );
               break;
            case BorderStyle.FixedSingle:
               minimumSize.Width += 4;
               minimumSize.Height += ( GetSuggestedHeight() - minimumSize.Height );
               break;
         }

         return minimumSize;
      }

      private void Cleanup()
      {
         foreach ( DotControl dc in _dotControls )
         {
            Controls.Remove( dc );
            dc.Dispose();
         }

         foreach ( FieldControl fc in _fieldControls )
         {
            Controls.Remove( fc );
            fc.Dispose();
         }

         _dotControls = null;
         _fieldControls = null;
      }

      private int GetSuggestedHeight()
      {
         _referenceTextBox.BorderStyle = BorderStyle;
         _referenceTextBox.Font = Font;
         return _referenceTextBox.Height;
      }

      [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Usage", "CA1806", Justification = "What should be done if ReleaseDC() doesn't work?" )]
      private static NativeMethods.TEXTMETRIC GetTextMetrics( IntPtr hwnd, Font font )
      {
         IntPtr hdc = NativeMethods.GetWindowDC( hwnd );

         NativeMethods.TEXTMETRIC textMetric;
         IntPtr hFont = font.ToHfont();

         try
         {
            IntPtr hFontPrevious = NativeMethods.SelectObject( hdc, hFont );
            NativeMethods.GetTextMetrics( hdc, out textMetric );
            NativeMethods.SelectObject( hdc, hFontPrevious );
         }
         finally
         {
            NativeMethods.ReleaseDC( hwnd, hdc );
            NativeMethods.DeleteObject( hFont );
         }

         return textMetric;
      }

      private void IPAddressControl_DragDrop( object sender, System.Windows.Forms.DragEventArgs e )
      {
         Text = e.Data.GetData( DataFormats.Text ).ToString();
      }

      private void IPAddressControl_DragEnter( object sender, System.Windows.Forms.DragEventArgs e )
      {
         if ( e.Data.GetDataPresent( DataFormats.Text ) )
         {
            e.Effect = DragDropEffects.Copy;
         }
         else
         {
            e.Effect = DragDropEffects.None;
         }
      }

      private void LayoutControls()
      {
         SuspendLayout();

         int difference = Width - MinimumSize.Width;

         Debug.Assert( difference >= 0 );

         int numOffsets = _fieldControls.Length + _dotControls.Length + 1;

         int div = difference / ( numOffsets );
         int mod = difference % ( numOffsets );

         int[] offsets = new int[numOffsets];

         for ( int index = 0; index < numOffsets; ++index )
         {
            offsets[index] = div;

            if ( index < mod )
            {
               ++offsets[index];
            }
         }

         int x = 0;
         int y = 0;

         switch ( BorderStyle )
         {
            case BorderStyle.Fixed3D:
               x = Fixed3DOffset.Width;
               y = Fixed3DOffset.Height;
               break;
            case BorderStyle.FixedSingle:
               x = FixedSingleOffset.Width;
               y = FixedSingleOffset.Height;
               break;
         }

         int offsetIndex = 0;

         x += offsets[offsetIndex++];

         for ( int i = 0; i < _fieldControls.Length; ++i )
         {
            _fieldControls[i].Location = new Point( x, y );

            x += _fieldControls[i].Width;

            if ( i < _dotControls.Length )
            {
               x += offsets[offsetIndex++];
               _dotControls[i].Location = new Point( x, y );
               x += _dotControls[i].Width;
               x += offsets[offsetIndex++];
            }
         }

         ResumeLayout( false );
      }

      private void OnCedeFocus( Object sender, CedeFocusEventArgs e )
      {
         switch ( e.Action )
         {
            case Action.Home:

               _fieldControls[0].TakeFocus( Action.Home );
               return;

            case Action.End:

               _fieldControls[FieldCount - 1].TakeFocus( Action.End );
               return;

            case Action.Trim:

               if ( e.FieldIndex == 0 )
               {
                  return;
               }

               _fieldControls[e.FieldIndex - 1].TakeFocus( Action.Trim );
               return;
         }

         if ( ( e.Direction == Direction.Reverse && e.FieldIndex == 0 ) ||
              ( e.Direction == Direction.Forward && e.FieldIndex == ( FieldCount - 1 ) ) )
         {
            return;
         }

         int fieldIndex = e.FieldIndex;

         if ( e.Direction == Direction.Forward )
         {
            ++fieldIndex;
         }
         else
         {
            --fieldIndex;
         }

         _fieldControls[fieldIndex].TakeFocus( e.Direction, e.Selection );
      }

      private void OnFieldGotFocus( Object sender, EventArgs e )
      {
         if ( !_focused )
         {
            _focused = true;
            base.OnGotFocus( EventArgs.Empty );
         }
      }

      private void OnFieldKeyDown( Object sender, KeyEventArgs e )
      {
         OnKeyDown( e );
      }

      private void OnFieldKeyPressed( Object sender, KeyPressEventArgs e )
      {
         OnKeyPress( e );
      }

      private void OnFieldPreviewKeyDown( Object sender, PreviewKeyDownEventArgs e )
      {
         OnPreviewKeyDown( e );
      }

      private void OnFieldKeyUp( Object sender, KeyEventArgs e )
      {
         OnKeyUp( e );
      }

      private void OnFieldLostFocus( Object sender, EventArgs e )
      {
         if ( !Focused )
         {
            _focused = false;
            base.OnLostFocus( EventArgs.Empty );
         }
      }

      private void OnFieldTextChanged( Object sender, TextChangedEventArgs e )
      {
         if ( null != FieldChangedEvent )
         {
            FieldChangedEventArgs args = new FieldChangedEventArgs();
            args.FieldIndex = e.FieldIndex;
            args.Text = e.Text;
            FieldChangedEvent( this, args );
         }

         OnTextChanged( EventArgs.Empty );
      }

      private void OnSubControlClicked( object sender, EventArgs e )
      {
         OnClick( e );
      }

      private void OnSubControlDoubleClicked( object sender, EventArgs e )
      {
         OnDoubleClick( e );
      }

      private void OnSubControlMouseClicked( object sender, MouseEventArgs e )
      {
         OnMouseClick( e );
      }

      private void OnSubControlMouseDoubleClicked( object sender, MouseEventArgs e )
      {
         OnMouseDoubleClick( e );
      }

      private void OnSubControlMouseEntered( object sender, EventArgs e )
      {
         OnMouseEnter( e );
      }

      private void OnSubControlMouseHovered( object sender, EventArgs e )
      {
         OnMouseHover( e );
      }

      private void OnSubControlMouseLeft( object sender, EventArgs e )
      {
         OnMouseLeave( e );
      }

      private void OnSubControlMouseMoved( object sender, MouseEventArgs e )
      {
         OnMouseMove( e );
      }

      private void Parse( String text )
      {
         Clear();

         if ( null == text )
         {
            return;
         }

         int textIndex = 0;

         int index = 0;

         for ( index = 0; index < _dotControls.Length; ++index )
         {
            int findIndex = text.IndexOf( _dotControls[index].Text, textIndex, StringComparison.Ordinal );

            if ( findIndex >= 0 )
            {
               _fieldControls[index].Text = text.Substring( textIndex, findIndex - textIndex );
               textIndex = findIndex + _dotControls[index].Text.Length;
            }
            else
            {
               break;
            }
         }

         _fieldControls[index].Text = text.Substring( textIndex );
      }

      // a hack to remove an FxCop warning
      private void ResetBackColorChanged()
      {
         _backColorChanged = false;
      }

      #endregion Private Methods

      #region Private Data

      private bool _autoHeight = true;
      private bool _backColorChanged;
      private BorderStyle _borderStyle = BorderStyle.Fixed3D;
      private DotControl[] _dotControls = new DotControl[FieldCount - 1];
      private FieldControl[] _fieldControls = new FieldControl[FieldCount];
      private bool _focused;
      private bool _hasMouse;
      private bool _readOnly;

      private Size Fixed3DOffset = new Size( 3, 3 );
      private Size FixedSingleOffset = new Size( 2, 2 );

      private TextBox _referenceTextBox = new TextBox();

      #endregion  // Private Data
   }
}