context: /*
      Copyright (C) 2014-2019 de4dot@gmail.com
  
      This file is part of dnSpy
  
      dnSpy is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.
  
      dnSpy is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU General Public License for more details.
  
      You should have received a copy of the GNU General Public License
      along with dnSpy.  If not, see <http://www.gnu.org/licenses/>.
  */
  
  using System;
  using System.Collections.Generic;
  using System.Collections.ObjectModel;
  using System.Diagnostics;
  using System.Linq;
  using System.Text;
  using dnSpy.Contracts.Hex;
  using VST = Microsoft.VisualStudio.Text;
  
  namespace dnSpy.Hex {
  	sealed class HexBufferLineFormatterImpl : HexBufferLineFormatter {
  		const int DEFAULT_GROUP_SIZE_IN_BYTES = 8;
  
  		static readonly HexValueFormatter[] valueFormatters = new HexValueFormatter[HexBufferLineFormatterOptions.HexValuesDisplayFormat_Last - HexBufferLineFormatterOptions.HexValuesDisplayFormat_First + 1] {
  			new HexByteValueFormatter(),
  			new HexUInt16ValueFormatter(),
  			new HexUInt32ValueFormatter(),
  			new HexUInt64ValueFormatter(),
  			new HexSByteValueFormatter(),
  			new HexInt16ValueFormatter(),
  			new HexInt32ValueFormatter(),
  			new HexInt64ValueFormatter(),
  			new DecimalByteValueFormatter(),
  			new DecimalUInt16ValueFormatter(),
  			new DecimalUInt32ValueFormatter(),
  			new DecimalUInt64ValueFormatter(),
  			new DecimalSByteValueFormatter(),
  			new DecimalInt16ValueFormatter(),
  			new DecimalInt32ValueFormatter(),
  			new DecimalInt64ValueFormatter(),
  			new SingleValueFormatter(),
  			new DoubleValueFormatter(),
  			new Bit8ValueFormatter(),
  			new HexUInt16BigEndianValueFormatter(),
  			new HexUInt32BigEndianValueFormatter(),
  			new HexUInt64BigEndianValueFormatter(),
  			new HexInt16BigEndianValueFormatter(),
  			new HexInt32BigEndianValueFormatter(),
  			new HexInt64BigEndianValueFormatter(),
  			new DecimalUInt16BigEndianValueFormatter(),
  			new DecimalUInt32BigEndianValueFormatter(),
  			new DecimalUInt64BigEndianValueFormatter(),
  			new DecimalInt16BigEndianValueFormatter(),
  			new DecimalInt32BigEndianValueFormatter(),
  			new DecimalInt64BigEndianValueFormatter(),
  			new SingleBigEndianValueFormatter(),
  			new DoubleBigEndianValueFormatter(),
  		};
  
  		static HexBufferLineFormatterImpl() {
  			for (int i = 0; i < valueFormatters.Length; i++) {
  				if (valueFormatters[i].Format != (HexValuesDisplayFormat)i)
  					throw new InvalidOperationException();
  			}
  			foreach (var fi in typeof(HexValuesDisplayFormat).GetFields()) {
  				if (!fi.IsLiteral)
  					continue;
  				var value = (HexValuesDisplayFormat)fi.GetValue(null)!;
  				if (value < HexBufferLineFormatterOptions.HexValuesDisplayFormat_First || value > HexBufferLineFormatterOptions.HexValuesDisplayFormat_Last)
  					throw new InvalidOperationException();
  			}
  		}
  
  		public override HexBuffer Buffer => buffer;
  		public override HexBufferSpan BufferSpan => HexBufferSpan.FromBounds(new HexBufferPoint(buffer, startPosition), new HexBufferPoint(buffer, endPosition));
  		public override HexPosition LineCount { get; }
  		public override int CharsPerLine { get; }
  		public override int BytesPerLine => (int)bytesPerLine;
  		public override int GroupSizeInBytes => groupSizeInBytes;
  		public override bool ShowOffset => showOffset;
  		public override bool OffsetLowerCaseHex => offsetFormatter.LowerCaseHex;
  		public override HexOffsetFormat OffsetFormat => offsetFormatter.Format;
  		public override HexPosition StartPosition => startPosition;
  		public override HexPosition EndPosition => endPosition;
  		public override HexPosition BasePosition => basePosition;
  		public override bool UseRelativePositions => useRelativePositions;
  		public override bool ShowValues => showValues;
  		public override bool ValuesLowerCaseHex => valuesLowerCaseHex;
  		public override int OffsetBitSize => offsetFormatter.OffsetBitSize;
  		public override HexValuesDisplayFormat ValuesFormat => valueFormatter.Format;
  		public override int BytesPerValue => valueFormatter.ByteCount;
  		public override bool ShowAscii => showAscii;
  		public override ReadOnlyCollection<HexColumnType> ColumnOrder => columnOrder;
  		public override VST.Span OffsetSpan { get; }
  		public override VST.Span ValuesSpan { get; }
  		public override VST.Span AsciiSpan { get; }
  		public override ReadOnlyCollection<HexGroupInformation> ValuesGroup { get; }
  		public override ReadOnlyCollection<HexGroupInformation> AsciiGroup { get; }
  
  		readonly HexBuffer buffer;
  		readonly StringBuilder stringBuilder;
  		readonly List<HexCell> cellList;
  		readonly bool useRelativePositions;
  		readonly HexPosition startPosition;
  		readonly HexPosition endPosition;
  		readonly HexPosition basePosition;
  		readonly HexOffsetFormatter offsetFormatter;
  		readonly HexValueFormatter valueFormatter;
  		readonly ulong bytesPerLine;
  		readonly int groupSizeInBytes;
  		readonly bool showOffset;
  		readonly bool showValues;
  		readonly bool showAscii;
  		readonly bool valuesLowerCaseHex;
  
  		readonly ReadOnlyCollection<HexColumnType> columnOrder;
  		static readonly HexColumnType[] defaultColumnOrders = new HexColumnType[3] {
  			HexColumnType.Offset,
  			HexColumnType.Values,
  			HexColumnType.Ascii,
  		};
  
  		public HexBufferLineFormatterImpl(HexBuffer buffer, HexBufferLineFormatterOptions options) {
  			if (options is null)
  				throw new ArgumentNullException(nameof(options));
  			if (options.CharsPerLine < 0)
  				throw new ArgumentOutOfRangeException(nameof(options));
  			if (options.BytesPerLine < HexBufferLineFormatterOptions.MinBytesPerLine || options.BytesPerLine > HexBufferLineFormatterOptions.MaxBytesPerLine)
  				throw new ArgumentOutOfRangeException(nameof(options));
  			if (options.GroupSizeInBytes < 0)
  				throw new ArgumentOutOfRangeException(nameof(options));
  			if (options.OffsetFormat < HexBufferLineFormatterOptions.HexOffsetFormat_First || options.OffsetFormat > HexBufferLineFormatterOptions.HexOffsetFormat_Last)
  				throw new ArgumentOutOfRangeException(nameof(options));
  			if (options.StartPosition >= HexPosition.MaxEndPosition)
  				throw new ArgumentOutOfRangeException(nameof(options));
  			if (options.EndPosition > HexPosition.MaxEndPosition)
  				throw new ArgumentOutOfRangeException(nameof(options));
  			if (options.StartPosition > options.EndPosition)
  				throw new ArgumentOutOfRangeException(nameof(options));
  			if (options.BasePosition >= HexPosition.MaxEndPosition)
  				throw new ArgumentOutOfRangeException(nameof(options));
  			if (options.OffsetBitSize < HexBufferLineFormatterOptions.MinOffsetBitSize || options.OffsetBitSize > HexBufferLineFormatterOptions.MaxOffsetBitSize)
  				throw new ArgumentOutOfRangeException(nameof(options));
  			if (options.ValuesFormat < HexBufferLineFormatterOptions.HexValuesDisplayFormat_First || options.ValuesFormat > HexBufferLineFormatterOptions.HexValuesDisplayFormat_Last)
  				throw new ArgumentOutOfRangeException(nameof(options));
  
  			this.buffer = buffer ?? throw new ArgumentNullException(nameof(buffer));
  			columnOrder = TryCreateColumns(options.ColumnOrder ?? defaultColumnOrders) ?? throw new ArgumentOutOfRangeException(nameof(options));
  			cellList = new List<HexCell>();
  			useRelativePositions = options.UseRelativePositions;
  			startPosition = options.StartPosition;
  			endPosition = options.EndPosition;
  			basePosition = options.BasePosition;
  			showOffset = options.ShowOffset;
  			showValues = options.ShowValues;
  			showAscii = options.ShowAscii;
  			valuesLowerCaseHex = options.ValuesLowerCaseHex;
  
  			int bitSize = options.OffsetBitSize;
  			if (bitSize == 0)
  				bitSize = CalculateOffsetBitSize();
  			offsetFormatter = CreateOffsetFormatter(options.OffsetFormat, bitSize, options.OffsetLowerCaseHex);
  			valueFormatter = valueFormatters[(int)options.ValuesFormat];
  
  			bytesPerLine = (ulong)options.BytesPerLine;
  			if (bytesPerLine == 0)
  				bytesPerLine = (ulong)CalculateBytesPerLine(options.CharsPerLine);
  			if (bytesPerLine % (ulong)valueFormatter.ByteCount != 0)
  				bytesPerLine = bytesPerLine - bytesPerLine % (ulong)valueFormatter.ByteCount + (ulong)valueFormatter.ByteCount;
  			if (bytesPerLine > (ulong)HexBufferLineFormatterOptions.MaxBytesPerLine)
  				bytesPerLine = (ulong)(HexBufferLineFormatterOptions.MaxBytesPerLine / valueFormatter.ByteCount * valueFormatter.ByteCount);
  			if (bytesPerLine <= 0)
  				throw new InvalidOperationException();
  			groupSizeInBytes = options.GroupSizeInBytes;
  			if (groupSizeInBytes == 0)
  				groupSizeInBytes = DEFAULT_GROUP_SIZE_IN_BYTES;
  			if ((ulong)groupSizeInBytes > bytesPerLine)
  				groupSizeInBytes = (int)bytesPerLine;
  			groupSizeInBytes = (groupSizeInBytes + valueFormatter.ByteCount - 1) / valueFormatter.ByteCount * valueFormatter.ByteCount;
  			if (groupSizeInBytes <= 0)
  				throw new InvalidOperationException();
  			CharsPerLine = CalculateCharsPerLine();
  			stringBuilder = new StringBuilder(CharsPerLine);
  
  			var totalBytes = options.EndPosition - options.StartPosition;
  			Debug.Assert(totalBytes <= new HexPosition(1, 0));
  			if (totalBytes == 0)
  				LineCount = 1;
  			else if (bytesPerLine == 1)
  				LineCount = totalBytes;
  			else if (totalBytes == new HexPosition(1, 0)) {
  				// 2^64 / x => (2^64 - x + x) / x => (2^64 - x) / x + 1
  				Debug.Assert(bytesPerLine > 1);
  				ulong d = (0UL - bytesPerLine) / bytesPerLine + 1UL;
  				ulong r = 0UL - (d * bytesPerLine);
  				LineCount = d + (r == 0UL ? 0UL : 1UL);
  			}
  			else {
  				ulong v = totalBytes.ToUInt64();
  				LineCount = v / bytesPerLine + (v % bytesPerLine == 0 ? 0UL : 1UL);
  			}
  			if (LineCount == 0)
  				throw new InvalidOperationException();
  			CalculateColumnSpans(out var offsetSpan, out var valuesSpan, out var asciiSpan);
  			OffsetSpan = offsetSpan;
  			ValuesSpan = valuesSpan;
  			AsciiSpan = asciiSpan;
  
  			var group = new List<HexGroupInformation>();
  			CalculateValuesGroupSpans(group);
  			ValuesGroup = new ReadOnlyCollection<HexGroupInformation>(group.ToArray());
  			CalculateAsciiGroupSpans(group);
  			AsciiGroup = new ReadOnlyCollection<HexGroupInformation>(group.ToArray());
  			if (ShowValues == ShowAscii && ValuesGroup.Count != AsciiGroup.Count)
  				throw new InvalidOperationException();
  		}
  
  		void CalculateValuesGroupSpans(List<HexGroupInformation> list) {
  			list.Clear();
  			if (!ShowValues)
  				return;
  
  			int bytePos = 0;
  			int pos = 0;
  			bool isGroup0 = true;
  			while (bytePos < BytesPerLine) {
  				int groupByteEnd = bytePos + groupSizeInBytes;
  				int groupStart = pos;
  				bool needSep = false;
  				while (bytePos < BytesPerLine && bytePos < groupByteEnd) {
  					if (needSep)
  						pos++;
  					needSep = true;
  					pos += valueFormatter.FormattedLength;
  					bytePos = bytePos + valueFormatter.ByteCount;
  				}
  				int groupIndex = isGroup0 ? 0 : 1;
  				var span = VST.Span.FromBounds(groupStart, pos);
  				VST.Span fullSpan;
  				if (bytePos < BytesPerLine) {
  					fullSpan = new VST.Span(span.Start, span.Length + 1);
  					pos++;
  				}
  				else
  					fullSpan = span;
  				list.Add(new HexGroupInformation(groupIndex, fullSpan, span));
  				isGroup0 = !isGroup0;
  			}
  			if (pos != ValuesSpan.Length)
  				throw new InvalidOperationException();
  		}
  
  		void CalculateAsciiGroupSpans(List<HexGroupInformation> list) {
  			list.Clear();
  			if (!ShowAscii)
  				return;
  
  			int pos = 0;
  			bool isGroup0 = true;
  			while (pos < BytesPerLine) {
  				int groupStart = pos;
  				pos += Math.Min(BytesPerLine - pos, groupSizeInBytes);
  
gt: 				int groupIndex = isGroup0 ? 0 : 1;