from pathlib import Path
import re
import datetime
import io

if __name__ == '__main__':
    import django
    import sys
    import os
    from pathlib import Path
    home = Path(__file__).parent.parent
    sys.path.insert(0, str(home))
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djphys.settings')
    django.setup()

from courses.models import Meeting, URL, convert_longurls, convert_shorturls
from people.models import CourseRecord


def scan_urls(txt: str) -> str:
    """
    Scan txt for urls of the form https://saeta.physics.hmc.edu/order_field/XX-xxxx.html
    Replace with a reference of the form {xx-xxxx}.
    See course.models.convert_shorturls() for the reverse operation.

    """
    flags = re.MULTILINE | re.DOTALL
    regex = r'(https?://saeta.physics.hmc.edu/)([^ #"]*?\.html)'
    urls = dict()
    ct = 0
    for u in re.findall(regex, txt, flags=flags):
        base = u[0]
        fields = u[1].split('/')
        url = f'{base}{"/".join(fields[:-1])}'
        if url not in urls:
            urls[url] = "{%s}" % (fields[-1].split('.')[0]) # strip .html
            ct += 1

    for k, v in urls.items():
        txt = txt.replace(k, v)

    if False:
        # Now look to inject #URLS near the top of the file
        ustart = "# URLs\n"
        if ustart not in txt:
            n = txt.index("\n\n") # look for the first blank line
        else:
            n = txt.index(ustart) # here's the comment
            m = txt.index("# Meetings\n")
            txt = txt[:n] + txt[m - 1:] # remove old stuff

        mapping = "\n".join([f"{v} = {k}" for k, v in urls.items()])

        txt = txt[:n] + "\n\n" + ustart + mapping + txt[n:]
    return txt


class Scripter:
    flags = re.MULTILINE | re.DOTALL

    @staticmethod
    def load(file):
        path = None
        if isinstance(file, str):
            path = Path(file)
        elif isinstance(file, Path):
            path = file
        if path:
            all_lines = [x.rstrip('\n') for x in open(
                path, 'r', encoding='utf8').readlines()]
        else:
            all_lines = [str(x, encoding='utf8').rstrip('\n')
                         for x in file.readlines()]
        lines = iter(all_lines)
        if next(lines) != "# CourseDump":
            raise Exception(
                f"File {file} is not a CourseDump formatted file")
        info = dict()

        while True:
            try:
                k, v = next(lines).split(" = ")
                info[k] = v
            except:
                break
        crid = int(info.get('course_record', 0))
        course = info.get('course', "")
        year = int(info.get('year', 0))
        semester = info.get('semester')
        cr = None

        try:
            cr = CourseRecord.objects.get(
                course__order_field=course, year=year, semester=semester)
        except:
            crs = Course.objects.get(order_field=course)
            cr = CourseRecord.objects.create(
                course=crs, year=year, semester=semester)

        sc = Scripter(cr)
        sc.lines = lines
        # will hold shortcuts to urls of form URL[a-z] = <actual url>
        sc.urls = dict()
        sc.load_urls()
        sc.load_meetings()
        return sc

    def __init__(self, course_record, filename=""):
        self.course_record = course_record
        if filename:
            ftype = None
            if isinstance(filename, (str, Path)):
                self.path = Path(filename)
                self.file = open(self.path, 'w')
                ftype = "disk"
            elif isinstance(filename, io.IOBase):
                ftype = "ram"
                self.file = filename
            self.header()
            self.meetings()
            if ftype == "disk":
                self.file.close()
                txt = open(self.path, 'r').read()
                urlsubbed = convert_longurls(txt)
                open(self.path, 'w').write(urlsubbed)
            else:
                self.file.flush()
                txt = self.file.getvalue()
                urlsubbed = convert_longurls(txt)
                self.file.seek(0)
                self.file.write(urlsubbed)
                self.file.flush()

    @property
    def course(self):
        return self.course_record.course

    @property
    def order_field(self):
        return self.course.order_field

    @property
    def url(self):
        return f"/c/{self.order_field}/"

    def write(self, s: str):
        print(s.replace('\r', ''), file=self.file)

    def header(self):
        self.write("# CourseDump")
        self.write(f"course_record = {self.course_record.id}")
        self.write(f"course = {self.order_field}")
        self.write(f"year = {self.course_record.year}")
        self.write(f"semester = {self.course_record.semester}")
        self.write("") # blank line
        if hasattr(self, 'urls'):
            self.write("# URLs")
            for k, v in self.urls:
                self.write(f"{v} = {k}")
            self.write("") # blank line

    def meetings(self):
        self.write("# Meetings")
        for m in Meeting.objects.filter(course_record=self.course_record).order_by('date'):
            self.write(f"id = {m.id}")
            self.write(f"date = {m.iso}")
            self.write(f"type = {m.session_type}")
            for fld in ("topic", "homework", "reading", "exam"):
                val = getattr(m, fld, "")
                if val is None:
                    val = ""
                self.write(f"{fld} = {val}")
            if m.urls.count() > 0:
                for u in m.urls.all():
                    self.write(f"url = {u.script}")
            if m.notes or True:
                self.write("notes = '''")
                if m.notes:
                    self.write(self.sub_dollars(m.notes))
                self.write("'''")
            self.write(f"hide = {str(m.hide)}")
            self.write("") # a blank line

    def load_urls(self):
        """
        Any urls in the file should be in a section headed by
        # URLs
        We will look for lines that start with #
        When we see # Meetings, we know we're done with URLs
        """
        seenhash = False
        while True:
            l = next(self.lines)
            if l.startswith("# "):
                if l.startswith("# URLs"):
                    seenhash = True
                    continue
                else:
                    break
            try:
                k, v = l.strip().split(" = ")
                self.urls[k] = v
            except:
                pass

    def add_url(self, meeting: Meeting, url_str: str):
        """
        The url should points to a page link to this meeting. Identify it in
        URL.objects, adding as necessary, and make sure that the urls field
        of the meeting reflects reality.
        """
        # The url_str has the form <a href="https://..../">description</a>
        try:
            t = convert_shorturls(url_str, self.order_field)
            m = re.match(r'<a href="(.*?)">(.*?)</a>', t)
            url = m.group(1)
            description = m.group(2)

            u = URL.objects.filter(url=url)
            if u.count() == 0:
                u = URL.objects.create(url=url, description=description)
            else:
                u = u[0]
            meeting.urls.add(u)
        except Exception as eeps:
            pass

    @property
    def next_line(self):
        l = next(self.lines)
        # Undo any abbreviated URLs
        urlhack = re.search(r"(URL[a-z])", l)
        if urlhack:
            u = urlhack.group(1)
            l = l.replace(u, self.urls[u])
        return l

    def load_meetings(self):
        try:
            m = None
            while True:
                l = self.next_line

                if l == "":
                    if m is None:
                        continue
                    meeting = None
                    if 'id' in m:
                        pk = m.pop('id')
                    else:
                        print(f"There is a problem in {m}")

                    if 'date' in m:
                        m['date'] = datetime.date.fromisoformat(m['date'])

                    candidates = Meeting.objects.filter(
                        course_record=self.course_record,
                        date=m['date']
                    ).order_by('id')
                    if candidates.count() > 1:
                        meeting = candidates[0]
                        for c in candidates[1:]:
                            c.delete()
                    elif candidates.count() == 1:
                        meeting = candidates[0]

                    if 'urls' in m:
                        urls = m.pop('urls')
                    else:
                        urls = None

                    if not meeting:
                        m['course_record'] = self.course_record
                        meeting = Meeting.objects.create(**m)

                    # To handle deleted links to URLs, we need to empty
                    # the current ManyToManyField.
                    for u in meeting.urls.all():
                        meeting.urls.remove(u)

                    if urls:
                        for url in urls:
                            self.add_url(meeting, url)

                    for k, v in m.items():
                        setattr(meeting, k, v)
                    meeting.save()
                    print(f"Saved [{meeting.id}] {meeting}")

                    m, meeting = None, None

                elif "=" in l:
                    try:
                        k, v = l.split("=")
                    except Exception as oops:
                        print(f"I ran into error {oops} analyzing line {l}")
                    k, v = k.strip(), v.strip()
                    if k == "id":
                        m = dict(id=int(v))
                    elif k == "type":
                        m['session_type'] = v
                    elif k == "hide":
                        m['hide'] = v == 'True'
                    elif k == "url":
                        if "urls" not in m:
                            m['urls'] = []
                        m['urls'].append(v)
                    elif k in ("notes", "md"):
                        lines = []
                        while True:
                            l = self.next_line
                            if l == "'''":
                                m['notes'] = self.make_markdown(
                                    "\n".join(lines))
                                break
                            else:
                                lines.append(l)
                    else:
                        m[k] = v

        except Exception as eeps:
            if not isinstance(eeps, StopIteration):
                print(eeps)

    def make_markdown(self, s: str):
        r"""
        Convert text into acceptable markdown. This means converting single
        dollar signs into \( \).
        """
        if "$" in s:
            t = re.sub(r"\$(.*?)\$", r"\\\( \1 \\\)", s, flags=self.flags)
            return t
        return s

    def sub_dollars(self, s: str):
        t = re.sub(r"\\\\\( (.*?) \\\\\)", r"$\1$", s, flags=self.flags)
        return t.replace("\r", "")


if __name__ == "__main__":
    if False:
        cr = CourseRecord.objects.get(course__order_field='p151', year=2024)
        sc = Scripter(cr, "p151-2024.txt")
        print(f"Wrote to {sc.path.absolute()}")
    else:
        path = Path('/Users/saeta/Documents/Courses/p064/2023/p064-2023.txt')
        urls = scan_urls(open(path, 'r').read())
        # sc = Scripter.load(path)


