context: // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
  // KIND, EITHER EXPRESSED OR IMPLIED!
  //
  // IF YOU FIND ERRORS OR POSSIBLE IMPROVEMENTS, PLEASE LET ME KNOW.
  // MAYBE TOGETHER WE CAN SOLVE THIS.
  //
  // YOU MAY USE THIS CODE: HOWEVER THIS GRANTS NO FUTURE RIGHTS.
  
  using System;
  using System.Text;
  using System.Text.RegularExpressions;
  
  namespace Telnet
  {
  	/// <summary>
  	/// Implements a simple "screen". This is used by telnet.
  	/// <p>
  	/// The x (rows) and y (columns) values may have an offset. If the offset
  	/// is 0/0 the left upper corner is [0,0], or 0-based. With an offset of 1/1
  	/// the left upper corner is [1,1], or 1-based.
  	/// </p>
  	/// </summary>
  	/// <remarks>
  	/// The class is not thread safe (e.g. search in buffer and modification
  	/// of buffer must not happen. It is duty of the calling class to guarantee this.
  	/// </remarks>
  	public class VirtualScreen : IDisposable
  	{
  		/// <summary>
  		/// ASCII code for Space
  		/// </summary>
  		public const byte SPACE = 32;
  
  		/// <summary>
  		/// window size 
  		/// </summary>
  		public int Width 
  		{
  			get
  			{
  				if (this.vs==null)
  					return 0;
  				else
  					return this.vs.GetLength(0);
  			}
  		} // Width
  
  		/// <summary>
  		/// Window height
  		/// </summary>
  		public int Height 
  		{
  			get
  			{
  				if (this.vs==null)
  					return 0;
  				else
  					return this.vs.GetLength(1);
  			}
  		} // Width
  
  		// External cursor values allowing an offset and thus
  		// 0-based or 1-based coordinates
  		private int offsetx = 0;
  		private int offsety = 0;
  
  		/// <summary>
  		/// Cursor position with offset considered
  		/// </summary>
  		public int CursorX 
  		{
  			get
  			{
  				// 2004-09-01 fixed to plus based on mail of Steve
  				return this.CursorX0 + this.offsetx;
  			}
  			set
  			{
  				this.CursorX0 = value - this.offsetx;
  			}
  		} // X
  
  		/// <summary>
  		/// Cursor position with offset considered
  		/// </summary>
  		public int CursorY 
  		{
  			get
  			{
  				// 2004-09-01 fixed to plus based on mail of Steve
  				return this.CursorY0 + this.offsety;
  			}
  			set
  			{
  				this.CursorY0 = value - this.offsety;
  			}
  		} // Y
  		
  		/// <summary>
  		/// X Offset 
  		/// </summary>
  		public int CursorXLeft
  		{
  			get
  			{
  				return this.offsetx;
  			}
  		}
  
  		/// <summary>
  		/// X max value 
  		/// </summary>
  		public int CursorXRight
  		{
  			get
  			{
  				return this.Width - 1 + this.offsetx;
  			}
  		}
  		/// <summary>
  		/// Y max value 
  		/// </summary>
  		public int CursorYMax
  		{
  			get
  			{
  				return this.Height - 1 + this.offsety;
  			}
  		}
  		/// <summary>
  		/// Y max value 
  		/// </summary>
  		public int CursorYMin
  		{
  			get
  			{
  				return this.offsety;
  			}
  		}
  		/// <summary>
  		/// Reset the cursor to upper left corner
  		/// </summary>
  		public void CursorReset() 
  		{
  			this.CursorY0 = 0;
  			this.CursorX0 = 0;
  		}
  
  		/// <summary>
  		/// Move the cursor to the beginning of the next line
  		/// </summary>
  		public void CursorNextLine() 
  		{
  			this.Write("\n\r");
  		}
  
  		/// <summary>
  		/// Set the cursor (offset coordinates)
  		/// </summary>
  		/// <param name="x">X Position (lines) with offset considered</param>
  		/// <param name="y">Y Position (columns) with offset considered</param>
  		/// <remarks>
  		/// Use the method MoveCursorTo(x,y) when upscrolling should
  		/// be supported
  		/// </remarks>
  		public void CursorPosition(int x, int y) 
  		{
  			this.CursorX = x;
  			this.CursorY = y;
  		}
  
  		// zero based cursor positions
  		// for internal usage only
  		private int cursorx0 = 0; 
  		private int cursory0 = 0;
  		/// <summary>
  		/// 0-based coordinates for cursor, internally used.
  		/// </summary>
  		private int CursorX0
  		{
  			get
  			{
  				return this.cursorx0;
  			}
  			set 
  			{
  				if (value<=0)
  					this.cursorx0 = 0;
  				else if (value>=this.Width)
  					this.cursorx0 = this.Width - 1;
  				else
  					this.cursorx0 = value;
  			}
  		}
  		/// <summary>
  		/// 0-based coordinates for cursor, internally used
  		/// </summary>
  		private int CursorY0
  		{
  			get
  			{
  				return this.cursory0;
  			}
  			set 
  			{
  				if (value<=0)
  					this.cursory0 = 0;
  				else
  					this.cursory0 = value; // due to the scrolling behaviour we do not cut big values
  			}
  		}
  
  		// screen array [x,y]
  		private byte[,] vs = null;
  		// cache for screen as string
  		string screenString = null;
  		string screenStringLower = null;
  
  		// changed output since last hardcopy of the screen
  		private bool changedScreen = false;
  
  		// visible area (=> scrolled updwards)
  		// - cleared by clean screen
  		// - used by the MoveCursorTo and MoveCursor methods
  		// - used by WriteByte
  		// - NOT used by ScrollUp
  		private int visibleAreaY0top = 0;
  		private int visibleAreaY0bottom = 0;
  
  		/// <summary>
  		/// Changed screen buffer ?
  		/// </summary>
  		public bool ChangedScreen 
  		{
  			get 
  			{
  				return this.changedScreen;
  			}
  		}
  
  		/// <summary>
  		/// Constructor (offset 0/0)
  		/// </summary>
  		/// <param name="width">Screen's width</param>
  		/// <param name="height">Screen's height</param>
  		public VirtualScreen(int width, int height) : this(width, height, 0, 0)
  		{
  			// nothing here
  		}
  		
  		/// <summary>
  		/// Constructor
  		/// </summary>
  		/// <param name="width">Screen's width</param>
  		/// <param name="height">Screen's height</param>
  		/// <param name="xOffset">Screen coordinates are 0,1 .. based</param>
  		/// <param name="yOffset">Screen coordinates are 0,1 .. based</param>
  		public VirtualScreen(int width, int height, int xOffset, int yOffset) 
  		{
  			this.offsetx = xOffset;
  			this.offsety = yOffset;
  			this.vs = new byte[width,height];
  			this.CleanScreen();
  			this.changedScreen = false; // reset becuase constructor
  			this.visibleAreaY0top = 0;
  			this.visibleAreaY0bottom = height -1;
  			this.CursorReset();
  		}
  
  		/// <summary>
  		/// Clean the screen and reset cursor.
  		/// </summary>
  		/// <remarks>
  		/// Changes the output-flag and scrolledUp attribute!
  		/// </remarks>
  		public void CleanScreen() 
  		{
  			int lx = this.vs.GetLength(0);
  			int ly = this.vs.GetLength(1);
  			for (int y=0; y < ly; y++) 
  			{
  				for (int x=0; x < lx; x++) 
  				{
  					this.vs[x,y] = SPACE;
  				}
  			}
  			this.CursorReset(); // cursor back to beginning
  			this.changedScreen = true;
  			this.visibleAreaY0top = 0;
  			this.visibleAreaY0bottom = this.Height-1;
  		} // cleanScreen
  
  		/// <summary>
  		/// Cleans a screen area, all values are
  		/// considering any offset
  		/// </summary>
  		/// <remarks>
  		/// - Changes the output-flag!
  		/// - Visible area is considered
  		/// </remarks>
  		/// <param name="xstart">upper left corner (included)</param>
  		/// <param name="ystart">upper left corner (included)</param>
  		/// <param name="xend">lower right corner (included)</param>
  		/// <param name="yend">lower right corner (included)</param>
  		public void CleanScreen(int xstart, int ystart, int xend, int yend) 
  		{
  			if (this.vs == null || xend <= xstart || yend <= ystart || xstart < this.offsetx || xend < this.offsetx || ystart < this.offsety || yend < this.offsety)
  				return; // nothing to do
  
  			int x0start = xstart - this.offsetx;
  			int y0start = ystart - this.offsety - this.visibleAreaY0top;
  			if (y0start<0)
  				y0start=0; // only visible area
  			int x0end = xend - this.offsetx;
  			int y0end = yend - this.offsety - this.visibleAreaY0top;
  			if (y0end<0)
  				return; // nothing to do
  
  			int lx = this.vs.GetLength(0);
  			int ly = this.vs.GetLength(1);
  
  			if (x0end >= lx)
  				x0end = lx -1;
  			if (y0end >= ly)
  				y0end = ly -1;
  			
  			for (int y=y0start; y <=y0end; y++) 
  			{
  				for (int x=x0start; x <=x0end; x++) 
  				{
  					this.vs[x,y] = SPACE;
  				}
  			}
  			this.changedScreen = true;
  		}
  
  		/// <summary>
  		/// Clean the current line
  		/// </summary>
  		/// <remarks>
  		/// - Changes the output-flag! <br/>
  		/// - Visible area is considered
  		/// </remarks>
  		/// <param name="xStart">X with offset considered</param>
  		/// <param name="xEnd">X with offset considered</param>
  		public void CleanLine(int xStart, int xEnd) 
  		{
  			int x0s = xStart - this.offsetx;
  			int x0e = xEnd - this.offsetx;
  			
  			if (xStart < xEnd)
  				return;
  			if (x0s<0)
  				x0s=0;
  			if (x0e>=this.Width)
  				x0e = this.Width -1;
  			
  			int y = this.cursory0 - this.visibleAreaY0top;
  			if (this.vs == null || y<0 || y > this.vs.GetLength(1))
  				return;
  
  			for (int x=x0s; x <=x0e; x++) 
  			{
  				this.vs[x,y] = SPACE;
  			}
  			this.changedScreen = true;
  		}
  
  		/// <summary>
  		/// Clean screen including the cursor position
  		/// </summary>
  		/// - Changes the output-flag! <br/>
  		/// - Visible area is considered
  		public void CleanToCursor() 
  		{
  			int y = this.CursorY -1; // line before
  			if (y>=this.offsety)
  				this.CleanScreen(this.CursorXLeft, this.offsety, this.CursorXRight, y);
  			this.CleanLine(this.CursorXLeft, this.CursorX);
  			this.changedScreen = true;
  		}
  
  		/// <summary>
  		/// Clean screen including the cursor position
  		/// </summary>
  		/// - Changes the output-flag! <br/>
  		/// - Visible area is considered
  		public void CleanFromCursor() 
  		{
  			int y = this.CursorY + 1; // line before
  			if (y <= this.visibleAreaY0bottom + this.offsety)
  				this.CleanScreen(this.CursorXLeft, y, this.CursorXRight, this.visibleAreaY0bottom + this.offsety);
  			this.CleanLine(this.CursorX, this.CursorXRight);
  			this.changedScreen = true;
  		}
  
  		/// <summary>
  		/// Scrolls up about n lines.
  		/// </summary>
  		/// <param name="lines"></param>
  		/// <remarks>
  		/// Changes the output-flag! <br/>
  		/// TODO: Do we have to change the coordinates offset?
  		/// Is line 5 after 2 lines scrolling now line 3 or still 5?
  		/// </remarks>
  		/// <returns>number of lines scrolled</returns>
  		/// 
  		public int ScrollUp(int lines) 
  		{
  			// scrolls up about n lines
  			if (lines < 1)
  				return 0;
  			
  			int lx = this.vs.GetLength(0);
  			int ly = this.vs.GetLength(1);
  
  			if (lines >= ly) 
  			{
  				// we need to save the visible are info
  				int vat = this.visibleAreaY0top;
  				int vab = this.visibleAreaY0bottom;
  				this.CleanScreen();
  				this.visibleAreaY0top = vat;
  				this.visibleAreaY0bottom = vab;
  			} 
  			else 
  			{
  
gt: 				for (int y=lines; y < ly; y++) 