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

namespace Rokojori
{ 
  public class TextLinesMapper
  {    
    List<TextLine> _lines;
    int _cachedLineIndex;

    void Reset()
    {
      _lines = new List<TextLine>();
      _cachedLineIndex = 0;
    }

    public void Map( string source )
    {
      Reset();

      var currentCharacterIndex = 0;
      currentCharacterIndex = NextLineBreak( source, currentCharacterIndex );

      var lastCharacterIndex = 0;
      var lastLineBreakLength = 0;

      var currentLineIndex = 0;
      var maxTries = 10000; var currentTries = 0;      
      // currentTries ++; if ( currentTries == maxTries ) { throw new System.Exception(); }
      while ( currentCharacterIndex != -1 )
      {
        
        currentTries ++; if ( currentTries == maxTries ) { throw new System.Exception(); }
        var currentBreakLength = LineBreakLength( source, currentCharacterIndex );     
        
        
        var lineLength = currentCharacterIndex - lastCharacterIndex;          
        var lineInfo = new TextLine( currentLineIndex, lastCharacterIndex, lineLength, lastLineBreakLength );
        _lines.Add( lineInfo );

        lastLineBreakLength = currentBreakLength;
        lastCharacterIndex = currentCharacterIndex;
        currentLineIndex ++;
        
        
        currentCharacterIndex = NextLineBreak( source, currentCharacterIndex + currentBreakLength );
      }

      var endLineLength = source.Length - lastCharacterIndex;
      var endLineInfo = new TextLine( currentLineIndex, lastCharacterIndex, endLineLength, lastLineBreakLength );
      _lines.Add( endLineInfo );
      
    }    
    
    public int numCharacters { get { return _lines[ _lines.Count -1 ].lineEnd + 1; } }

    public int numLines { get { return _lines.Count; } }
    public string lineInfos 
    {
      get 
      {
        var output = new List<string>();
        foreach ( var line in _lines )
        {
          output.Add( line.info );
        }

        return Lists.Join( output, ",\n" ); 
      } 
    } 
    public TextLine GetLine( int characterIndex )
    {
      var lineIndex = GetLineIndex( characterIndex );
      return lineIndex == -1 ? null : _lines[ lineIndex ];
    }

    public TextAnchor GetAnchor( int characterIndex, bool forTextEditor )
    {
      var line = GetLine( characterIndex );
      if ( line == null ) { return null; }

      return forTextEditor ? line.GetTextEditorAnchor( characterIndex ) : line.GetRawAnchor( characterIndex );      
    }
 
    public TextSelection GetSelection( int startCharacterIndex, int endCharacterIndex, bool forTextEditor = false )
    {
      var startAnchor = GetAnchor( startCharacterIndex, forTextEditor );
      var endAnchor  = GetAnchor( endCharacterIndex, forTextEditor );
      
      if ( startAnchor == null || endAnchor == null ) { return null; }
      
      return new TextSelection( startAnchor, endAnchor );
    }

    public TextSelection GetTextEditorSelection( int startCharacterIndex, int endCharacterIndex )
    {
      return GetSelection( startCharacterIndex, endCharacterIndex, true );
    }

    public string GetTextEditorInfo( int startCharacterIndex, int endCharacterIndex )
    {
      var selection = GetTextEditorSelection( startCharacterIndex, endCharacterIndex );

      if ( selection != null ) { return selection.info; }

      return "@chars(" + startCharacterIndex + "," + endCharacterIndex + ")";  
    }
  
    public static string GetLineInfo( string source, int startCharacterIndex, int endCharacterIndex = -1 )
    {
      endCharacterIndex = endCharacterIndex == -1 ? startCharacterIndex : endCharacterIndex;

      var linesMapper = new TextLinesMapper();
      linesMapper.Map( source );

      return linesMapper.GetTextEditorSnippet( source, startCharacterIndex, endCharacterIndex );
    }

    public static string GetLineInfo( string source, LexerEvent le )
    {
      return GetLineInfo( source, le.offset );
    } 

    public string GetTextEditorSnippet( string source, int startCharacterIndex, int endCharacterIndex )
    {
      var startLine = GetLineIndex( startCharacterIndex );
      var endLine = GetLineIndex( endCharacterIndex );
      
      if ( endLine < startLine )
      {
        var b = endLine; endLine = startLine; startLine = b;
      }

      var snippet = new StringBuilder();
      var lengthMaxLineNumberIndex = ( _lines[ endLine ].textEditorLineIndex + "" ).Length;

      for ( var i = startLine; i <= endLine; i++ )
      {
        var line = _lines[ i ];
        var lineNumber = _lines[ i ].textEditorLineIndex + "";

        while ( lineNumber.Length < lengthMaxLineNumberIndex )
        {
          lineNumber = "0" + lineNumber;
        }

        var content = line.GetContent( source );
        snippet.Append( lineNumber + ": " );
        snippet.Append( content );
        snippet.Append( "\n" );
      }

      return snippet.ToString();

    }

    public string GetTextEditorInfo( int characterIndex )
    {
      var anchor = GetAnchor( characterIndex, true );

      if ( anchor != null ) { return anchor.info; }

      return "@chars(" + characterIndex + ")";  
    }



    TextLine _cachedLine { get { return _lines[ _cachedLineIndex ]; } }

    public int GetLineIndex( int characterIndex )
    {
      if ( _cachedLine.Contains( characterIndex ) )
      {
        return _cachedLineIndex;
      }

      if ( _lines.Count == 0 || characterIndex < 0 || characterIndex > numCharacters ) 
      { return -1; }

      var lineContainsCharacterIndex = false;   
      var lineIndexInRange = true;

      var characterIndexIsHigher = _cachedLine.characterIndex < characterIndex;
      var searchForward = 1;
      var searchBackward = -1 ;
      var searchStep = characterIndexIsHigher ? searchForward : searchBackward;

      var maxTries = 1000; var currentTries = 0;      
      // currentTries ++; if ( currentTries == maxTries ) { throw new System.Exception(); }

      do 
      {
        currentTries ++; if ( currentTries == maxTries ) { throw new System.Exception(); }
        var nextLineIndex = _cachedLineIndex + searchStep;
        lineIndexInRange = 0 <= nextLineIndex && nextLineIndex < _lines.Count;
        
        if ( lineIndexInRange )
        {
          _cachedLineIndex = nextLineIndex;
          lineContainsCharacterIndex = _cachedLine.Contains( characterIndex );
        }
        else
        {
          lineContainsCharacterIndex = false;
        }  
        
      }
      while ( ! lineContainsCharacterIndex && lineIndexInRange );  

      if ( lineContainsCharacterIndex )
      {
        return _cachedLineIndex;
      }

      return -1;

    }

    int NextLineBreak( string source, int offset )
    {
      for ( int i = offset; i < source.Length; i++ )
      {
        if ( source[ i ] == '\n' || source[ i ] == '\r' )
        {
          return i;
        }
      }

      return -1;
    }

    int LineBreakLength( string source, int offset )
    {
      if ( source[ offset ] == '\r' && ( ( offset + 1 ) < source.Length ) )
      {
        return source[ offset + 1 ] == '\n' ? 2 : 1;
      }

      return 1;
    }

    

  }

  
}