import re
from dataclasses import dataclass
import json
import requests
import wx
import uuid

### Dictionary stuff

line_regex = r"(\S+)\s(\S+)\s\[([^\]]+)\]\s(.+)"

@dataclass
class Word:
    simplified: str
    traditional: str
    pinyin: str
    definitions: list[str]

def parse_cedict(path: str):
    words = []
    with open(path) as file:
        for line in file:
            if line.startswith("#"):
                continue
            else:
                words.append(parse_line(line))
    return words

def parse_line(line: str):
    traditional, simplified, pinyin, definitions = re.match(line_regex, line).groups()
    return Word(
        simplified,
        traditional,
        pinyin,
        [d for d in definitions.split("/") if d],
    )

def build_pinyin_index(words: list[Word]):
    index = {}
    for word in words:
        normalized_word = normalize_pinyin(word.pinyin)
        if normalized_word in index:
            index[normalized_word].append(word)
        else:
            index[normalized_word] = [word]
    return index

def build_traditional_index(words: list[Word]):
    index = {}
    for word in words:
        if word.traditional in index:
            index[word.traditional].append(word)
        else:
            index[word.traditional] = [word]
    return index

## Tone markers

pinyinToneMarks = {
    u'a': u'āáǎà', u'e': u'ēéěè', u'i': u'īíǐì',
    u'o': u'ōóǒò', u'u': u'ūúǔù', u'ü': u'ǖǘǚǜ',
    u'A': u'ĀÁǍÀ', u'E': u'ĒÉĚÈ', u'I': u'ĪÍǏÌ',
    u'O': u'ŌÓǑÒ', u'U': u'ŪÚǓÙ', u'Ü': u'ǕǗǙǛ'
}

def convert_pinyin_callback(match):
    tone= int(match.group(3))
    vowel = match.group(1)
    # the 5th tone is neutral
    if tone == 5:
        return match

    # for multiple vowels, use first one if it is a/e/o, otherwise use second one
    pos=0
    if len(vowel) > 1 and not vowel[0] in 'aeoAEO':
        pos=1

    return vowel[0:pos]+pinyinToneMarks[vowel[pos]][tone-1]+vowel[pos+1:] + match.group(2)

def convert_pinyin(s: str):
    return re.sub(r'([aeiou]{1,3})(n?g?r?)([12345])', convert_pinyin_callback, s, flags=re.IGNORECASE)

def normalize_pinyin(s: str):
    return re.sub(r'[12345]', '', s).replace(' ', '')

### Ankiconnect

# TODO make deck and model configurable
def add_note(hanzi: str, pinyin: str, definition: str, tags: list[str]):
    request_data = {
        "version": 5,
        "action": "addNote",
        "params": {
            "note": {
                "deckName": "寫漢字",
                "modelName": "Mandarin Custom",
                "fields": {
                    "Pinyin": pinyin,
                    "Character": hanzi,
                    "Definition": definition
                },
                "tags": tags
            }
        }
    }
    print(json.dumps(request_data))
    r = requests.post("http://localhost:8765/", data=json.dumps(request_data))
    print(r.json())
    return r.json()

### UI stuff

class MainFrame(wx.Frame):
    def __init__(self):
        # set up the dictionary
        dictionary = parse_cedict("./cedict_1_0_ts_utf-8_mdbg.txt")
        self.pinyin_idx = build_pinyin_index(dictionary)
        self.traditional_idx = build_traditional_index(dictionary)
        # IDs of words selected to be in the deck
        self.selected_words = []
        # IDs of words in results
        self.result_words = []

        super().__init__(parent=None, title='Mandarin Anki', size = (460, 400))
        panel = wx.Panel(self)
        vertical_layout = wx.BoxSizer(wx.VERTICAL)
        results_vertical_layout = wx.BoxSizer(wx.VERTICAL)
        columns_layout = wx.BoxSizer(wx.HORIZONTAL)

        # search and results
        search_label = wx.StaticText(panel, label="Search:")
        self.search = wx.TextCtrl(panel, size = (200, -1))
        self.search.Bind(wx.EVT_TEXT, self.OnKeyTyped)

        self.results = wx.ListBox(panel, size = (200, 200))
        self.results.Bind(wx.EVT_LISTBOX, self.OnWordSelected)

        self.selected_hanzi = wx.StaticText(panel, label="")
        self.selected_pinyin = wx.StaticText(panel, label="")
        self.selected_definitions = wx.CheckListBox(panel, size= (200,200))
        tags_label = wx.StaticText(panel, label="Tags (comma separated):")
        self.tags = wx.TextCtrl(panel, size = (200, -1))
        results_vertical_layout.Add(self.selected_hanzi, 0)
        results_vertical_layout.Add(self.selected_pinyin, 0)
        results_vertical_layout.Add(self.selected_definitions, 0)
        results_vertical_layout.Add(tags_label)
        results_vertical_layout.Add(self.tags, 0)

        self.add_card = wx.Button(panel, label = "Add card")
        self.add_card.Bind(wx.EVT_BUTTON, self.OnAddCard)
        results_vertical_layout.Add(self.add_card, 0)


        vertical_layout.Add(search_label, 0)
        vertical_layout.Add(self.search, 0)
        vertical_layout.Add(columns_layout)
        columns_layout.Add(self.results, 0, wx.EXPAND)
        columns_layout.Add(results_vertical_layout, 0, wx.EXPAND)

        panel.SetSizer(vertical_layout)
        self.Show()

    def OnKeyTyped(self, event):
        if event.GetString():
            if normalize_pinyin(event.GetString()) in self.pinyin_idx:
                matches = self.pinyin_idx[normalize_pinyin(event.GetString())]
                self.results.Set([f"{w.traditional} {convert_pinyin(w.pinyin)} {w.definitions[0]}" for w in matches])
                self.result_words = matches
            elif event.GetString() in self.traditional_idx:
                matches = self.traditional_idx[event.GetString()]
                self.results.Set([f"{w.traditional} {convert_pinyin(w.pinyin)} {w.definitions[0]}" for w in matches])
                self.result_words = matches
            else:
                self.results.Set([])
                self.result_words = []

    def OnWordSelected(self, event):
        word = self.result_words[event.GetEventObject().GetSelection()]
        self.selected_word = word
        self.selected_hanzi.SetLabel(word.traditional)
        self.selected_pinyin.SetLabel(convert_pinyin(word.pinyin))
        self.selected_definitions.Clear()
        self.selected_definitions.InsertItems(word.definitions, 0)

    def OnAddCard(self, event):
        if not self.selected_word or not self.selected_definitions.GetCheckedItems():
            return
        print("adding note " + self.selected_word.traditional)
        tags = [tag.strip() for tag in self.tags.GetValue().split(",")]
        add_note(
            self.selected_word.traditional,
            convert_pinyin(self.selected_word.pinyin),
            ", ".join(self.selected_definitions.GetCheckedStrings()),
            tags
        )

if __name__ == '__main__':
    app = wx.App()
    frame = MainFrame()
    app.MainLoop()
