# -*- coding: utf-8 -*-
#*****************************************************************************
#       Copyright (C) 2006  Jorgen Stenarson. <jorgen.stenarson@bostream.nu>
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file COPYING, distributed as part of this software.
#*****************************************************************************
import re, operator, sys

import wordmatcher
import pyreadline.clipboard as clipboard
from pyreadline.logger import  log
from pyreadline.unicode_helper import ensure_unicode

kill_ring_to_clipboard = False #set to true to copy every addition to kill ring to clipboard


class NotAWordError(IndexError):
    pass


def quote_char(c):
    if ord(c) > 0:
        return c

############## Line positioner ########################

class LinePositioner(object):
    def __call__(self, line):
        NotImplementedError(u"Base class !!!")

class NextChar(LinePositioner):
    def __call__(self, line):
        if line.point < len(line.line_buffer):
            return line.point + 1
        else:
            return line.point
NextChar = NextChar()

class PrevChar(LinePositioner):
    def __call__(self, line):
        if line.point > 0:
            return line.point - 1
        else:
            return line.point
PrevChar = PrevChar()

class NextWordStart(LinePositioner):
    def __call__(self, line):
        return line.next_start_segment(line.line_buffer, line.is_word_token)[line.point]
NextWordStart = NextWordStart()

class NextWordEnd(LinePositioner):
    def __call__(self, line):
        return line.next_end_segment(line.line_buffer, line.is_word_token)[line.point]
NextWordEnd = NextWordEnd()

class PrevWordStart(LinePositioner):
    def __call__(self, line):
        return line.prev_start_segment(line.line_buffer, line.is_word_token)[line.point]
PrevWordStart = PrevWordStart()


class WordStart(LinePositioner):
    def __call__(self, line):
        if line.is_word_token(line.get_line_text()[Point(line):Point(line) + 1]):
            if Point(line) > 0 and line.is_word_token(line.get_line_text()[Point(line) - 1:Point(line)]):
                return PrevWordStart(line)
            else:
                return line.point
        else:   
            raise NotAWordError(u"Point is not in a word")
WordStart = WordStart()

class WordEnd(LinePositioner):
    def __call__(self, line):
        if line.is_word_token(line.get_line_text()[Point(line):Point(line) + 1]):
            if line.is_word_token(line.get_line_text()[Point(line) + 1:Point(line) + 2]):
                return NextWordEnd(line)
            else:
                return line.point
        else:   
            raise NotAWordError(u"Point is not in a word")
WordEnd = WordEnd()

class PrevWordEnd(LinePositioner):
    def __call__(self, line):
        return line.prev_end_segment(line.line_buffer, line.is_word_token)[line.point]
PrevWordEnd = PrevWordEnd()

class PrevSpace(LinePositioner):
    def __call__(self, line):
        point = line.point
        if line[point - 1:point].get_line_text() == u" ":
            while point > 0 and line[point - 1:point].get_line_text() == u" ":
                point -= 1
        while point > 0 and line[point - 1:point].get_line_text() != u" ":
            point -= 1
        return point
PrevSpace = PrevSpace()


class StartOfLine(LinePositioner):
    def __call__(self, line):
        return 0
StartOfLine = StartOfLine()

class EndOfLine(LinePositioner):
    def __call__(self, line):
        return len(line.line_buffer)
EndOfLine = EndOfLine()

class Point(LinePositioner):
    def __call__(self, line):
        return line.point
Point = Point()

class Mark(LinePositioner):
    def __call__(self, line):
        return line.mark
k = Mark()

all_positioners = [(value.__class__.__name__, value) 
                        for key, value in globals().items() 
                         if isinstance(value, LinePositioner)]
all_positioners.sort()

############### LineSlice #################

class LineSlice(object):
    def __call__(self, line):
        NotImplementedError(u"Base class !!!")


class CurrentWord(LineSlice):
    def __call__(self, line):
        return slice(WordStart(line), WordEnd(line), None)
CurrentWord = CurrentWord()

class NextWord(LineSlice):
    def __call__(self, line):
        work = TextLine(line)
        work.point = NextWordStart
        start = work.point
        stop = NextWordEnd(work)
        return slice(start, stop)
NextWord = NextWord()

class PrevWord(LineSlice):
    def __call__(self, line):
        work = TextLine(line)
        work.point = PrevWordEnd
        stop = work.point
        start = PrevWordStart(work)
        return slice(start, stop)
PrevWord = PrevWord()

class PointSlice(LineSlice):
    def __call__(self, line):
        return slice(Point(line), Point(line) + 1, None)
PointSlice = PointSlice()


###############  TextLine  ######################

class TextLine(object):
    def __init__(self, txtstr, point = None, mark = None):
        self.line_buffer = []
        self._point = 0
        self.mark = -1
        self.undo_stack = []
        self.overwrite = False
        if isinstance(txtstr, TextLine): #copy 
            self.line_buffer = txtstr.line_buffer[:]
            if point is None:
                self.point = txtstr.point
            else:                
                self.point = point
            if mark is None:
                self.mark = txtstr.mark
            else:
                self.mark = mark
        else:            
            self._insert_text(txtstr)
            if point is None:
                self.point = 0
            else:
                self.point = point
            if mark is None:
                self.mark = -1
            else:
                self.mark = mark

        self.is_word_token = wordmatcher.is_word_token
        self.next_start_segment = wordmatcher.next_start_segment
        self.next_end_segment = wordmatcher.next_end_segment
        self.prev_start_segment = wordmatcher.prev_start_segment
        self.prev_end_segment = wordmatcher.prev_end_segment
        
    def push_undo(self):
        ltext = self.get_line_text()
        if self.undo_stack and ltext == self.undo_stack[-1].get_line_text():
            self.undo_stack[-1].point = self.point
        else:
            self.undo_stack.append(self.copy())

    def pop_undo(self):
        if len(self.undo_stack) >= 2:
            self.undo_stack.pop()
            self.set_top_undo()
            self.undo_stack.pop()
        else:
            self.reset_line()
            self.undo_stack = []

    def set_top_undo(self):
        if self.undo_stack:
            undo = self.undo_stack[-1]
            self.line_buffer = undo.line_buffer
            self.point = undo.point
            self.mark = undo.mark
        else:
            pass
        
    def __repr__(self):
        return u'TextLine("%s",point=%s,mark=%s)'%(self.line_buffer, self.point, self.mark)

    def copy(self):
        return self.__class__(self)

    def set_point(self,value):
        if isinstance(value, LinePositioner):
            value = value(self)
        assert  (value <= len(self.line_buffer))           
        if value > len(self.line_buffer):
            value = len(self.line_buffer)
        self._point = value
    def get_point(self):
        return self._point
    point = property(get_point, set_point)


    def visible_line_width(self, position = Point):
        """Return the visible width of the text in line buffer up to position."""
        extra_char_width = len([ None for c in self[:position].line_buffer if 0x2013 <= ord(c) <= 0xFFFD])
        return len(self[:position].quoted_text()) + self[:position].line_buffer.count(u"\t")*7 + extra_char_width

    def quoted_text(self):
        quoted = [ quote_char(c) for c in self.line_buffer ]
        self.line_char_width = [ len(c) for c in quoted ]
        return u''.join(map(ensure_unicode, quoted))

    def get_line_text(self):
        buf = self.line_buffer
        buf = map(ensure_unicode, buf)
        return u''.join(buf)
            
    def set_line(self, text, cursor = None):
        self.line_buffer = [ c for c in str(text) ]
        if cursor is None:
            self.point = len(self.line_buffer)
        else:
            self.point = cursor

    def reset_line(self):
        self.line_buffer = []
        self.point = 0

    def end_of_line(self):
        self.point = len(self.line_buffer)

    def _insert_text(self, text, argument=1):
        text = text * argument
        if self.overwrite:
            for c in text:
                #if self.point:
                self.line_buffer[self.point] = c
                self.point += 1
        else:            
            for c in text:
                self.line_buffer.insert(self.point, c)
                self.point += 1
    
    def __getitem__(self, key):
        #Check if key is LineSlice, convert to regular slice
        #and continue processing
        if isinstance(key, LineSlice): 
            key = key(self)
        if isinstance(key, slice):
            if key.step is None:
                pass
            else:
                raise Error
            if key.start is None:
                start = StartOfLine(self)
            elif isinstance(key.start,LinePositioner):
                start = key.start(self)
            else:
                start = key.start
            if key.stop is None:                   
                stop = EndOfLine(self)
            elif isinstance(key.stop, LinePositioner):
                stop = key.stop(self)
            else:
                stop = key.stop
            return self.__class__(self.line_buffer[start:stop], point=0)
        elif isinstance(key, LinePositioner):
            return self.line_buffer[key(self)]
        elif isinstance(key, tuple):
            raise IndexError(u"Cannot use step in line buffer indexing") #Multiple slice not allowed
        else:
            # return TextLine(self.line_buffer[key])
            return self.line_buffer[key]

    def __delitem__(self, key):
        point = self.point
        if isinstance(key, LineSlice):
            key = key(self)
        if isinstance(key, slice):
            start = key.start
            stop = key.stop
            if isinstance(start, LinePositioner):
                start = start(self)
            elif start is None:
                start=0
            if isinstance(stop, LinePositioner):
                stop = stop(self)
            elif stop is None:
                stop = EndOfLine(self)
        elif isinstance(key, LinePositioner):
            start = key(self)
            stop = start + 1
        else:
            start = key
            stop = key + 1
        prev = self.line_buffer[:start]
        rest = self.line_buffer[stop:]
        self.line_buffer = prev + rest
        if point > stop:
            self.point = point - (stop - start)
        elif point >= start and point <= stop:
            self.point = start


    def __setitem__(self, key, value):
        if isinstance(key, LineSlice):
            key = key(self)
        if isinstance(key, slice):
            start = key.start
            stop = key.stop
        elif isinstance(key, LinePositioner):
            start = key(self)
            stop = start + 1
        else:
            start = key
            stop = key + 1
        prev = self.line_buffer[:start]
        value = self.__class__(value).line_buffer
        rest = self.line_buffer[stop:]
        out = prev + value + rest
        if len(out) >= len(self):
            self.point = len(self)
        self.line_buffer = out

    def __len__(self):
        return len(self.line_buffer)

    def upper(self):
        self.line_buffer = [x.upper() for x in self.line_buffer]
        return self

    def lower(self):
        self.line_buffer = [x.lower() for x in self.line_buffer]
        return self
        
    def capitalize(self):
        self.set_line(self.get_line_text().capitalize(), self.point)
        return self
        
    def startswith(self, txt):
        return self.get_line_text().startswith(txt)

    def endswith(self, txt):
        return self.get_line_text().endswith(txt)

    def __contains__(self, txt):
        return txt in self.get_line_text()


lines = [TextLine(u"abc"),
         TextLine(u"abc def"),
         TextLine(u"abc def  ghi"),
         TextLine(u"  abc  def  "),
        ]
l = lines[2]
l.point = 5



class ReadLineTextBuffer(TextLine):
    def __init__(self,txtstr, point = None, mark = None):
        super(ReadLineTextBuffer, self).__init__(txtstr, point, mark)
        self.enable_win32_clipboard = True
        self.selection_mark = -1
        self.enable_selection = True
        self.kill_ring = []

    def __repr__(self):
        return u'ReadLineTextBuffer'\
               u'("%s",point=%s,mark=%s,selection_mark=%s)'%\
                 (self.line_buffer, self.point, self.mark,self.selection_mark)


    def insert_text(self, char, argument=1):
        self.delete_selection()
        self.selection_mark = -1
        self._insert_text(char, argument)
    
    def to_clipboard(self):
        if self.enable_win32_clipboard:
                clipboard.set_clipboard_text(self.get_line_text())
    
######### Movement

    def beginning_of_line(self):
        self.selection_mark = -1
        self.point = StartOfLine
        
    def end_of_line(self):
        self.selection_mark = -1
        self.point = EndOfLine

    def forward_char(self,argument = 1):
        if argument < 0:
            self.backward_char(-argument)
        self.selection_mark = -1
        for x in range(argument):
            self.point = NextChar
        
    def backward_char(self, argument=1):
        if argument < 0:
            self.forward_char(-argument)
        self.selection_mark = -1
        for x in range(argument):
            self.point = PrevChar
        
    def forward_word(self,argument=1):
        if argument<0:
            self.backward_word(-argument)
        self.selection_mark=-1
        for x in range(argument):
            self.point = NextWordStart
       
    def backward_word(self, argument=1):
        if argument < 0:
            self.forward_word(-argument)
        self.selection_mark = -1
        for x in range(argument):
            self.point = PrevWordStart

    def forward_word_end(self, argument=1):
        if argument < 0:
            self.backward_word_end(-argument)
        self.selection_mark = -1
        for x in range(argument):
            self.point = NextWordEnd

    def backward_word_end(self, argument=1):
        if argument < 0:
            self.forward_word_end(-argument)
        self.selection_mark = -1
        for x in range(argument):
            self.point = NextWordEnd

######### Movement select
    def beginning_of_line_extend_selection(self):
        if self.enable_selection and self.selection_mark < 0:
            self.selection_mark = self.point
        self.point = StartOfLine
        
    def end_of_line_extend_selection(self):
        if self.enable_selection and self.selection_mark < 0:
            self.selection_mark = self.point
        self.point = EndOfLine

    def forward_char_extend_selection(self,argument=1):
        if argument < 0:
            self.backward_char_extend_selection(-argument)
        if self.enable_selection and self.selection_mark < 0:
            self.selection_mark = self.point
        for x in range(argument):
            self.point = NextChar
        
    def backward_char_extend_selection(self, argument=1):
        if argument < 0:
            self.forward_char_extend_selection(-argument)
        if self.enable_selection and self.selection_mark < 0:
            self.selection_mark = self.point
        for x in range(argument):
            self.point = PrevChar
        
    def forward_word_extend_selection(self, argument=1):
        if argument < 0:
            self.backward_word_extend_selection(-argument)
        if self.enable_selection and self.selection_mark < 0:
            self.selection_mark = self.point
        for x in range(argument):
            self.point = NextWordStart
       
    def backward_word_extend_selection(self, argument=1):
        if argument < 0:
            self.forward_word_extend_selection(-argument)
        if self.enable_selection and self.selection_mark < 0:
            self.selection_mark = self.point
        for x in range(argument):
            self.point = PrevWordStart

       
    def forward_word_end_extend_selection(self, argument=1):
        if argument < 0:
            self.backward_word_end_extend_selection(-argument)
        if self.enable_selection and self.selection_mark < 0:
            self.selection_mark = self.point
        for x in range(argument):
            self.point = NextWordEnd

    def backward_word_end_extend_selection(self, argument=1):
        if argument < 0:
            self.forward_word_end_extend_selection(-argument)
        if self.enable_selection and self.selection_mark < 0:
            self.selection_mark = self.point
        for x in range(argument):
            self.point = PrevWordEnd


######### delete       

    def delete_selection(self):
        if self.enable_selection and self.selection_mark >= 0:
            if self.selection_mark < self.point:
                del self[self.selection_mark:self.point]
                self.selection_mark = -1
            else:                
                del self[self.point:self.selection_mark]
                self.selection_mark = -1
            return True
        else:
            self.selection_mark = -1
            return False

    def delete_char(self, argument=1):
        if argument < 0:
            self.backward_delete_char(-argument)
        if self.delete_selection():
            argument -= 1
        for x in range(argument):
            del self[Point]
        
    def backward_delete_char(self, argument=1):
        if argument < 0:
            self.delete_char(-argument)
        if self.delete_selection():
            argument -= 1
        for x in range(argument):
            if self.point > 0:
                self.backward_char()
                self.delete_char()

    def forward_delete_word(self, argument=1):
        if argument < 0:
            self.backward_delete_word(-argument)
        if self.delete_selection():
            argument -= 1
        for x in range(argument):
            del self[Point:NextWordStart]

    def backward_delete_word(self, argument=1):
        if argument < 0:
            self.forward_delete_word(-argument)
        if self.delete_selection():
            argument -= 1
        for x in range(argument):
            del self[PrevWordStart:Point]

    def delete_current_word(self):
        if not self.delete_selection():
            del self[CurrentWord]
        self.selection_mark =- 1
        
    def delete_horizontal_space(self):
        if self[Point] in " \t":
            del self[PrevWordEnd:NextWordStart]
        self.selection_mark = -1
######### Case

    def upcase_word(self):
        p = self.point
        try:
            self[CurrentWord] = self[CurrentWord].upper()
            self.point = p
        except NotAWordError:
            pass
        
    def downcase_word(self):
        p = self.point
        try:
            self[CurrentWord] = self[CurrentWord].lower()
            self.point = p
        except NotAWordError:
            pass
        
    def capitalize_word(self):
        p = self.point
        try:
            self[CurrentWord] = self[CurrentWord].capitalize()
            self.point = p
        except NotAWordError:
            pass
########### Transpose
    def transpose_chars(self):
        p2 = Point(self)
        if p2 == 0:
            return
        elif p2 == len(self):
            p2 = p2 - 1
        p1 = p2 - 1
        self[p2], self[p1] = self[p1], self[p2]
        self.point = p2 + 1

    def transpose_words(self):
        word1 = TextLine(self)
        word2 = TextLine(self)
        if self.point == len(self):
            word2.point = PrevWordStart
            word1.point = PrevWordStart(word2)
        else:
            word1.point = PrevWordStart
            word2.point = NextWordStart
        stop1 = NextWordEnd(word1)
        stop2 = NextWordEnd(word2)
        start1 = word1.point
        start2 = word2.point
        self[start2:stop2] = word1[Point:NextWordEnd]
        self[start1:stop1] = word2[Point:NextWordEnd]
        self.point = stop2
        

############ Kill

    def kill_line(self):
        self.add_to_kill_ring(self[self.point:])
        del self.line_buffer[self.point:]
    
    def kill_whole_line(self):
        self.add_to_kill_ring(self[:])
        del self[:]
    
    def backward_kill_line(self):
        del self[StartOfLine:Point]
        
    def unix_line_discard(self):
        del self[StartOfLine:Point]
        pass

    def kill_word(self):
        """Kills to next word ending"""
        del self[Point:NextWordEnd]

    def backward_kill_word(self):
        """Kills to next word ending"""
        if not self.delete_selection():
            del self[PrevWordStart:Point]
        self.selection_mark = -1

    def forward_kill_word(self):
        """Kills to next word ending"""
        if not self.delete_selection():
            del self[Point:NextWordEnd]
        self.selection_mark = -1

    def unix_word_rubout(self):
        if not self.delete_selection():
            del self[PrevSpace:Point]
        self.selection_mark = -1

    def kill_region(self):
        pass

    def copy_region_as_kill(self):
        pass

    def copy_backward_word(self):
        pass

    def copy_forward_word(self):
        pass
        

    def yank(self):
        self.paste_from_kill_ring()

    def yank_pop(self):
        pass

##############  Mark 

    def set_mark(self):
        self.mark = self.point
        
    def exchange_point_and_mark(self):
        pass


    def copy_region_to_clipboard(self): # ()
        u'''Copy the text in the region to the windows clipboard.'''
        if self.enable_win32_clipboard:
                mark = min(self.mark, len(self.line_buffer))
                cursor = min(self.point, len(self.line_buffer))
                if self.mark == -1:
                        return
                begin = min(cursor, mark)
                end = max(cursor, mark)
                toclipboard = u"".join(self.line_buffer[begin:end])
                clipboard.SetClipboardText(toclipboard)

    def copy_selection_to_clipboard(self): # ()
        u'''Copy the text in the region to the windows clipboard.'''
        if self.enable_win32_clipboard and self.enable_selection and self.selection_mark >= 0:
                selection_mark = min(self.selection_mark,len(self.line_buffer))
                cursor = min(self.point,len(self.line_buffer))
                if self.selection_mark == -1:
                        return
                begin = min(cursor, selection_mark)
                end = max(cursor, selection_mark)
                toclipboard = u"".join(self.line_buffer[begin:end])
                clipboard.SetClipboardText(toclipboard)


    def cut_selection_to_clipboard(self): # ()
        self.copy_selection_to_clipboard()
        self.delete_selection()
##############  Paste


############## Kill ring
    def add_to_kill_ring(self,txt):
        self.kill_ring = [txt]
        if kill_ring_to_clipboard:
            clipboard.SetClipboardText(txt.get_line_text())


    def paste_from_kill_ring(self):
        if self.kill_ring:
            self.insert_text(self.kill_ring[0])


##################################################################
q = ReadLineTextBuffer(u"asff asFArw  ewrWErhg", point=8)
q = TextLine(u"asff asFArw  ewrWErhg", point=8)

def show_pos(buff, pos, chr = u"."):
    l = len(buff.line_buffer)
    def choice(bool):
        if bool:
            return chr
        else:
            return u" "
    return u"".join([choice(pos==idx) for idx in range(l + 1)])


def test_positioner(buff, points, positioner):
    print (u" %s "%positioner.__class__.__name__).center(40, u"-")
    buffstr = buff.line_buffer
    
    print u'"%s"'%(buffstr)
    for point in points:
        b = TextLine(buff, point = point)
        out=[u" "] * (len(buffstr) + 1)
        pos = positioner(b)
        if pos == point:
            out[pos] = u"&"
        else:
            out[point] = u"."
            out[pos] = u"^"
        print u'"%s"'%(u"".join(out))
    
if __name__ == "__main__":

    print u'%-15s "%s"'%(u"Position", q.get_line_text())
    print u'%-15s "%s"'%(u"Point", show_pos(q, q.point))


    for name, positioner in all_positioners:
        pos = positioner(q)
        []
        print u'%-15s "%s"'%(name, show_pos(q, pos, u"^"))

    l = ReadLineTextBuffer(u"kjjk asads   asad")
    l.point = EndOfLine
