###############################################################
# Name:             Editor.py
# Purpose:     Subclass of StyledTextControl providing songpress
#            editor: loading, saving and syntax hilighting
# Author:         Luca Allulli (webmaster@roma21.it)
# Created:     2009-01-16
# Copyright: Luca Allulli (https://www.skeed.it/songpress)
# License:     GNU GPL v2
##############################################################

import codecs

from wx.stc import *
import wx.lib, wx.lib.newevent

from .SDIMainFrame import *
from .SongTokenizer import *
from . import Transpose


EventTextChanged, EVT_TEXT_CHANGED = wx.lib.newevent.NewEvent()


def get_text_from_clipboard():
    """
    Return text in clipboard, or None
    """
    if wx.TheClipboard.IsOpened():
        return None
    wx.TheClipboard.Open()
    do = wx.TextDataObject()
    if not wx.TheClipboard.GetData(do):
        return None
    wx.TheClipboard.Close()
    return do.GetText()


class Editor(StyledTextCtrl):
    def __init__(self, spframe, interactive=True, frame=None):
        self.frame = spframe.frame if frame is None else frame
        StyledTextCtrl.__init__(self, self.frame)
        self.spframe = spframe
        self.interactive = interactive
        if self.interactive:
            self.lastPos = 0
            self.frame.Bind(EVT_STC_CHANGE, self.OnTextChange, self)
            self.frame.Bind(EVT_STC_UPDATEUI, self.OnUpdateUI, self)
            self.Bind(EVT_STC_DOUBLECLICK, self.OnDoubleClick, self)
            self.Bind(wx.EVT_CHAR, self.OnChar, self)
            # self.Bind(EVT_STC_CLIPBOARD_COPY, self.frame.OnCopy, self)
            self.autoChangeMode = False
        self.frame.Bind(EVT_STC_STYLENEEDED, self.OnStyleNeeded, self)
        self.STC_STYLE_NORMAL = 11
        self.STC_STYLE_CHORD = 12
        self.STC_STYLE_COMMAND = 13
        self.STC_STYLE_ATTR = 14
        self.STC_STYLE_CHORUS = 15
        self.STC_STYLE_COMMENT = 16
        self.SetFont("Lucida Console", 12)
        self.SetLexer(STC_LEX_CONTAINER)
        self.StyleSetForeground(self.STC_STYLE_NORMAL, wx.Colour(0, 0, 0))
        self.StyleSetForeground(self.STC_STYLE_CHORUS, wx.Colour(0, 0, 0))
        self.StyleSetForeground(self.STC_STYLE_CHORD, wx.Colour(255, 0, 0))
        self.StyleSetForeground(self.STC_STYLE_COMMAND, wx.Colour(0, 0, 255))
        self.StyleSetForeground(self.STC_STYLE_ATTR, wx.Colour(0, 128, 0))
        self.StyleSetForeground(self.STC_STYLE_COMMENT, wx.Colour(128, 128, 128))
        self.StyleSetBold(self.STC_STYLE_CHORUS, True)
        #Dummy "token": we artificially replace every normalToken into a chorusToken when we are
        #inside chorus.  Then, we can associate the chorus style in self.tokenStyle dictionary.
        self.chorusToken = 'chorusToken'
        self.tokenStyle = {
            SongTokenizer.openCurlyToken: self.STC_STYLE_COMMAND,
            SongTokenizer.closeCurlyToken: self.STC_STYLE_COMMAND,
            SongTokenizer.normalToken: self.STC_STYLE_NORMAL,
            SongTokenizer.commandToken: self.STC_STYLE_COMMAND,
            SongTokenizer.attrToken: self.STC_STYLE_ATTR,
            SongTokenizer.chordToken: self.STC_STYLE_CHORD,
            SongTokenizer.closeChordToken: self.STC_STYLE_CHORD,
            SongTokenizer.colonToken: self.STC_STYLE_COMMAND,
            SongTokenizer.commentToken: self.STC_STYLE_COMMENT,
            self.chorusToken: self.STC_STYLE_CHORUS
        }
        #self.chorus[i] == True iff, at the end of line i, we are still in chorus (i.e. bold) mode
        self.chorus = []

    def SetFont(self, face, size):
        font = wx.Font(
            size,
            wx.FONTFAMILY_DEFAULT,
            wx.FONTSTYLE_NORMAL,
            wx.FONTWEIGHT_NORMAL,
            faceName = face
        )
        self.StyleSetFont(STC_STYLE_DEFAULT, font)
        #I don't know why, but the following line is necessary in order to make
        #the font bold
        self.StyleSetFont(self.STC_STYLE_CHORUS, font)

    def New(self):
        self.ClearAll()

    def Open(self):
        self.ClearAll()
        self.LoadFile(self.spframe.document)

    def Save(self):
        #self.SaveFile(self.spframe.document)
        t = self.GetText()
        f = codecs.open(self.spframe.document, 'w', 'utf-8')
        f.write(t)
        f.close()

    def GetTextOrSelection(self):
        start, end = self.GetSelection()
        if start == end:
            return self.GetText()
        return self.GetSelectedText()

    def ReplaceTextOrSelection(self, text):
        start, end = self.GetSelection()
        if start == end:
            self.SetText(text)
        else:
            self.ReplaceSelection(text)

    def GetChordUnderCursor(self):
        """Return info about chord under cursor, or None

        Return a 3-tuple: (begin, end, chord)
            begin: position before open bracket
            end: position after close bracket
            chord: chord, without brackets
        """
        pos, dummy = self.GetSelection()
        char = ""
        if self.GetSelectedText().find('[') != -1:
            start = pos
            while char != '[':
                next = self.PositionAfter(start)
                char = self.GetTextRange(start, next)
                start = next
            start = self.PositionBefore(start)
        else:
            start = self.PositionBefore(pos)
            while start >= 0 and char != '[' and char != '\n' and char != ']':
                char = self.GetTextRange(start, self.PositionAfter(start))
                if start == 0:
                    start = -1
                else:
                    start = self.PositionBefore(start)
            if start >= 0:
                start = self.PositionAfter(start)
            else:
                start = 0
        if char == '[':
            end = self.PositionBefore(pos)
            l = self.GetLength()
            while end < l and char != ']' and char != '\n':
                char = self.GetTextRange(end, self.PositionAfter(end))
                end = self.PositionAfter(end)
            if char == ']':
                return (start, end, self.GetTextRange(self.PositionAfter(start), self.PositionBefore(end)))
        return None

    def SelectNextChord(self):
        dummy, pos = self.GetSelection()
        n = self.GetLength()
        c = ''
        while pos < n:
            while pos < n and c != '[':
                c = self.GetTextRange(pos, self.PositionAfter(pos))
                pos = self.PositionAfter(pos)
            if c == '[':
                start = pos
                while pos < n and c != ']' and c != '\n':
                    c = self.GetTextRange(pos, self.PositionAfter(pos))
                    pos = self.PositionAfter(pos)
                if c == ']':
                    self.SetSelection(self.PositionBefore(start), pos)
                    return True
        return False

    def SelectPreviousChord(self):
        pos, dummy = self.GetSelection()
        c = ''
        done = False
        while not done:
            while not done and c != ']':
                c = self.GetTextRange(pos, self.PositionAfter(pos))
                if pos <= 0:
                    done = True
                else:
                    pos = self.PositionBefore(pos)
            if c == ']':
                end = pos
                while not done and c != '[' and c != '\n':
                    c = self.GetTextRange(pos, self.PositionAfter(pos))
                    if pos <= 0:
                        pos = -1
                        done = True
                    else:
                        pos = self.PositionBefore(pos)
                if pos == -1:
                    pos = 0
                else:
                    pos = self.PositionAfter(pos)
                if c == '[':
                    self.SetSelection(pos, self.PositionAfter(self.PositionAfter(end)))
                    return True
        return False

    def RemoveChordsInSelection(self):
        self.ReplaceSelection(Transpose.removeChords(self.GetSelectedText()))
        
    def CopyOnlyText(self):
        text = Transpose.removeChordPro(self.GetSelectedText())
        c = wx.TheClipboard
        if c.Open():
            c.SetData(wx.TextDataObject(text))
            c.Close()

    def OnTextChange(self, evt):
        if self.interactive:
            self.spframe.SetModified()
            self.spframe.TextUpdated()
            if not self.autoChangeMode:
                currentPos = self.GetCurrentPos()
                if currentPos - self.lastPos > 2:
                    evt = EventTextChanged(lastPos=self.lastPos, currentPos=currentPos)
                    wx.PostEvent(self.frame, evt)

    def OnUpdateUI(self, evt):
        self.lastPos = self.GetCurrentPos()
        evt.Skip(False)

    def OnDoubleClick(self, evt):
        t = self.GetChordUnderCursor()
        if t is not None:
            self.SetSelection(t[0], t[1])
        evt.Skip()

    def OnChar(self, evt):
        t = self.GetSelectedText()
        if t != '' and t[0] == '[' and t[-1] == ']':
            c = evt.GetKeyCode()
            if (c >= 65 and c <= 90) or (c >= 97 and c <= 122):
                s, e = self.GetSelection()
                self.SetSelection(self.PositionAfter(s), self.PositionBefore(e))
        evt.Skip()

    def AutoChangeMode(self, acm):
        self.autoChangeMode = acm
        
    def PasteChords(self):
        src = get_text_from_clipboard()
        if src is None:
            return
        self.BeginUndoAction()
        start, end = self.GetSelection()
        if start == end:
            l = self.LineFromPosition(start)
            end = self.PositionFromLine(l + len(src.splitlines()))
            if end == -1:
                end = self.GetLength()
            else:
                end = self.PositionBefore(end)
        prev = self.PositionBefore(end)
        while end > start and self.GetCharAt(prev) in [10, 13]:
            end = prev
            prev = self.PositionBefore(end)
        self.SetSelection(start, end)
        sel = self.GetSelectedText()
        if sel == "":
            sel = " "
        self.ReplaceSelection(Transpose.pasteChords(src, sel))
        self.EndUndoAction()

    def OnStyleNeeded(self, evt):
        end = evt.GetPosition()
        pos = self.GetEndStyled()
        ln = self.LineFromPosition(pos)
        start = self.PositionFromLine(ln)
        if ln > 0 and len(self.chorus) > ln-1:
            bold = self.chorus[ln-1]
        else:
            bold = False
        #changedBold is True iff self.chorus has changed for the current line, and thus we need
        #to process (at least) another line
        changedBold = False
        lc = self.GetLineCount()
        while (changedBold and ln < lc) or start < end:
            self.StartStyling(start)
            l = self.GetLine(ln)
            tkz = SongTokenizer(l)
            for tok in tkz:
                n = len(tok.content.encode('utf-8'))
                t = tok.token
                if bold and t == SongTokenizer.normalToken:
                    t = self.chorusToken
                self.SetStyling(n, self.tokenStyle[t])
                if t == SongTokenizer.commandToken:
                    if tok.content.upper() == 'SOC':
                        bold = True
                    elif tok.content.upper() == 'EOC':
                        bold = False
            if len(self.chorus) > ln:
                changedBold = self.chorus[ln] != bold
                self.chorus[ln] = bold
            else:
                self.chorus.append(bold)
                changedBold = False
            ln = ln + 1
            start = self.PositionFromLine(ln)


