<commit_before>﻿using System;
using System.IO;
using static System.Console;
using static System.IO.File;
using static System.IO.Path;

//TODO: Edit mode

namespace ConHexView
{
    static class HexView
    {
        #region Constants
        const int SCROLL_LINE = 0x10;
        const int SCROLL_SECTION = 0x80;
        const int SCROLL_PAGE = 0x0100;
        const string NAME_OFFSET = "Offset";
        #endregion

        #region Properties
        /// <summary>
        /// Index.
        /// </summary>
        static int CurrentOffset = 0;
        
        struct CurrentFile
        {
            static internal string Path
            {
                get; set;
            }

            static internal long Length
            {
                get; set;
            }

            static internal string Filename
            {
                get
                {
                    if (Path != null && Path != string.Empty)
                        return GetFileName(Path);
                    else
                        return string.Empty;
                }
            }

            static internal string FilenameWithoutExtension
            {
                get
                {
                    if (Path != null && Path != string.Empty)
                        return GetFileNameWithoutExtension(Path);
                    else
                        return string.Empty;
                }
            }
        }

        /// <summary>
        /// Current <see cref="OffsetViewMode"/> mode.
        /// </summary>
        static OffsetViewMode CurrentOffsetViewMode;

        /// <summary>
        /// Offset height.
        /// </summary>
        static int OffsetHeight = WindowHeight - 5;

        static byte[,] Buffer = new byte[OffsetHeight, 16];

        readonly static ConsoleColor fgOriginal = ForegroundColor;
        readonly static ConsoleColor bgOriginal = BackgroundColor;
        #endregion

        #region Enumerations
        /// <summary>
        /// Enumeration of different offset views.
        /// </summary>
        public enum OffsetViewMode
        {
            // Offset h
            // 00000010
            Hexadecimal,
            // Offset d
            // 00000016
            Decimal,
            // Offset o
            // 00000020
            Octal
        }
        #endregion

        #region Public methods
        public static void Open(string pFilePath)
        {
            Open(pFilePath, OffsetViewMode.Hexadecimal);
        }

        public static void Open(string pFilePath, OffsetViewMode pOffsetViewMode)
        {
            if (!Exists(pFilePath))
                throw new FileNotFoundException
                {
                    Source = pFilePath
                };

            CurrentFile.Path = pFilePath;
            CurrentOffsetViewMode = pOffsetViewMode;

            UpdateTitleMap();
            UpdateOffsetMap();
            UpdateInfoMap();
            PlaceControlMap();

            Read();

            do
            {
                UpdateMainScreen();
            } while (ReadUserKey());
        }
        #endregion

        #region Private methods
        /// <summary>
        /// Read the current file.
        /// </summary>
        static void Read()
        {
            Read(0);
        }

        /// <summary>
        /// Read the current file at a certain position.
        /// </summary>
        /// <param name="pBaseOffset">Position.</param>
        static void Read(int pBaseOffset)
        {
            //TODO: Do smart reading to kill the input lag (you know)

            using (StreamReader sr = new StreamReader(CurrentFile.Path))
            {
                sr.BaseStream.Position = pBaseOffset;
                CurrentFile.Length = sr.BaseStream.Length;

                for (int y = 0; y < OffsetHeight; y++)
                {
                    for (int x = 0; x < 16; x++)
                    {
                        Buffer[y, x] = (byte)sr.Read();
                    }
                }
            }
        }

        /// <summary>
        /// Read the user's key.
        /// </summary>
        /// <returns><see cref="true"/> if using the program.</returns>
        static bool ReadUserKey()
        {
            ConsoleKeyInfo cki = ReadKey(true);

            switch (cki.Key)
            {
                case ConsoleKey.Escape:
                    return false;

                case ConsoleKey.X:
                    if (cki.Modifiers == ConsoleModifiers.Control)
                        return Exit();
                    break;

                case ConsoleKey.I:
                    if (cki.Modifiers == ConsoleModifiers.Control)
                        //TODO: Better Info function
                        Message($"Size: {CurrentFile.Length}");
                    break;

                case ConsoleKey.Home:
                    CurrentOffset = 0;
                    ReadAndUpdate(CurrentOffset);
                    break;
                case ConsoleKey.End:
                    CurrentOffset = (int)CurrentFile.Length - OffsetHeight;
                    ReadAndUpdate(CurrentOffset);
                    break;

                case ConsoleKey.PageUp:
                    if (CurrentOffset - SCROLL_PAGE >= 0)
                    {
                        CurrentOffset -= SCROLL_PAGE;
                        ReadAndUpdate(CurrentOffset);
                    }
                    break;
                case ConsoleKey.PageDown:
                    if (CurrentOffset + SCROLL_PAGE < CurrentFile.Length)
                    {
                        CurrentOffset += SCROLL_PAGE;
                        ReadAndUpdate(CurrentOffset);
                    }
                    break;
                    
                case ConsoleKey.UpArrow:
                    if (CurrentOffset - SCROLL_LINE >= 0)
                    {
                        CurrentOffset -= SCROLL_LINE;
                        ReadAndUpdate(CurrentOffset);
                    }
                    break;
                case ConsoleKey.DownArrow:
                    if (CurrentOffset + SCROLL_LINE < CurrentFile.Length)
                    {
                        CurrentOffset += SCROLL_LINE;
                        ReadAndUpdate(CurrentOffset);
                    }
                    break;
            }

            return true;
        }

        static void ReadAndUpdate(int pOffset)
        {
            Read(pOffset);
            UpdateMainScreen();
            UpdateInfoMap();
        }

        /// <summary>
        /// Update the section of the screen with the data.
        /// </summary>
        static void UpdateMainScreen()
        {
            SetCursorPosition(0, 2);

            //TODO: Find a way to stop rendering before total length
            // Hint:
            // CurrentOffset [+ index] <= CurrentFile.Length

            for (int y = 0; y < OffsetHeight; y++)
            {
                switch (CurrentOffsetViewMode)
                {
                    case OffsetViewMode.Hexadecimal:
                        // 00000010
                        Write($"{((y * 0x10) + CurrentOffset).ToString("X8")}  ");
                        break;
                    case OffsetViewMode.Decimal:
                        //       16
                        Write($"{(y * 16) + CurrentOffset, 8}  ");
                        break;
                    case OffsetViewMode.Octal:
                        //       20
                        Write($"{Convert.ToString((y * 16) + CurrentOffset, 8), 8}  ");
                        break;
                }


                for (int x = 0; x < 16; x++)
                {
                    Write($"{Buffer[y, x].ToString("X2")} ");
                }

                Write(" ");
                
                for (int x = 0; x < 16; x++)
                {
                    Write($"{Buffer[y, x].ToChar()}");
                }

                WriteLine();
            }
        }

        /// <summary>
        /// Update the upper bar.
        /// </summary>
        static void UpdateTitleMap()
        {
            ToggleColors();
            
            SetCursorPosition(0, 0);
            // Should I include the project's name in the upper bar?
            Write($"{CurrentFile.Filename}");
            Write(new string(' ', WindowWidth - CurrentFile.Filename.Length));

            ResetColor();
        }

        /// <summary>
        /// Update the offset map (left view)
        /// </summary>
        static void UpdateOffsetMap()
        {
            SetCursorPosition(0, 1);
            Write($"Offset {CurrentOffsetViewMode.GetChar()}  00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
        }

        /// <summary>
        /// Update the data map (right view)
        /// </summary>
        static void UpdateInfoMap()
        {
            SetCursorPosition(0, WindowHeight - 3);
            // CAN'T THIS LINE BE ANY LONGER?
            Write($"{NAME_OFFSET} (DEC): {CurrentOffset, -10} | {NAME_OFFSET} (HEX): {CurrentOffset.ToString("X8")} | {NAME_OFFSET} (OCT): {Convert.ToString(CurrentOffset, 8), -10}");
        }

        /// <summary>
        /// Places the control map on screen (e.g. ^D Die)
        /// </summary>
        static void PlaceControlMap()
        {
            SetCursorPosition(0, WindowHeight - 2);

            ToggleColors();
            Write("^K");
            ResetColor();
            Write(" Help         ");

            ToggleColors();
            Write("^F");
            ResetColor();
            Write(" Find         ");

            ToggleColors();
            Write("^G");
            ResetColor();
            Write(" Goto line    ");

            ToggleColors();
            Write("^H");
            ResetColor();
            Write(" Replace      ");

            ToggleColors();
            Write("^E");
            ResetColor();
            Write(" Edit mode");

            WriteLine();

            ToggleColors();
            Write("^X");
            ResetColor();
            Write(" Exit         ");

            ToggleColors();
            Write("^I");
            ResetColor();
            Write(" Info         ");

            ToggleColors();
            Write("^D");
            ResetColor();
            Write(" Dump         ");

            ToggleColors();
            Write("^V");
            ResetColor();
            Write(" Offset view  ");

            ToggleColors();
            Write("^A");
            ResetColor();
            Write(" Data view");
        }

        /// <summary>
        /// Toggles current ForegroundColor to black
        /// and BackgroundColor to gray.
        /// </summary>
        static void ToggleColors()
        {
            ForegroundColor = ConsoleColor.Black;
            BackgroundColor = ConsoleColor.Gray;
        }

        /// <summary>
        /// Displays a message on screen to inform the user.
        /// </summary>
        /// <param name="pMessage">Message to show.</param>
        static void Message(string pMessage)
        {
            string msg = $"[ {pMessage} ]";

            SetCursorPosition((WindowWidth / 2) - (msg.Length / 2), WindowHeight - 3);
            ToggleColors();

            Write(msg);

            ResetColor();
        }

        static int ReadValue()
        {
            //TODO: int ReadValue()
            throw new NotImplementedException();
        }

        /// <summary>
        /// The user is exiting the application.
        /// So we prepare the departure.
        /// </summary>
        /// <returns>Always true.</returns>
        /// <remarks>It's false due to the while loop.</remarks>
        static bool Exit()
        {
            Clear();

            return false;
        }
        #endregion

        #region Type extensions
        /// <summary>
        /// Returns a safe character for the console (cmd) to display.
        /// </summary>
        /// <param name="pIn">Byte to transform.</param>
        /// <returns>Safe console character.</returns>
        static char ToChar(this byte pIn)<commit_msg>Notes<commit_after>﻿using System;
using System.IO;
using static System.Console;
using static System.IO.File;
using static System.IO.Path;

//TODO: Edit mode

/*
    Box of ideas (Lazy TODO list)
    - --scrollable (BufferHeight) (if possible)
    - currenfile fileinfo
    - --dump
    - Scrollbar (-ish)
    - Top right: Insert(INS)/Overwrite(OVR)
    - Help section (F1)
    - Search: /regex/ (Begins with && ends with)
    - Info: Lines, Length
    - no args -> empty buffer
    - Message() -> [ Example message! ]
    - Message(ProgressBar=bool) -> Progress bar (Dump) -> [ Done! ]
*/

namespace ConHexView
{
    static class HexView
    {
        #region Constants
        const int SCROLL_LINE = 0x10;
        const int SCROLL_SECTION = 0x80;
        const int SCROLL_PAGE = 0x0100;
        const string NAME_OFFSET = "Offset";
        #endregion

        #region Properties
        /// <summary>
        /// Index.
        /// </summary>
        static int CurrentOffset = 0;
        
        struct CurrentFile
        {
            static internal string Path
            {
                get; set;
            }

            static internal long Length
            {
                get; set;
            }

            static internal string Filename
            {
                get
                {
                    if (Path != null && Path != string.Empty)
                        return GetFileName(Path);
                    else
                        return string.Empty;
                }
            }

            static internal string FilenameWithoutExtension
            {
                get
                {
                    if (Path != null && Path != string.Empty)
                        return GetFileNameWithoutExtension(Path);
                    else
                        return string.Empty;
                }
            }
        }

        /// <summary>
        /// Current <see cref="OffsetViewMode"/> mode.
        /// </summary>
        static OffsetViewMode CurrentOffsetViewMode;

        /// <summary>
        /// Offset height.
        /// </summary>
        static int OffsetHeight = WindowHeight - 5;

        static byte[,] Buffer = new byte[OffsetHeight, 16];

        readonly static ConsoleColor fgOriginal = ForegroundColor;
        readonly static ConsoleColor bgOriginal = BackgroundColor;
        #endregion

        #region Enumerations
        /// <summary>
        /// Enumeration of different offset views.
        /// </summary>
        public enum OffsetViewMode
        {
            // Offset h
            // 00000010
            Hexadecimal,
            // Offset d
            // 00000016
            Decimal,
            // Offset o
            // 00000020
            Octal
        }
        #endregion

        #region Public methods
        public static void Open(string pFilePath)
        {
            Open(pFilePath, OffsetViewMode.Hexadecimal);
        }

        public static void Open(string pFilePath, OffsetViewMode pOffsetViewMode)
        {
            if (!Exists(pFilePath))
                throw new FileNotFoundException
                {
                    Source = pFilePath
                };

            CurrentFile.Path = pFilePath;
            CurrentOffsetViewMode = pOffsetViewMode;

            UpdateTitleMap();
            UpdateOffsetMap();
            UpdateInfoMap();
            PlaceControlMap();

            Read();

            do
            {
                UpdateMainScreen();
            } while (ReadUserKey());
        }
        #endregion

        #region Private methods
        /// <summary>
        /// Read the current file.
        /// </summary>
        static void Read()
        {
            Read(0);
        }

        /// <summary>
        /// Read the current file at a certain position.
        /// </summary>
        /// <param name="pBaseOffset">Position.</param>
        static void Read(int pBaseOffset)
        {
            //TODO: Do smart reading to kill the input lag (you know)

            using (StreamReader sr = new StreamReader(CurrentFile.Path))
            {
                sr.BaseStream.Position = pBaseOffset;
                CurrentFile.Length = sr.BaseStream.Length;

                for (int y = 0; y < OffsetHeight; y++)
                {
                    for (int x = 0; x < 16; x++)
                    {
                        Buffer[y, x] = (byte)sr.Read();
                    }
                }
            }
        }

        /// <summary>
        /// Read the user's key.
        /// </summary>
        /// <returns><see cref="true"/> if using the program.</returns>
        static bool ReadUserKey()
        {
            ConsoleKeyInfo cki = ReadKey(true);

            switch (cki.Key)
            {
                case ConsoleKey.Escape:
                    return Exit();

                case ConsoleKey.X:
                    if (cki.Modifiers == ConsoleModifiers.Control)
                        return Exit();
                    break;

                case ConsoleKey.I:
                    if (cki.Modifiers == ConsoleModifiers.Control)
                        //TODO: Better Info function
                        Message($"Size: {CurrentFile.Length}");
                    break;

                case ConsoleKey.Home:
                    CurrentOffset = 0;
                    ReadAndUpdate(CurrentOffset);
                    break;
                case ConsoleKey.End:
                    CurrentOffset = (int)CurrentFile.Length - OffsetHeight;
                    ReadAndUpdate(CurrentOffset);
                    break;

                case ConsoleKey.PageUp:
                    if (CurrentOffset - SCROLL_PAGE >= 0)
                    {
                        CurrentOffset -= SCROLL_PAGE;
                        ReadAndUpdate(CurrentOffset);
                    }
                    break;
                case ConsoleKey.PageDown:
                    if (CurrentOffset + SCROLL_PAGE < CurrentFile.Length)
                    {
                        CurrentOffset += SCROLL_PAGE;
                        ReadAndUpdate(CurrentOffset);
                    }
                    break;
                    
                case ConsoleKey.UpArrow:
                    if (CurrentOffset - SCROLL_LINE >= 0)
                    {
                        CurrentOffset -= SCROLL_LINE;
                        ReadAndUpdate(CurrentOffset);
                    }
                    break;
                case ConsoleKey.DownArrow:
                    if (CurrentOffset + SCROLL_LINE < CurrentFile.Length)
                    {
                        CurrentOffset += SCROLL_LINE;
                        ReadAndUpdate(CurrentOffset);
                    }
                    break;
            }

            return true;
        }

        static void ReadAndUpdate(int pOffset)
        {
            Read(pOffset);
            UpdateMainScreen();
            UpdateInfoMap();
        }

        /// <summary>
        /// Update the section of the screen with the data.
        /// </summary>
        static void UpdateMainScreen()
        {
            SetCursorPosition(0, 2);

            //TODO: Find a way to stop rendering before total length
            // Hint:
            // CurrentOffset [+ index] <= CurrentFile.Length

            for (int y = 0; y < OffsetHeight; y++)
            {
                switch (CurrentOffsetViewMode)
                {
                    case OffsetViewMode.Hexadecimal:
                        // 00000010
                        Write($"{((y * 0x10) + CurrentOffset).ToString("X8")}  ");
                        break;
                    case OffsetViewMode.Decimal:
                        //       16
                        Write($"{(y * 16) + CurrentOffset, 8}  ");
                        break;
                    case OffsetViewMode.Octal:
                        //       20
                        Write($"{Convert.ToString((y * 16) + CurrentOffset, 8), 8}  ");
                        break;
                }


                for (int x = 0; x < 16; x++)
                {
                    Write($"{Buffer[y, x].ToString("X2")} ");
                }

                Write(" ");
                
                for (int x = 0; x < 16; x++)
                {
                    Write($"{Buffer[y, x].ToChar()}");
                }

                WriteLine();
            }
        }

        /// <summary>
        /// Update the upper bar.
        /// </summary>
        static void UpdateTitleMap()
        {
            ToggleColors();
            
            SetCursorPosition(0, 0);
            // Should I include the project's name in the upper bar?
            Write($"{CurrentFile.Filename}");
            Write(new string(' ', WindowWidth - CurrentFile.Filename.Length));

            ResetColor();
        }

        /// <summary>
        /// Update the offset map (left view)
        /// </summary>
        static void UpdateOffsetMap()
        {
            SetCursorPosition(0, 1);
            Write($"Offset {CurrentOffsetViewMode.GetChar()}  00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
        }

        /// <summary>
        /// Update the data map (right view)
        /// </summary>
        static void UpdateInfoMap()
        {
            SetCursorPosition(0, WindowHeight - 3);
            // CAN'T THIS LINE BE ANY LONGER?
            Write($"{NAME_OFFSET} (DEC): {CurrentOffset, -10} | {NAME_OFFSET} (HEX): {CurrentOffset.ToString("X8")} | {NAME_OFFSET} (OCT): {Convert.ToString(CurrentOffset, 8), -10}");
        }

        /// <summary>
        /// Places the control map on screen (e.g. ^D Die)
        /// </summary>
        static void PlaceControlMap()
        {
            SetCursorPosition(0, WindowHeight - 2);

            ToggleColors();
            Write("^K");
            ResetColor();
            Write(" Help         ");

            ToggleColors();
            Write("^F");
            ResetColor();
            Write(" Find         ");

            ToggleColors();
            Write("^G");
            ResetColor();
            Write(" Goto line    ");

            ToggleColors();
            Write("^H");
            ResetColor();
            Write(" Replace      ");

            ToggleColors();
            Write("^E");
            ResetColor();
            Write(" Edit mode");

            WriteLine();

            ToggleColors();
            Write("^X");
            ResetColor();
            Write(" Exit         ");

            ToggleColors();
            Write("^I");
            ResetColor();
            Write(" Info         ");

            ToggleColors();
            Write("^D");
            ResetColor();
            Write(" Dump         ");

            ToggleColors();
            Write("^V");
            ResetColor();
            Write(" Offset view  ");

            ToggleColors();
            Write("^A");
            ResetColor();
            Write(" Data view");
        }

        /// <summary>
        /// Toggles current ForegroundColor to black
        /// and BackgroundColor to gray.
        /// </summary>
        static void ToggleColors()
        {
            ForegroundColor = ConsoleColor.Black;
            BackgroundColor = ConsoleColor.Gray;
        }

        /// <summary>
        /// Displays a message on screen to inform the user.
        /// </summary>
        /// <param name="pMessage">Message to show.</param>
        static void Message(string pMessage)
        {
            string msg = $"[ {pMessage} ]";

            SetCursorPosition((WindowWidth / 2) - (msg.Length / 2), WindowHeight - 3);
            ToggleColors();

            Write(msg);

            ResetColor();
        }

        static int ReadValue()
        {
            //TODO: int ReadValue()
            throw new NotImplementedException();
        }

        static void Dump()
        {
            //TODO: Finish void Dump()
            using (StreamWriter sw = new StreamWriter($"{CurrentFile.Filename}.txt"))
            {
                sw.WriteLine(CurrentFile.Filename);
                sw.WriteLine();

                using (StreamReader sr = new StreamReader(CurrentFile.Path))
                {

                }
            }
        }

        /// <summary>
        /// The user is exiting the application.
        /// So we prepare the departure.
        /// </summary>
        /// <returns>Always true.</returns>
        /// <remarks>It's false due to the while loop.</remarks>
        static bool Exit()
        {
            Clear();

            return false;
        }
        #endregion

        #region Type extensions
        /// <summary>
        /// Returns a safe character for the console (cmd) to display.
        /// </summary>
        /// <param name="pIn">Byte to transform.</param>
        /// <returns>Safe console character.</returns>
        static char ToChar(this byte pIn)
{'fraction_non_alphanumeric': 0.1035431031046989, 'fraction_numerical': 0.009844167231278147, 'mean_word_length': 1.0656952333909608, 'pattern_counts': {'":': 0, '<': 115, '<?xml version=': 0, '>': 107, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5061944', 'n_tokens_mistral': 6391, 'n_tokens_neox': 5789, 'n_words': 1506}