from dramatist.drama.Drama import Drama
import re
import requests
import asyncio

from dramatist.core.DramatistParseException import DramatistParseException
from dramatist.drama.Markup import Markup
from dramatist.drama.Speaker import Speaker
from dramatist.drama.SpeechPart import SpeechPart
import xml.etree.ElementTree as ElementTree


class Dramatist:

    def from_file(self, file_path):
        """
        Load a drama from the given file path.
        :param file_path: The file path to the xml file
        :return: A drama
        """
        xml_root = ElementTree.parse(file_path).getroot()
        return self.__create_drama(xml_root)

    def from_api_by_name(self, corpus_name, play_name):
        """
        Load a drama from the DraCor API.
        :param corpus_name: The DraCor corpus name
        :param play_name: The DraCor play name
        :return: A drama
        """
        task1 = asyncio.create_task(
            self.__load_play_from_api(corpus_name, play_name)
        )

        return task1

    def from_api_by_id(self, play_id):
        """
        Load a drama from the DraCor API.
        :param play_id: The DraCor play id
        :return: A drama
        """
        task1 = asyncio.create_task(
            self.__load_play_from_id(play_id)
        )

        return task1

    async def __load_play_from_id(self, play_id):
        play_info = await self.__load_info(play_id)

        if play_info:
            corpus_name = play_info['corpus']
            play_name = play_info['name']
            return await self.__load_play_from_api(corpus_name, play_name)
        else:
            return None

    async def __load_info(self, play_id):
        headers = {
            'Accept': 'application/json'
        }

        response = requests.get(f'https://dracor.org/api/v1/id/{play_id}', headers=headers)
        if response.status_code == 200:
            result_json = response.json()
            return result_json
        else:
            return None

    async def __load_play_from_api(self, corpus_name, play_name):
        headers = {
            'Accept': 'application/xml'
        }

        response = requests.get(f'https://dracor.org/api/v1/corpora/{corpus_name}/plays/{play_name}/tei',
                                headers=headers)

        if response.status_code == 200:
            xml_root = ElementTree.fromstring(response.text)
            return self.__create_drama(xml_root)
        else:
            return None

    def __create_drama(self, xml_root):
        ns = self.__get_namespace(xml_root)
        list_person_content = xml_root.find(f'.//{ns}listPerson')
        body_content = xml_root.find(f'.//{ns}body')
        drama_name = xml_root.find(f'.//{ns}title[@type="main"]').text
        drama = Drama(drama_name)
        self.__get_text_from_element(ns, body_content, False, True, [], None, drama,
                                     False)
        drama.update_positions()
        self.__add_persons(ns, drama, list_person_content)

        return drama

    def __get_namespace(self, element):
        m = re.match(r'\{.*}', element.tag)
        return m.group(0) if m else ''

    def __get_text_from_element(self, ns, element, use_tail, is_first_act, stack, sibling_tag, drama, first_child):
        new_beginning = False
        result = ''
        markups = []

        if element.tag == f'{ns}div':
            div_type = element.attrib.get('type')
            stack.append(div_type)
        else:
            stack.append(element.tag.removeprefix(ns))

        if element.tag == f'{ns}div':
            div_type = element.attrib.get('type')

            if div_type == 'prologue':
                drama.new_act()

            if div_type == 'act':
                drama.new_act()

            if div_type == 'scene':
                drama.new_scene()

        if element.tag == f'{ns}sp':
            speaker_id = element.attrib.get('who')[1:]
            drama.new_speech(speaker_id)

        # There can be speech with only stage instruction before
        if element.tag == f'{ns}lg' and 'sp' not in stack:
            drama.new_speech('nn')

        if element.tag == f'{ns}l' or element.tag == f'{ns}head' or element.tag == f'{ns}stage':
            new_beginning = True

        # emph is ignored, so we need a space
        if element.tag == f'{ns}emph':
            if not first_child:
                result += ' '
            markups.append(Markup(0, -1, 'emph'))

        if element.tag == f'{ns}pb':
            if not first_child:
                result += ' '

        if element.text:
            text = element.text
            if text:
                cleaned_text = self.__clean_text(text)
                if cleaned_text:
                    new_beginning = False
                    result += cleaned_text

        sub_sibling_tag = None
        is_first = True
        for e_pos, text in enumerate(element):
            is_first_child_at_start = False

            if is_first:
                if 'p' == stack[-1] and not result:
                    is_first_child_at_start = True
                elif new_beginning:
                    is_first_child_at_start = True

            if 'p' in stack and 'p' == stack[-1]:
                if result:
                    if is_first:
                        drama.add_speech(result.strip(), SpeechPart.TYPE_SPEECH_P_B, markups)
                        markups = []
                    else:
                        drama.add_speech(result.strip(), SpeechPart.TYPE_SPEECH_P, markups)
                        markups = []

                    result = ''

            inner_text, is_first_act, sub_sibling_tag, new_markups = (
                self.__get_text_from_element(ns, text, True, is_first_act, stack, sub_sibling_tag, drama,
                                             is_first_child_at_start))

            for nm in new_markups:
                if result:
                    nm.start = nm.start + len(result) + 1
                    nm.end = nm.end + len(result) + 1
                else:
                    nm.start = nm.start
                    nm.end = nm.end

            markups.extend(new_markups)
            result += inner_text
            is_first = False

        if 'emph' == stack[-1]:
            length = len(result.strip())
            markups[-1].end = length

        if 'l' in stack and 'l' == stack[-1]:
            if 'epigraph' in stack:
                drama.acts[-1].add_text_to_epigraph(result.strip())
            else:
                drama.add_speech(result.strip(), SpeechPart.TYPE_SPEECH_LINE, markups)
                markups = []

            result = ''

        # there can be quotes
        if 'bibl' == stack[-1]:
            drama.acts[-1].add_text_to_epigraph(result.strip())
            result = ''

        if 'p' in stack and 'p' == stack[-1] and 'sp' in stack:
            if result:
                if sub_sibling_tag:
                    drama.add_speech(result.strip(), SpeechPart.TYPE_SPEECH_P, markups)
                    markups = []
                else:
                    drama.add_speech(result.strip(), SpeechPart.TYPE_SPEECH_P_B, markups)
                    markups = []

            result = ''

        if 'speaker' in stack:
            drama.set_speaker(result)
            result = ''

        if 'stage' in stack and 'stage' == stack[-1]:
            if 'p' in stack:
                if first_child:
                    # TODO: improve special case, see SpeechPart.py
                    drama.add_speech(result.strip(), SpeechPart.TYPE_STAGE_DIRECTION_COUNT, markups)
                    markups = []
                else:
                    drama.add_speech(result.strip(), SpeechPart.TYPE_STAGE_DIRECTION_INLINE, markups)
                    markups = []
                result = ''
            elif 'sp' in stack:
                if (sibling_tag == 'lg' or sibling_tag == 'l' or sibling_tag == 'head' or sibling_tag == 'stage' or
                        sibling_tag == 'p'):
                    drama.add_speech(result.strip(), SpeechPart.TYPE_STAGE_DIRECTION, markups)
                    markups = []
                    result = ''
                else:
                    drama.add_speech(result.strip(), SpeechPart.TYPE_STAGE_DIRECTION_INLINE, markups)
                    markups = []
                    result = ''
            elif 'scene' in stack:
                drama.add_scene_stage_direction(result)
            elif 'act' in stack:
                drama.add_act_stage_direction(result)
            elif 'prologue' in stack:
                drama.add_act_stage_direction(result)
            else:
                raise DramatistParseException('Unknown stage case')

        if 'scene' in stack and 'head' in stack and 'head' == stack[-1]:
            drama.set_scene_title(result)
            result = ''
        elif 'prologue' in stack and 'head' in stack and 'head' == stack[-1]:
            drama.set_act_title(result)
            result = ''
        elif 'act' in stack and 'head' in stack and 'head' == stack[-1]:
            drama.set_act_title(result)
            result = ''

        if 'trailer' in stack:
            drama.set_trailer(result)
            result = ''

        if use_tail and element.tail:
            text = element.tail
            if text:
                if 'p' in stack and 'stage' == stack[-1]:
                    result += ' '

                if 'emph' in stack:
                    result += ' '

                result += self.__clean_text(text)
                result = result.rstrip(' ')

        last_tag = stack.pop()

        return result, is_first_act, last_tag, markups

    def __clean_text(self, text):
        result = re.sub(" +", " ", text, flags=re.DOTALL)

        if not result == ' ':
            result = text.strip()

        result = re.sub(" *\n *", " ", result, flags=re.DOTALL)
        # result = re.sub(" +", " ", result, flags=re.DOTALL)
        return result

    def __add_persons(self, ns, drama, list_person_element):
        for person_elem in list_person_element.findall(f'{ns}person'):
            p_id = person_elem.get('{http://www.w3.org/XML/1998/namespace}id')
            sex = person_elem.attrib.get('sex')
            name = person_elem[0].text
            speaker = Speaker(p_id, name, sex)
            drama.add_speaker(speaker)
