using System.Globalization;
using System.Runtime.CompilerServices;
using static System.Math;

namespace InnovEnergy.Lib.Utils;

public static class StringUtils
{
    public static String AppendPath(this String left, String right)
    {
        return left.TrimEnd('/') + '/' + right.TrimStart('/');
    }

    public static String RemoveSuffix(this String s, String suffix)
    {
        return s.Substring(0, s.LastIndexOf(suffix, StringComparison.Ordinal));
    }

    public static String SafeToString(this Object? o)
    {
        return o?.ToString() ?? "<null>";
    }

    public static Int32 NumberOfLines(this String s)
    {
        return s.Split(new[] {Environment.NewLine}, StringSplitOptions.None).Length;
    }

    public static String NewLineBefore(this String s)
    {
        return Environment.NewLine + s;
    }

    public static String AddTitle(this String s, String title)
    {
        return title.Underline() + s;
    }

    public static String Underline(this String s)
    {
        return s.NewLine() + "".PadRight(s.Length, '-').NewLine(2);
    }

    public static Boolean IsNullOrEmpty(this String? s)
    {
        return String.IsNullOrEmpty(s);
    }

    public static Boolean IsNullOrWhiteSpace(this String? s)
    {
        return String.IsNullOrWhiteSpace(s);
    }

    public static String Format(this Object value, String fmt)
    {
        return String.Format(fmt, value);
    }

    public static String NewLine(this String s)
    {
        return s + Environment.NewLine;
    }

    public static String NewLine(this String s, Int32 number)
    {
        return Enumerable
            .Repeat(Environment.NewLine, number)
            .Aggregate(s, (a, b) => a + b);
    }


    public static String Append(this String s, String other)
    {
        return s + other;
    }

    public static String SurroundWith(this String s, String beforeAfter)
    {
        return beforeAfter + s + beforeAfter;
    }

    public static String SurroundWith(this String s, String before, String after)
    {
        return before + s + after;
    }


    public static String ShellSingleQuote(this String s)
    {
        return $"'{s.Replace("'", @"'\''")}'";
    }

    // TODO: review
    public static String Quote(this String s)
    {
        return s.Replace(@"\", @"\\")
                .Replace("\"", "\\\"")
                .SurroundWith("\"");
    }

    public static String Join(this String separator, params Object[] objects)
    {
        return String.Join(separator, objects);
    }

    public static String Join(this IEnumerable<String> strings)
    {
        return String.Join("", strings);
    }

    public static String JoinWith(this IEnumerable<String?> strings, String separator)
    {
        return String.Join(separator, strings);
    }


    public static String Indent(this String text)
    {
        return "    ".SideBySideWith(text, "");
    }

    public static String Indent(this String text, Int32 amount)
    {
        return text.Indent("".PadLeft(amount));
    }

    public static String Indent(this String text, String indent)
    {
        return Enumerable
              .Repeat(indent, text.SplitLines().Count)
              .JoinLines()
              .SideBySideWith(text, "");
    }

    public static IReadOnlyList<String> SplitLines(this String? s)
    {
        return s is null 
             ? Array.Empty<String>() 
             : s.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
    }
    
    
    public static String AlignLeft(this String s, Int32 width = 0)
    {
        var lines = s.SplitLines();
        var w = Max(lines.Max(l => l.Length), width);

        return lines
              .Select(l => l.PadRight(w))
              .JoinLines();
    }

    public static String AlignRight(this String s, Int32 width = 0)
    {
        var lines = s.SplitLines();
        var w = Max(lines.Max(l => l.Length), width);

        return lines
              .Select(l => l.PadLeft(w))
              .JoinLines();
    }
    
    public static String AlignCenterHorizontal(this String s, Int32 width = 0)
    {
        var lines = s.SplitLines();
        var w = Max(lines.Max(l => l.Length), width);

        return lines
              .Select(l => l.PadLeft((w + l.Length) / 2).PadRight(w))
              .JoinLines();
    }
    
    public static String AlignTop(this String s, Int32 height)
    {
        var lines   = s.SplitLines();
        var padding = Enumerable.Repeat("", Max(height - lines.Count, 0));
        
        return lines
              .Concat(padding)
              .JoinLines();
    }
    
    public static String AlignBottom(this String s, Int32 height)
    {
        var lines   = s.SplitLines();
        var padding = Enumerable.Repeat("", Max(height - lines.Count, 0));
        
        return padding
              .Concat(lines)
              .JoinLines();
    }
    
    public static String AlignCenterVertical(this String s, Int32 height)
    {
        var lines         = s.SplitLines();
        var nPadding      = Max(height - lines.Count, 0);
        var paddingTop    = Enumerable.Repeat("", nPadding/2);
        var paddingBottom = Enumerable.Repeat("", nPadding - nPadding / 2);
        
        return paddingTop
              .Concat(lines)
              .Concat(paddingBottom)
              .JoinLines();
    }
    
    public static String SideBySideWith(this String left, String right, String separator = "      ")
    {
        var leftLines  = left.SplitLines();
        var rightLines = right.SplitLines();

        var leftWidth = left.Width();

        var nLines = Max(leftLines.Count, rightLines.Count);

        var leftPadded  = leftLines .PadEnd(nLines, "").Select(l => l.PadRight(leftWidth));
        var rightPadded = rightLines.PadEnd(nLines, "");

        return Enumerable
              .Zip(leftPadded, rightPadded, (l, r) => String.Join(separator, l, r))
              .JoinLines();
    }


    public static Int32 Width(this String str)
    {
        return str.SplitLines().Max(l => l.Length);
    }
    
    public static Int32 Height(this String str)
    {
        return str.SplitLines().Count;
    }


    public static String Join(this ITuple tuple)
    {
        return tuple.JoinWith("");
    }

    public static String JoinWith(this ITuple tuple, String separator)
    {
        return tuple
              .ToEnumerable()
              .Select(t => t.SafeToString())
              .JoinWith(separator);
    }

    public static String JoinWith<T>(this IEnumerable<T> args, String separator)
    {
        return String.Join(separator, args);
    }

    public static IEnumerable<String> GetLinesStartingWith(this IEnumerable<String> lines, String prefix)
    {
        return lines.Where(l => l.StartsWith(prefix));
    }

    public static String GetLineStartingWith(this IEnumerable<String> lines, String prefix)
    {
        return lines
              .GetLinesStartingWith(prefix)
              .Single();
    }


    public static Boolean EndsWith(this String str, Char c)
    {
        if (str.Length <= 0)
            return false;

        return str[^1] == c;
    }


    public static String AfterFirst(this String str, Char separator)
    {
        return str.Substring(str.IndexOf(separator) + 1);
    }

    public static String AfterLast(this String str, Char separator)
    {
        return str.Substring(str.LastIndexOf(separator) + 1);
    }

    public static String UntilLast(this String str, Char separator)
    {
        var i = str.LastIndexOf(separator);

        return i >= 0
            ? str[..i]
            : str;
    }

    public static String UntilFirst(this String str, Char separator)
    {
        var i = str.IndexOf(separator);

        return i >= 0
             ? str[..i]
             : str;
    }

    public static Boolean Contains(this String? str, String value, CompareOptions compareOptions)
    {
        return Contains(str, value, compareOptions, CultureInfo.InvariantCulture);
    }

    public static Boolean Contains(this String? str, String value, CompareOptions compareOptions, CultureInfo culture)
    {
        return str is not null && culture.CompareInfo.IndexOf(str, value, compareOptions) != -1;
    }

    public static String NewLine(this Object s)
    {
        return s + Environment.NewLine;
    }

    public static String Spaces(this String s, Int32 count = 4)
    {
        return s.PadRight(s.Length + count);
    }


    public static String JoinNonEmptyLines(this IEnumerable<String> lines)
    {
        return String.Join(Environment.NewLine, lines.Unless(String.IsNullOrWhiteSpace));
    }

    public static String JoinLines(this IEnumerable<String> lines)
    {
        return String.Join(Environment.NewLine, lines);
    }

    public static String JoinLines(params String[] lines)
    {
        return String.Join(Environment.NewLine, lines);
    }

    public static String SeparatedBy(this IEnumerable<String> s, String separator)
    {
        return String.Join(separator, s);
    }

    public static Boolean IsInteger(this String s)
    {
        return Int64.TryParse(s, out var n);
    }


    public static String TrimStart(this String target, String trimString)
    {
        if (String.IsNullOrEmpty(trimString)) return target;

        var result = target;
        while (result.StartsWith(trimString))
            result = result[trimString.Length..];

        return result;
    }


    public static String TrimEnd(this String target, String trimString)
    {
        if (String.IsNullOrEmpty(trimString)) 
            return target;

        var result = target;
        while (result.EndsWith(trimString))
            result = result[..^trimString.Length];

        return result;
    }

    public static String EnsureStartsWith(this String target, String prefix)
    {
        return $"{prefix}{target.TrimStart(prefix)}";
    }
    
    public static String EnsureEndsWith(this String target, String postfix)
    {
        return $"{target.TrimEnd(postfix)}{postfix}";
    }


}