using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using System;

using System.Globalization;
using Godot;
using System.Linq;

namespace Rokojori
{
  public class MatchRange
  {
    public int start;
    public int length;

    public MatchRange( int s, int l )
    {
      start = s;
      length = l;
    }

    public string GetString( string source)
    {
      return source.Substring( start, length );
    }
  }

  public class MatchResult
  {
    public string source;
    public bool matched = false;
    
    public MatchRange match;

    public List<MatchRange> groups;    

    public string this[ int index ]
    {
      get
      {
        if ( ! matched )
        {
          return null;
        }

        if ( index == 0 )
        { 
          return match.GetString( source );
        }

        if ( groups != null && index > 0 && index <= groups.Count )
        {
          return groups[ index - 1 ] == null ? null :  groups[ index - 1 ].GetString( source );
        }

        return null;
      }
    }

  }

  public static class RegexUtility
  {
    public static MatchResult Exec( this string source, string pattern, RegexOptions options = RegexOptions.None )
    {
      var regex = new Regex( pattern );
      var match = regex.Match( source );

      var result = new MatchResult();
      result.source = source;
      result.matched = match.Success;

      if ( match.Success )
      {
        result.match = new MatchRange( match.Index,  match.Length );

        result.groups = new List<MatchRange>();

        for ( int i = 1; i < match.Groups.Count; i++ )
        {
          var group = match.Groups[i];

          if ( group.Success )
          {
            result.groups.Add( new MatchRange( group.Index, group.Length ) );
          }
          else
          {
            result.groups.Add( null );
          }
        }
      }    

      return null;
    }

    public static bool Matches( this string source, string regexPattern, RegexOptions options = RegexOptions.None )
    {
      var match = new Regex( regexPattern, options ).Match( source );

      return match != null && match.Success;
    }

    public static Regex MakeSticky( Regex regex )
    {
      var source = MakeSourceSticky( regex + "" );

      return new Regex( source, regex.Options );
    }

    public static string LineBreak( this string value )
    {
      return value + "\n";
    }

    public static string LineBreaks( this string value )
    {
      return value + "\n\n";
    }

    public static string Indent( this string value, string indentation = "  ", bool indentFirstLine = true )
    {
      var lines = SplitLines( value );

      var ignoreFirstLine = ! indentFirstLine;

      var linesBefore = lines;
      lines = RemoveIntendation( lines );
      
      var indentedLines = lines.Map( 
        l => 
        {  

          if ( ignoreFirstLine )
          {
            ignoreFirstLine = false;
            return l;
          }
          else
          {
            return indentation + l;
          } 

          
        }
      ); 

      return indentedLines.Join( "\n" ); 
    }

    public static List<string> RemoveIntendation( string value  )
    {
      return RemoveIntendation( SplitLines( value ) );
    }

    public static List<string> RemoveIntendation( List<string> lines )
    {
      var initial = 1000000;
    
      var numSpaces = initial;

      for ( int i = 0; i < lines.Count; i++ )
      {
        if ( IsNullOrWhiteSpace( lines[ i ] ) )
        {
          continue;
        }

        numSpaces = Mathf.Min( numSpaces, MatchLength( lines[ i ], @"^\s+" ) );
      }

      if ( numSpaces == initial || numSpaces <= 0 )
      {
        return lines;
      }

      var outputLines = lines.Map(
        ( l )=>
        {
          if ( IsNullOrWhiteSpace( l ) )
          {
            return "";
          }

          return l.Substring( numSpaces );
        }
      );

      
      return outputLines;
    }

    public static bool IsNullOrWhiteSpace( string s )
    {
      return string.IsNullOrWhiteSpace( s );
    }

    public static string MakeSourceSticky( string source )
    {
      if ( ! source.StartsWith( "\\G" ) )
      {
        source = "\\G(?:" + source + ")";
      }

      return source;
    } 

    public static bool IsUpperCase( string value, int position )
    {
      if ( value == null || value.Length == 0 || value.Length <= position )
      {
        return false;
      }

      var ch = ( value[ position ] + "" ).ToUpper();

      return ch == ( value[ position ] + "" );
    }

    public static bool StartsWithUpperCase( string value )
    {
      return IsUpperCase( value, 0 );
    }

    public static string NumberToString( double value )
    {
      try
      {
        var specifier = "G";

        return value.ToString( specifier, CultureInfo.InvariantCulture );
      }
      catch( System.Exception e )
      {
        RJLog.Log(e );
      }

      return "0";
    }

    public static string _G( this float value )
    {
      var stringValue = NumberToString( value );

      if ( ! stringValue.Contains( "." ) )
      {
        stringValue += ".0";
      }

      return stringValue;
    }

    public static string _F( this float value )
    {
      return value.ToString( "0.0", CultureInfo.InvariantCulture );
    }

    public static string _FF( this float value )
    {
      return value.ToString( "0.00", CultureInfo.InvariantCulture );
    }

    public static string _FFF( this float value )
    {
      return value.ToString( "0.000", CultureInfo.InvariantCulture );
    }

    public static string _DDD( this double value )
    {
      return value.ToString( "0.000", CultureInfo.InvariantCulture );
    }

    public static string _M( this float meters )
    {
      return _FF( meters ) + "m";
    }

    public static string _CM( this float meters )
    {
      return _FF( meters * 100 ) + "cm";
    }

    public static string _KM( this float meters )
    {
      return _FF( meters / 1000 ) + "km";
    }

    

    public static double ParseDouble( string source, double alternative = 0 )
    {
      try 
      { 
        double newValue = System.Double.Parse( source, CultureInfo.InvariantCulture );
        return newValue;
      }
      catch ( System.Exception e )
      {
        if ( e != null )
        {

        }
        return alternative;
      }
    }

    public static float ParseFloat( string source, float alternative = 0 )
    {
      return ( float ) ParseDouble( source, alternative );
    }

    public static int ParseInt( string source, int alternative = 0 )
    {
      var multiply = 1;
      var value = 0;

      for ( int i = source.Length - 1; i >= 0 ; i-- )
      {
        var symbol = source[ i ];
        
        if ( i == 0  )
        {
          if ( symbol == '-' )
          { 
            return - value; 
          }

          if ( symbol == '+' )
          { 
            return value; 
          }
        }

        var digitValue = ParseDigit( symbol );
        
        if ( digitValue == -1 )
        {
          return alternative;
        }

        value += digitValue * multiply;

        multiply *= 10;
      }  

      return value;
    }

    public static int ParseDigit( char c )
    {
      switch ( c )
      {
        case '0': return 0;
        case '1': return 1;
        case '2': return 2;
        case '3': return 3;
        case '4': return 4;
        case '5': return 5;
        case '6': return 6;
        case '7': return 7;
        case '8': return 8;
        case '9': return 9;
      }

      return -1;
    }

    public static bool Matching( string source, string regex, RegexOptions options = RegexOptions.None )
    {
      if ( source == null )
      {
        return false;
      }

      return new Regex( regex, options ).Match( source ).Success;
    }

    public static int MatchLength( string source, string regex, RegexOptions options = RegexOptions.None, int start = 0 )
    {
      var match = new Regex( regex, options ).Match( source, start );

      if ( ! match.Success )
      {
        return 0;
      } 

      return match.Length;

    }

    public static bool MatchingIgnoreCase( string source, string regex )
    {
      return Matching( source, regex, RegexOptions.IgnoreCase );
    }

    public static string Substring( string source, string regex )
    {
      return new Regex( regex ).Match( source ).Value;
    }

    public static string EscapeForRegex( string source )
    {
      var sb = new StringBuilder();

      var escapes = "+*?^$\\.[]{}()|/";

      for ( int i = 0; i < source.Length; i++ )
      {
        var character = source[ i ];

        if ( escapes.IndexOf( character ) != -1 )
        {
          sb.Append( "\\" );
        } 

        sb.Append( character );
      }

      return sb.ToString();
    }

    public static string LeadingZeros( int value, int minDigits = 2)
    {
      var stringValue = value + "";

      while ( stringValue.Length < minDigits )
      {
        stringValue = "0" + stringValue;
      }

      return stringValue;
    }

    public static string NumberWithThousandsSeperator( int value, string seperator = "." )
    {
      var isNegative = value < 0;

      if ( isNegative ){ value = - value; }

      var stringValue = value + "";

      var builder = new StringBuilder();

      for ( int i = 0; i < stringValue.Length; i++ )
      {
        var index = ( stringValue.Length - 1 ) - i ;

        if ( i % 4 == 3 )
        {
          builder.Append( seperator );
        }

        builder.Append( stringValue[ index ] );
      }

      var seperatorValue = builder.ToString();

      return Reverse( seperatorValue );
    }

    public static string Reverse( string source )
    {
      char[] characters = source.ToCharArray();
      System.Array.Reverse( characters );
      return new string( characters );
    }

    public static Regex EscapedOrRegex( string first, params string[] other )
    {
      var value = EscapeForRegex( first );
      
      for ( var i = 0; i < other.Length; i++ )
      {
        value += "|" + EscapeForRegex( other[ i ] );

        /*

        if ( i == 0 )
        { 
          value = "(" + value;
        }         
        value += ")|(" + EscapeForRegex( other[ i ] );

        if ( i == ( other.Length - 1 ) )
        {
          value += ")";
        }

        */
      }

      //Logs.Log("EscapedOrRegex:", "'" + value + "'");
      return new Regex( value );

    }

    public static Color ParseColor( string source, Color alternative )
    {
      if ( Matching( source, @"#\d+" ) )
      {
        return ParseHexColor( source, alternative );
      } 
      else if ( Matching( source, @"^rgb" ) )
      { 
        return ParseRGBColor( source, alternative );
      } 
      else if ( Matching( source, @"^hsl" ) )
      {
        return ParseHSLColor( source, alternative );
      }

      return alternative;
    }


    public static double ParsePercentage( string source )
    {
      source = Remove( source, @"\s*%" );
      return ParseDouble( source.Trim(), 0 ) / 100.0;
    }

    public static int ParseHexCharacter( char hexCharacter )
    {
      if ( '0' <= hexCharacter && hexCharacter <= '9' )
      {
        return (int) (hexCharacter - '0' );
      } 

      if ( 'a' <= hexCharacter && hexCharacter <= 'f' )
      {
        return (int) (hexCharacter - 'a') + 10;
      }

      if ( 'A' <= hexCharacter && hexCharacter <= 'F' )
      {
        return (int) (hexCharacter - 'A') + 10;
      }

      return 0;
    }
    
    public static int ParseHex( string source )
    {
      var value = 0;
      var shift = 0;

      for ( var i = source.Length - 1; i < source.Length; i++ )
      {
        var hexValue = ParseHexCharacter( source[ i ] ) << shift;
        shift += 4;
      }

      return value;

    }
    
    public static Color ParseHexColor( string source, Color alternative )
    {
      source = RegexUtility.Remove( source, @"^#" ); 

      var numbers = new List<float>();

      for ( var i = 0; i < 3; i++ )
      {
        var value = source.Substring( i * 2, 2 );
        var numberValue = ParseHex( value ) / 255f;
        numbers.Add( numberValue );
      }

      if ( numbers.Count == 3 )
      {
        numbers.Add( 1f );
      } 

      return new Color( numbers[ 0 ], numbers[ 1 ], numbers[ 2 ] , numbers[ 3 ] );

    }

    public static Color ParseRGBColor( string source, Color alternative )
    {
      source = RegexUtility.Remove( source, @"^rgba?\(" ); 
      source = RegexUtility.Remove( source, @"\)$" );
      
      var splits = Split( source, @"\," );
      var numbers = Lists.Map<string,float>( splits, 
        ( string value, int index ) =>
        { 
          if ( value.Contains( "%" ) )
          {
            return (float) ParsePercentage( value );
          }
           
          return ParseFloat( value );
        }
      );

      if ( numbers.Count == 3 )
      {
        numbers.Add( 1f );
      } 

      return new Color( numbers[ 0 ], numbers[ 1 ], numbers[ 2 ] , numbers[ 3 ] );

    }

    public static HSLColor ParseHSLColor( string source, HSLColor alternative )
    {
      source = RegexUtility.Remove( source, @"^hsl\(" ); 
      source = RegexUtility.Remove( source, @"\)$" );
      
      var splits = Split( source, @"\," );
      var numbers =Lists.Map<string,float>( splits, 
        ( string value, int index ) =>
        { 
          if ( value.Contains( "%" ) )
          {
            return (float) ParsePercentage( value );
          }
           
          return ParseFloat( value );
        }
      );

      if ( numbers.Count < 3 )
      {
        RJLog.Log("Not enough numbers parsed: ", source, ">>", numbers.Count, Lists.Join( numbers, "," ) );
      }
     

      if ( numbers.Count == 3 )
      {
        numbers.Add( 1f );
      } 

      return new HSLColor( numbers[ 0 ], numbers[ 1 ], numbers[ 2 ] , numbers[ 3 ] );
    }

    public static List<string> Split( string source, Regex regex )
    { 
      var strings = regex.Split( source );
      var list = new List<string>();

      list.AddRange( strings );

      return list;
    } 

    public static List<string> Split( string source, string regex )
    {
      return Split( source, new Regex( regex ) );
    }

    public static List<string> SplitLines( string source )
    {
      return Regex.Split( source, "\r\n|\r|\n" ).ToList();
    }

    public static string Remove( string source, string regex )
    {
      return Replace( source, regex, "" );
    }

    public static string Replace( string source, Regex regex, string replacement )
    {
      return regex.Replace( source, replacement );
    }
    
    public static string Remove( string source, Regex regex )
    {
      return Replace( source, regex, "" );
    }

    public static string Replace( string source, string regex, string replacement )
    {
      return new Regex( regex ).Replace( source, replacement );
    }

    public static string ReplaceMultiple( string source, Dictionary<string,string> replacements )
    {
      var replaced = source;

      foreach ( var vk in replacements )
      {
        replaced = replaced.Replace( vk.Key, vk.Value );
      }

      return replaced;
    }

    public static string UpperCaseAndWide( string source )
    {
      var sb = new StringBuilder();

      for ( int i = 0; i < source.Length; i++ )
      {
        if ( i != 0 )
        {
          sb.Append( " " );
        }

        sb.Append( ( source[ i ] + "" ).ToUpper() );
        
      }

      return sb.ToString();
    }

    public static string TrimToLastPathFragment( string path )
    {
      var splits = SplitPaths( path );

      return splits[ splits.Count - 1 ];
    }

    public static bool StartsWithPathProtocol( string path )
    {
      return Matching( path, @"^\w+://" );
    }

    public static string ExtractPathProtocol( string path )
    {
      if ( ! StartsWithPathProtocol( path) )
      {
        return "";
      }

      var splitPosition = path.IndexOf( "://" );

      return path.Substring( 0, splitPosition + 3 ) ;
    }

    public static string ParentPath( string path )
    {
      var protocol = ExtractPathProtocol( path );

      if ( protocol.Length != 0 )
      {
        path = path.Substring( protocol.Length );
      }

      var list = SplitPaths( path );

      list.RemoveAt( list.Count -1 );

      return protocol + Lists.Join( list, "/" );
    }


    public static List<string> SplitPaths( string path )
    {
      var splittedPaths = new List<string>();
      var normalizedPath = NormalizePath( path );
      var splits = Split( normalizedPath, new Regex( @"\/" ) );
      return splits;
    } 


    public static string ToValidCSName( string source)
    {
      var output = new StringBuilder();

      for ( int i = 0; i < source.Length; i++)
      {
        var s = source[ i ];
        var isDigit  = Char.IsDigit( s );
        var isLetter = Char.IsLetter( s );
        var isAscii  = Char.IsAscii( s );

        var isDigitAsFirst = i == 0 && isDigit;
        var isAllowed = ( isDigit || isLetter ) && isAscii;

        if ( isDigitAsFirst || ! isAllowed  )
        {
          output.Append( "_" );
        } 
        else
        {
          output.Append( source[ i ] );
        }
      }

      source = output.ToString();

      return source.ToCamelCase();
     
    }   

    public static string JoinPaths( List<string> paths, int startIndex = 0, int length = -1 )
    {
      var normalizedPaths = new List<string>();
      // normalizedPaths.AddRange( paths );

      var endIndex = startIndex + length;
      var end = ( length < 0 || ( length + startIndex ) >= paths.Count ) ? paths.Count : endIndex;
     
      for ( var i = startIndex; i < end; i++ )
      {
        normalizedPaths.Add( _NormalizePath( paths[ i ] ) );
      } 

      return Lists.Join( normalizedPaths, "/" ); 
    }

    public static string Join( string pathA, string pathB, params string[] paths )
    {
      var normalizedPaths = new List<string>();
      normalizedPaths.Add( pathA );
      normalizedPaths.Add( pathB );
      normalizedPaths.AddRange( paths );
     
      for ( var i = 0; i < normalizedPaths.Count; i++ )
      {
        normalizedPaths[ i ] = _NormalizePath( normalizedPaths[ i ] );
      } 

      return Lists.Join( normalizedPaths, "/" );
    }

    private static string _EnsureForwardSlashes( string path )
    { 
      if ( path.IndexOf( "\\" ) == -1 )
      {
        return path;
      }  

      var correctedPath = "";
      
      for ( int i = 0; i < path.Length; i++ )
      {
        if ( path[ i ] == '\\' )
        {
          correctedPath += "/";
        } 
        else
        {
          correctedPath += path[ i ];
        }         
      }

      return correctedPath;
    }

    public static string NormalizePath( string path )
    {
      return _NormalizePath( path );
    }

    public static string WindowsPath( string path )
    {
      path = _NormalizePath( path );

      var slashes = @"\/";

      path = Regex.Replace( path, slashes, "\\" );

      return path;
    }

    private static string _NormalizePath( string path )
    {
      path = _EnsureForwardSlashes( path );

      var startSlashes = @"^\/+";
      var endSlashes = @"\/+$";
      var multiples = @"\/\/+";

      path = Regex.Replace( path, startSlashes, "" );
      path = Regex.Replace( path, endSlashes, "" );
      path = Regex.Replace( path, multiples, "/" );

      return path;
    }
  }
}