# -*- 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,log_sock
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("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("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("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()==" ":
            while point>0 and line[point-1:point].get_line_text()==" ":
                point-=1
        while point>0 and line[point-1:point].get_line_text()!=" ":
            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
Mark=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("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 '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("\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):
        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("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("abc"),
       TextLine("abc def"),
       TextLine("abc def  ghi"),
       TextLine("  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 'ReadLineTextBuffer("%s",point=%s,mark=%s,selection_mark=%s)'%(self.line_buffer,self.point,self.mark,self.selection_mark)


    def insert_text(self,char):
        self.delete_selection()
        self.selection_mark=-1
        self._insert_text(char)
    
    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[self.point:].to_clipboard()
        self.add_to_kill_ring(self[self.point:])
        del self.line_buffer[self.point:]
    
    def kill_whole_line(self):
        #self[:].to_clipboard()
        self.add_to_kill_ring(self[:])
        del self[:]
    
    def backward_kill_line(self):
        #self[StartOfLine:Point].to_clipboard()
        del self[StartOfLine:Point]
        
    def unix_line_discard(self):
        del self[StartOfLine:Point]
        pass

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

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

    def forward_kill_word(self):
        """Kills to next word ending"""
        #self[Point:NextWordEnd].to_clipboard()
        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): # ()
        '''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="".join(self.line_buffer[begin:end])
                clipboard.SetClipboardText(toclipboard)

    def copy_selection_to_clipboard(self): # ()
        '''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="".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("asff asFArw  ewrWErhg",point=8)
q=TextLine("asff asFArw  ewrWErhg",point=8)

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


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

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


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

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