import os
import re
import urllib.parse
import html
import mimetypes
import shutil
import enum
import glob
from types import SimpleNamespace as Empty

import markdown
import jinja2

from . import log
from . import walktree
from . import members

from .search import CssClass, ResultFlag, ResultMap, Trie, serialize_search_data, base85encode_search_data, search_filename, searchdata_filename, searchdata_filename_b85, searchdata_format_version

from .markdown.admonition import AdmonitionExtension
from .markdown.fix_links import FixLinksExtension
from .markdown.add_classes import AddClassesExtension
from .markdown.record_images import RecordLinkedImagesExtension
from .markdown.mdx_subscript import SubscriptExtension
from .markdown.mdx_superscript import SuperscriptExtension
import mdx_math_svg

doxpp_path = os.path.dirname(os.path.realpath(__file__))
default_templates = os.path.join(doxpp_path, 'html_templates')

class Status:
    # This defines the status of our generator
    def __init__(self, data, options):
        # Data read in from JSON file
        self.data = data

        # These dictionaries contain the same dictionaries as in 'data', but indexed by their ID so they're
        # easy to find. It is the *same* dictionaries, modifying these will modify 'data'.
        self.members = walktree.create_member_dict(data['members'])
        self.headers = walktree.create_element_dict(data['headers'])
        self.groups = walktree.create_element_dict(data['groups'])
        self.pages = walktree.create_element_dict(data['pages'])

        # This dictionary links each unique ID to a page where it can be found.
        # To link to an ID, link to "<page>.html#<ID>", unless page==ID, in which case
        # it suffices to link to "<page>.html".
        # Items not in here are not documented.
        self.id_map = {
            'classes': 'classes',
            'files': 'files',
            'index': 'index',
            'modules': 'modules',
            'namespaces': 'namespaces',
            'pages': 'pages'
        }

        # This dictionary links page_id to the compound with the relevant information for that page.
        self.html_pages = {}

        # This is the set of images referenced in the documentation
        self.images = set()

        # Options
        self.show_private_virtual = options['show_private_virtual']
        self.show_private_nonvirtual = options['show_private_nonvirtual']
        self.show_protected = options['show_protected']
        self.show_undocumented = options['show_undocumented']
        self.modify_include_statement = options['modify_include_statement']

    def get_link(self, id):
        # Convert an ID into a URL to link to
        try:
            page = self.id_map[id]
        except KeyError:
            log.warning('Referencing type %s, which was excluded from documentation', id)  # TODO: this is normal, though...
            return None
        if page == id:
            return page + '.html'
        else:
            return page + '.html#' + id

    def find_title(self, id):
        # This is used only in process_navbar_links()
        standard = {
            'classes': 'Classes',
            'files': 'Files',
            'index': 'Home',
            'modules': 'Modules',
            'namespaces': 'Namespaces',
            'pages': 'Pages'
        }
        if id in standard:
            return standard[id]
        if id in self.members:
            return self.members[id]['name']
        if id in self.headers:
            return self.headers[id]['name']
        if id in self.groups:
            return self.groups[id]['name']
        if id in self.pages:
            return self.pages[id]['title']
        # TODO: how about links to (sub-)sections and anchors?
        return '(unknown)'

    def get_compound(self, id):
        if id in self.members:
            return self.members[id]
        elif id in self.headers:
            return self.headers[id]
        elif id in self.groups:
            return self.groups[id]
        elif id in self.pages:
            return self.pages[id]
        else:
            #log.error("Looking for 'compound' data structure for unknown id = %s", id)
            return {}

html_tag_re = re.compile('<.*?>')
def strip_html_tags(title):
    return html_tag_re.sub('', title)

def generate_fully_qualified_names(members, status: Status):
    for member in members:
        name = member['name']
        parent = member['parent']
        if parent:
            if parent in status.members:
                name = status.members[parent]['fully_qualified_name'] + '::<wbr />' + name
            else:
                log.error('Member %s has as parent an unknown id %s', name, parent)
                name = '<unknown>::<wbr />' + name
        member['fully_qualified_name'] = name
        if 'members' in member:
            generate_fully_qualified_names(member['members'], status)

def register_anchors_to_page(compound, page_id, status: Status):
    for section in compound['sections']:
        if section[0] == 'search':
            log.error("ID of (sub-)section is 'search', this will interfere with the search functionality.")
        status.id_map[section[0]] = page_id
    for anchor in compound['anchors']:
        if anchor == 'search':
            log.error("ID of anchor is 'search', this will interfere with the search functionality.")
        status.id_map[anchor] = page_id

def document_member_on_page(member, page_id, status: Status):
    if member['id'] == 'search':
        log.error("ID of documented element is 'search', this will interfere with the search functionality.")
        # TODO: Solve by changing ID of element?
        # TODO: There are other "reserved IDs" on documentation pages, should we protect those as well?
    member['page_id'] = page_id
    status.id_map[member['id']] = page_id
    register_anchors_to_page(member, page_id, status)

def document_member_variables_on_page(member, page_id, status: Status):
    for var in member['variables']:
        document_member_on_page(var, page_id, status)

def create_page(compound, status: Status):
    page_id = compound['id']
    document_member_on_page(compound, page_id, status)
    status.html_pages[page_id] = compound

def delete_page(compound, status: Status):
    del status.id_map[compound['id']]
    del status.html_pages[compound['page_id']]
    compound['page_id'] = ''
    # This function is only evert called for undocumented compounds, which necessarily will not have any
    # sections or anchors, so we don't have to worry about undoing any actions by `register_anchors_to_page()`.

def show_member(member, status: Status):
    if not (member['brief'] or (member['member_type'] == 'enum' and member['has_value_details'])):
        if not status.show_undocumented:
            return False
    if 'access' in member:
        if member['access'] == 'private':
            if 'virtual' in member and member['virtual']:
                if not status.show_private_virtual:
                    return False
            else:
                if not status.show_private_nonvirtual:
                    return False
        elif member['access'] == 'protected':
            if not status.show_protected:
                return False
    return True

def is_class_like(member):
    return member['member_type'] in ['class', 'struct', 'union']

def class_has_documented_members(compound):
    return (compound['typeless_functions'] or compound['groups'] or compound['groups_names'] or
            compound['classes'] or compound['enums'] or compound['aliases'] or compound['functions'] or
            compound['operators'] or compound['variables'] or compound['related'])

def class_is_simple(compound):  # This is true if it has no documented members other than variables
    return not (compound['typeless_functions'] or compound['groups'] or compound['groups_names'] or
                compound['classes'] or compound['enums'] or compound['aliases'] or compound['functions'] or
                compound['operators'] or compound['related'] or compound['bases'] or compound['derived'])

def compound_has_documented_members(compound):  # not for classes
    return (compound['modules'] or compound['namespaces'] or compound['classes'] or compound['enums'] or
            compound['aliases'] or compound['functions'] or compound['operators'] or compound['variables'] or
            compound['macros'])

def add_compound_member_booleans(compound):
    compound['has_class_details'] = False
    compound['has_enum_details'] = False
    compound['has_alias_details'] = False
    compound['has_function_details'] = False
    compound['has_variable_details'] = False
    compound['has_macro_details'] = False

def add_class_member_lists(compound):
    compound['typeless_functions'] = []
    compound['groups'] = []
    compound['groups_names'] = {}
    compound['classes'] = []
    compound['enums'] = []
    compound['aliases'] = []
    compound['functions'] = []
    compound['operators'] = []
    compound['variables'] = []
    add_compound_member_booleans(compound)

def add_compound_member_lists(compound):  # not for classes
    compound['modules'] = []
    compound['namespaces'] = []
    compound['classes'] = []
    compound['enums'] = []
    compound['aliases'] = []
    compound['functions'] = []
    compound['operators'] = []
    compound['variables'] = []
    compound['macros'] = []
    add_compound_member_booleans(compound)

def process_enum_values(member, status: Status):
    # member must be an enum
    # We only get here if member is being shown
    for value in member['members']:
        document_member_on_page(value, member['page_id'], status)

def process_class_member(compound, member, status: Status):
    # compound must be a class/struct/union
    if is_class_like(member):
        process_class(member, status)
        if member['simple']:
            document_member_on_page(member, compound['id'], status)
            document_member_variables_on_page(member, compound['id'], status)
            if member['doc'] or member['variables']:
                compound['has_class_details'] = True
        if 'page_id' in member:
            compound['classes'].append(member)
    elif show_member(member, status):
        document_member_on_page(member, compound['id'], status)
        member_type = member['member_type']
        if member['group']:
            if member['group'] not in compound['groups_names']:
                compound['groups_names'][member['group']] = len(compound['groups'])
                compound['groups'].append({
                    'name': member['group'],
                    'id': 'group--' + urllib.parse.quote(member['group']),
                    'members': []
                })
            compound['groups'][compound['groups_names'][member['group']]]['members'].append(member)
        elif member_type == 'enum':
            compound['enums'].append(member)
            process_enum_values(member, status)
        elif member_type == 'alias':
            compound['aliases'].append(member)
        elif member_type == 'function':
            if member['method_type'] == 'method':
                compound['functions'].append(member)
            elif member['method_type'] == 'operator':
                compound['operators'].append(member)
            else:
                compound['typeless_functions'].append(member)  # this can actually also be an assignment operator
        elif member_type == 'variable':
            compound['variables'].append(member)
        else:
            log.error("Member of type %s cannot be listed on %s page", member_type, compound['member_type'])
            return
        if member['doc'] or (member_type == 'enum' and member['has_value_details']):
            compound['has_' + member_type + '_details'] = True

def process_namespace_member(compound, member, status: Status):
    # compound must be a namespace
    if member['member_type'] != 'namespace' and not member['header']:
        # TODO: This only happens because of an issue with parent template class defined in other header file
        log.error("Member %s doesn't have a header file, this is a bug in dox++parse that needs to be fixed", member['id'])
    header_compound = status.headers[member['header']] if member['header'] else {}
    group_compound = status.groups[member['group']] if member['group'] else {}
    if is_class_like(member):
        process_class(member, status)
        if member['simple']:
            if member['group']:
                page_id = member['group']
            elif compound['id']:
                page_id = compound['id']
            else:
                page_id = member['header']
            document_member_on_page(member, page_id, status)
            document_member_variables_on_page(member, page_id, status)
            if member['doc'] or member['variables']:
                (compound if page_id == compound['id'] else status.get_compound(page_id))['has_class_details'] = True
        if 'page_id' in member:
            compound['classes'].append(member)
            if header_compound:
                header_compound['classes'].append(member)
            if group_compound:
                group_compound['classes'].append(member)
    elif member['member_type'] == 'namespace':
        process_namespace(member, status)
        if 'page_id' in member:
            compound['namespaces'].append(member)
            if header_compound:
                header_compound['namespaces'].append(member)
            if group_compound:
                group_compound['namespaces'].append(member)
    elif show_member(member, status):
        if member['relates']:
            page_id = member['relates']
            # Replace the ID in the list of related members by the member itself
            related_list = status.get_compound(page_id)['related']
            try:
                index = related_list.index(member['id'])
                related_list[index] = member
            except ValueError:
                # This should not occur, but let's make sure
                related_list.append(member)
        elif member['group']:
            page_id = member['group']
        elif compound['id']:
            page_id = compound['id']
        else:
            page_id = member['header']
        document_member_on_page(member, page_id, status)
        member_type = member['member_type']
        if member_type == 'enum':
            compound['enums'].append(member)
            if header_compound:
                header_compound['enums'].append(member)
            if group_compound:
                group_compound['enums'].append(member)
            process_enum_values(member, status)
        elif member_type == 'alias':
            compound['aliases'].append(member)
            if header_compound:
                header_compound['aliases'].append(member)
            if group_compound:
                group_compound['aliases'].append(member)
        elif member_type == 'function':
            if member['operator']:
                compound['operators'].append(member)
                if header_compound:
                    header_compound['operators'].append(member)
                if group_compound:
                    group_compound['operators'].append(member)
            else:
                compound['functions'].append(member)
                if header_compound:
                    header_compound['functions'].append(member)
                if group_compound:
                    group_compound['functions'].append(member)
        elif member_type == 'variable':
            compound['variables'].append(member)
            if header_compound:
                header_compound['variables'].append(member)
            if group_compound:
                group_compound['variables'].append(member)
        elif member_type == 'macro':
            compound['macros'].append(member)
            if header_compound:
                header_compound['macros'].append(member)
            if group_compound:
                group_compound['macros'].append(member)
        else:
            log.error("Member of type %s cannot be listed on %s page", member_type, compound['member_type'])
            return
        if member['doc'] or (member_type == 'enum' and member['has_value_details']):
            (compound if page_id == compound['id'] else status.get_compound(page_id))['has_' + member_type + '_details'] = True

def process_class(compound, status: Status):
    # compound must be a class/struct/union
    # Process namespace members
    for member in compound['members']:
        process_class_member(compound, member, status)
    # Create a page for this one?
    compound['simple'] = False
    if compound['id'] and (show_member(compound, status) or class_has_documented_members(compound)):
        compound['simple'] = class_is_simple(compound)
        if not compound['simple']:
            create_page(compound, status)

def process_namespace(compound, status: Status):
    # compound must be a namespace
    # Process namespace members
    for member in compound['members']:
        process_namespace_member(compound, member, status)
    # Create a page for this one?
    if compound['id'] and (show_member(compound, status) or compound_has_documented_members(compound)):
        create_page(compound, status)

def has_value_details(member):
    for child in member['members']:
        if child['brief']:
            return True
    return False

def verify_namespace_header(member):
    for child in member['members']:
        if child['header'] and child['header'] != member['header']:
            member['header'] = ''
            return

def find_header_file(compound):
    # TODO: Modules without members, that only have submodules, should be assigned a header file.
    # We first count how many members have the same header file
    headers = {}
    for members in (compound['classes'], compound['enums'], compound['aliases'], compound['functions'],
                    compound['operators'], compound['variables'], compound['macros']):
        for member in members:
            if member['header'] in headers:
                headers[member['header']] += 1
            else:
                headers[member['header']] = 1
    # Any header file that is shown in 80% of the members we take as the header for the compound
    threshold = 0.8 * sum(headers.values())
    compound_header = max(headers, key=lambda m: headers[m], default='')
    if compound_header in headers and headers[compound_header] < threshold:
        compound_header = ''
    compound['header'] = compound_header

def process_base_derived_related_lists(status: Status):
    for member in status.members.values():
        if 'bases' in member and member['bases']:
            member['base_classes'] = []
            for base in member['bases']:
                if 'id' in base:
                    tmp = status.members[base['id']].copy()  # we make a copy so we can change the 'access' value.
                    tmp['access'] = base['access']
                    member['base_classes'].append(tmp)
                else:
                    tmp = {
                        'member_type': 'class',  # TODO: we don't know, could be struct.
                        'id': '',
                        'page_id': '',
                        'name': html.escape(base['typename']),
                        'fully_qualified_name': html.escape(base['typename']),
                        'access': base['access']
                    }
                    member['base_classes'].append(tmp)
        if 'derived' in member and member['derived']:
            member['derived_classes'] = []
            for id in member['derived']:
                member['derived_classes'].append(status.members[id])
        if 'related' in member and member['related']:
            related_list = member['related']
            member['related'] = []
            for related in related_list:
                if isinstance(related, dict):
                    member['related'].append(related)

def assign_page(status: Status):
    # TODO: Add option `show_if_documented_children`
    # All header files will have a page, whether they're documented or not
    for header in status.headers.values():
        add_compound_member_lists(header)
        header['member_type'] = 'file'
        create_page(header, status)
    # All groups (modules) will have a page, whether they're documented or not
    for group in status.groups.values():
        if not group['name']:
            # We need a name!
            # Apparently this group was ony added to, never documented.
            group['name'] = group['id']
        add_compound_member_lists(group)
        group['member_type'] = 'module'
        create_page(group, status)
    # Prepare class and namespace members with needed fields. Also:
    #  - find out if enum members have documented values
    #  - find out if namespace members all have the same header file, and reset namespace header if not
    for member in status.members.values():
        if not member['id']:
            continue
        if member['member_type'] == 'namespace':
            add_compound_member_lists(member)
            verify_namespace_header(member)
        elif is_class_like(member):
            add_class_member_lists(member)
        elif member['member_type'] == 'enum':
            member['has_value_details'] = has_value_details(member)
    # Assign members to a specific page
    base = {  # bogus namespace to get things rolling
        'id': '',
        'member_type': 'namespace',
        'members': status.data['members'],
        'header': '',
        'group': ''
    }
    add_compound_member_lists(base)
    process_namespace(base, status)
    # Assign a header file to groups and namespaces
    for member in status.members.values():
        if member['id'] and member['header']:
            member['header'] = status.modify_include_statement(member['header'])
    for group in status.groups.values():
        find_header_file(group)
    for member in status.members.values():
        if member['id'] and member['member_type'] == 'namespace':
            find_header_file(member)
    # Don't show undocumented header files that don't list any members
    for header in status.headers.values():
        if not compound_has_documented_members(header) and not header['brief']:
            delete_page(header, status)
    # Don't show undocumented groups (modules) that don't list any members (these are unlikely to exist...)
    for group in status.groups.values():
        if not compound_has_documented_members(group) and not group['brief']:
            delete_page(group, status)
    # Assign sub-groups to be shown in parent group's page; they're always sorted
    for group in status.groups.values():
        group['modules'] = []
        for id in group['subgroups']:
            if status.groups[id]['page_id']:
                group['modules'].append(status.groups[id])
        group['modules'].sort(key=lambda x: x['name'].casefold())
    # All pages have a page, obviously
    for page in status.pages.values():
        page['member_type'] = 'page'
        create_page(page, status)

def create_indices(status: Status):
    index = {}
    index['symbols'] = []   # Used in classes and namespaces index
    index['files'] = []     # Used in files index
    index['modules'] = []   # Used in modules index
    index['pages'] = []     # used in pages index
    # Symbols (class, struct, union and namespace)
    for member in status.members.values():
        if not member['id']:
            continue
        if member['member_type'] not in ['namespace', 'class', 'struct', 'union']:  # TODO: rewrite using is_class_like()
            continue
        member['children'] = []
        if 'page_id' not in member or not member['page_id']:
            continue
        if member['parent']:
            parent = status.members[member['parent']]
            parent['children'].append(member) # Parent has been processed earlier, has a 'children' key
            if member['member_type'] == 'namespace':
                parent['has_child_namespace'] = True
        else:
            index['symbols'].append(member)
    index['symbols'].sort(key=lambda x: x['name'].casefold())
    for member in status.members.values():
        if 'children' in member:
            member['children'].sort(key=lambda x: x['name'].casefold())
    # Files (headers)
    index['files'] = walktree.build_file_hierarchy(status.data['headers'])
    # Modules (groups)
    status.data['groups'].sort(key=lambda x: x['name'].casefold())
    for group in status.data['groups']:
        if not group['page_id']:
            continue
        if not group['parent']:
            index['modules'].append(group)
        group['children'] = group['modules']
    # Pages
    status.data['pages'].sort(key=lambda x: x['title'].casefold())
    for page in status.data['pages']:
        if page['id'] == 'index':
            continue  # the index is not on the list of pages
        if not page['parent']:
            index['pages'].append(page)
        page['children'] = []
        prev = None
        nav_list = []
        for s in page['subpages']:
            # We don't sort children, but use them in the order they're referenced in the parent page
            child = status.pages[s]
            page['children'].append(child)
            nav_list.append((child['id'] + '.html', child['title']))
        if nav_list:
            prev = None
            parent = (page['id'] + '.html', page['title'])
            for i in range(len(nav_list)):
                next = nav_list[i + 1] if len(nav_list) > i + 1 else None
                page['children'][i]['footer_navigation'] = (prev, parent, next)
                prev = nav_list[i]
    return index

def process_navbar_links(navbar, status: Status):
    # Convert (title, id, sub) into (title, link, id, sub)
    out = []
    for title, id, sub in navbar:
        if sub:
            sub = process_navbar_links(sub, status)
        if len(id) > 1 and id[0] == '#':
            id = id[1:]
            link = status.get_link(id)
            if not link:
                log.error("Navbar references an id that is not in the documentation")
            if not title:
                title = status.find_title(id)
        else:
            link = id
            id = ''
        out.append((title, link, id, sub))
    return out

def remove_p_tag(title):
    # Markdown always encloses its output in paragraph tags, but we don't want these when it's
    # a page or section title, nor for the brief docs.
    if title[:3] == '<p>' and title[-4:] == '</p>':
        title = title[3:-4]
    if '<p>' in title:
        log.warning("Title contains a HTML paragraph tag: %s", title)
    return title

def process_sections_recursive(sections, level, md):
    if sections[0][2] < level:
        return []
    section = sections.pop(0)
    subsections = []
    while sections and sections[0][2] > section[2]:
        subsections.append(process_sections_recursive(sections, level + 1, md))
    return (section[0], remove_p_tag(md.reset().convert(section[1])), subsections)

def process_sections(compound, md):
    # compound['sections'] is a flat list of tuples [(name, title, level), ...]
    # We turn it into a hierarchical list according to level: [(name, title, [...]), ...]
    # We also apply Markdown processing to `title`
    sections = compound['sections']
    compound['sections'] = []
    while sections:
        compound['sections'].append(process_sections_recursive(sections, 1, md))

def parse_markdown(status: Status, math_cache_file):
    math_svg_extension = mdx_math_svg.MathSvgExtension(inline_class='m-math', display_class='m-math', fontsize=1)
    extensions = [
        # Extensions packaged with `markdown`:
        'attr_list',        # https://python-markdown.github.io/extensions/attr_list/
        'md_in_html',       # https://python-markdown.github.io/extensions/md_in_html/
        'tables',           # https://python-markdown.github.io/extensions/tables/
        'fenced_code',      # https://python-markdown.github.io/extensions/fenced_code_blocks/
        'codehilite',       # https://python-markdown.github.io/extensions/code_hilite/
        'sane_lists',       # https://python-markdown.github.io/extensions/sane_lists/
        'smarty',           # https://python-markdown.github.io/extensions/smarty/
        # Installed with package `markdown-headdown`
        'mdx_headdown',     # https://github.com/SaschaCowley/Markdown-Headdown
        # Installed with package `mdx_math_svg`
        math_svg_extension,         # https://github.com/crisluengo/mdx_math_svg
        # Our own concoctions
        AdmonitionExtension(),              # Modification of the standard 'admonition' extension
        FixLinksExtension(status.id_map),   # Fixes links from '#id' to 'page_id.html#id'
        AddClassesExtension(),              # Adds m.css classes to <img> and <table>
        RecordLinkedImagesExtension(status.images),  # Stores names of images linked in the documentation
        # Two extensions not installed through PyPI because they cause a downgrade of the Markdown package
        # (would be installed with packages `MarkdownSuperscript` and `MarkdownSubscript`)
        SubscriptExtension(),       # https://github.com/jambonrose/markdown_subscript_extension
        SuperscriptExtension()      # https://github.com/jambonrose/markdown_superscript_extension
    ]
    # TODO: Add an extension that inserts zero-width word breaks <wbr> after ::.
    extension_configs = {
        'codehilite': {
            'css_class': 'm-code',
            'wrapcode': False
        },
        'mdx_headdown': {
            'offset': 1
        }
    }
    md = markdown.Markdown(extensions=extensions, extension_configs=extension_configs, output_format="html5")

    math_svg_extension.latex2svg.load_cache(math_cache_file)

    for header in status.headers.values():
        if header['brief']:
            header['brief'] = remove_p_tag(md.reset().convert(header['brief']))
        if header['doc']:
            header['doc'] = md.reset().convert(header['doc'])
        process_sections(header, md)
    for group in status.groups.values():
        if group['brief']:
            group['brief'] = remove_p_tag(md.reset().convert(group['brief']))
        if group['doc']:
            group['doc'] = md.reset().convert(group['doc'])
        group['name'] = remove_p_tag(md.reset().convert(group['name']))
        process_sections(group, md)
    for member in status.members.values():
        if member['id'] not in status.id_map:
            continue
        if member['brief']:
            member['brief'] = remove_p_tag(md.reset().convert(member['brief']))
        if member['doc']:
            member['doc'] = md.reset().convert(member['doc'])
        process_sections(member, md)
    for page in status.pages.values():
        if page['doc']:
            page['doc'] = md.reset().convert(page['doc'])
        if page['title']:
            page['title'] = remove_p_tag(md.reset().convert(page['title']))
        process_sections(page, md)

    math_svg_extension.latex2svg.save_cache(math_cache_file)

def render_type(type, status: Status, doc_link_class):
    typename = html.escape(type['typename'])
    if typename:
        if type['id']:
            link = status.get_link(type['id'])
            if link:
                typename = '<a href="' + link + '" class="' + doc_link_class + '">' + typename + '</a>'
        if type['qualifiers']:
            if type['qualifiers'][0] == 'c':
                typename += ' '
            typename += html.escape(type['qualifiers'])
    return typename

def parse_types(status: Status, doc_link_class):
    for member in status.members.values():
        if 'page_id' not in member or not member['page_id']:
            continue  # Don't bother with non-documented members
        if 'type' in member and isinstance(member['type'], dict):
            member['type']['type'] = render_type(member['type'], status, doc_link_class)
        if 'return_type' in member and member['return_type']:
            member['return_type']['type'] = render_type(member['return_type'], status, doc_link_class)
        if 'arguments' in member:
            for arg in member['arguments']:
                arg['type'] = render_type(arg, status, doc_link_class)
        if 'template_parameters' in member:
            for arg in member['template_parameters']:
                if arg['type'] == 'type':
                    arg['type'] = 'typename'
                    if arg['default']:
                        arg['default'] = render_type(arg['default'], status, doc_link_class)
                else:  # isinstance(arg['type'], 'dict'):
                    arg['type'] = render_type(arg['type'], status, doc_link_class)
                arg['name'] = html.escape(arg['name'])  # just in case this is "<SFINAE>".

def add_wbr(text: str):
    # TODO: This function not yet used
    if '<' in text:  # Stuff contains HTML code, do not touch!
        return text
    if '::' in text:  # C++ names
        return text.replace('::', '::<wbr />')
    if '_' in text:  # VERY_LONG_UPPER_CASE macro names
        return text.replace('_', '_<wbr />')
    # These characters are quite common, so at least check that there is no
    # space (which may hint that the text is actually some human language):
    if '/' in text and not ' ' in text:  # URLs
        return text.replace('/', '/<wbr />')
    return text

def add_breadcrumb(compound, name, compounds):
    path_reverse = [compound['id']]
    parent = compound['parent']
    while parent:
        path_reverse.append(parent)
        parent = compounds[parent]['parent']
    compound['breadcrumb'] = []
    for elem in reversed(path_reverse):
        compound['breadcrumb'].append([compounds[elem][name], elem + '.html', strip_html_tags(compounds[elem][name])])

def fixup_namespace_compound_members(compound, status: Status):
    # Adjusts data in the members referenced in compound for display in the context of compound.
    # Some details about a member appear differently depending on which page we're looking at.
    # This function only changes member fields that are not primary, (i.e. original data is not lost):
    # - has_details: if a section with member details needs to be shown for this member
    # - include: either () to not show include file information, or ("name", link) to show it
    # - TODO: fixup referenced types in each member to be relative to compound if compound is a namespace
    def has_details(member):
        if member['page_id'] != compound['page_id']:
            return False
        if member['doc']:
            return True
        if is_class_like(member) and member['variables']:  # we only get here if it's 'simple'.
            return True
        if member['member_type'] == 'enum' and member['has_value_details']:
            return True
        return False

    if compound['member_type'] == 'file':
        # None of the members will show an include file
        compound_header = compound['id']
    else:
        compound_header = compound['header']
        if compound_header:
            compound['include'] = ('"' + status.headers[compound_header]['name'] + '"', compound_header + '.html')
        else:
            compound['include'] = ()
    for members in (compound['classes'], compound['enums'], compound['aliases'], compound['functions'],
                    compound['operators'], compound['variables'], compound['macros']):
        for member in members:
            if is_class_like(member) and not member['simple']:
                member['has_details'] = False
                continue
            member['has_details'] = has_details(member)
            if member['header'] == compound_header:
                member['include'] = ()
            else:
                member['include'] = ('"' + status.headers[member['header']]['name'] + '"', member['header'] + '.html')
                member['has_details'] = True
                if member['page_id'] == compound['id']:
                    compound['has_' + member['member_type'] + '_details'] = True

def fixup_class_compound_members(compound, status: Status):
    # This function works like `fixup_namespace_compound_members`, but is for when the compound is a class/struct/union.
    # - TODO: fixup referenced types in each member to be relative to compound
    def has_details(member):
        if is_class_like(member):
            if not member['simple']:
                return False
            if member['variables']:
                return True
        if member['doc']:
            return True
        if member['member_type'] == 'enum' and member['has_value_details']:
            return True
        return False

    compound['include'] = ('"' + status.headers[compound['header']]['name'] + '"', compound['header'] + '.html')
    for members in (compound['typeless_functions'], compound['classes'], compound['enums'], compound['aliases'],
                    compound['functions'], compound['operators'], compound['variables']):
        for member in members:
            member['has_details'] = has_details(member)
    for group in compound['groups']:
        for member in group['members']:
            member['has_details'] = has_details(member)
    for member in compound['related']:
        member['has_details'] = has_details(member)
        if member['header'] == compound['header']:
            member['include'] = ()
        else:
            member['include'] = ('"' + status.headers[member['header']]['name'] + '"', member['header'] + '.html')
            member['has_details'] = True
            compound['has_' + member['member_type'] + '_details'] = True

class EntryType(enum.Enum):
    # Order must match the search_type_map below; first value is reserved for
    # ResultFlag.ALIAS
    PAGE = 1
    NAMESPACE = 2
    MODULE = 3
    CLASS = 4
    STRUCT = 5
    UNION = 6
    ALIAS = 7
    FILE = 8
    FUNCTION = 9
    MACRO = 10
    ENUM = 11
    ENUM_VALUE = 12
    VARIABLE = 13

entry_type_map = {
    'page': EntryType.PAGE,
    'namespace': EntryType.NAMESPACE,
    'module': EntryType.MODULE,
    'class': EntryType.CLASS,
    'struct': EntryType.STRUCT,
    'union': EntryType.UNION,
    'alias': EntryType.ALIAS,
    'file': EntryType.FILE,
    'function': EntryType.FUNCTION,
    'macro': EntryType.MACRO,
    'enum': EntryType.ENUM,
    'enumvalue': EntryType.ENUM_VALUE,
    'variable': EntryType.VARIABLE
}

search_type_map = [
    # Order must match the EntryType above
    (CssClass.SUCCESS, 'page'),
    (CssClass.PRIMARY, 'namespace'),
    (CssClass.SUCCESS, 'module'),
    (CssClass.PRIMARY, 'class'),
    (CssClass.PRIMARY, 'struct'),
    (CssClass.PRIMARY, 'union'),
    (CssClass.PRIMARY, 'alias'),
    (CssClass.WARNING, 'file'),
    (CssClass.INFO, 'function'),
    (CssClass.INFO, 'macro'),
    (CssClass.PRIMARY, 'enum'),
    (CssClass.DEFAULT, 'enum value'),
    (CssClass.DEFAULT, 'variable')
]

snake_case_point ='_[^_]'
camel_case_point = '[^A-Z][A-Z].'
snake_case_point_re = re.compile(snake_case_point)
camel_case_point_re = re.compile(camel_case_point)
camel_or_snake_case_point_re = re.compile('({})|({})'.format(snake_case_point, camel_case_point))
word_point_re = re.compile('\W\w')

def add_entry_to_search_data(result, joiner: str, trie: Trie, map: ResultMap,
                             add_snake_case_suffixes, add_camel_case_suffixes):
    has_params = hasattr(result, 'params') and result.params is not None

    # Add entry as-is
    index = map.add(joiner.join(result.prefix + [result.name_with_args]),
                    result.url, suffix_length=result.suffix_length, flags=result.flags)

    # Add functions and function macros the second time with () appended, everything is the same
    # except for suffix length which is 2 chars shorter
    index_args = None
    if has_params:
        index_args = map.add(joiner.join(result.prefix + [result.name_with_args]), result.url,
                             suffix_length=result.suffix_length - 2, flags=result.flags)

    # Add the result multiple times with all possible prefixes
    prefixed_name = result.prefix + [result.name]
    for i in range(len(prefixed_name)):
        lookahead_barriers = []
        name = ''
        for j in prefixed_name[i:]:
            if name:
                lookahead_barriers += [len(name)]
                name += joiner
            name += j
        trie.insert(name.lower(), index, lookahead_barriers=lookahead_barriers)

        # Add functions and function macros the second time with () appended, referencing the other
        # result that expects () appended. The lookahead barrier is at the ( character to avoid the
        # result being shown twice.
        if has_params:
            trie.insert(name.lower() + '()', index_args, lookahead_barriers=lookahead_barriers + [len(name)])

    # Add the result multiple times again for all parts of the name
    if joiner == ' » ':
        prefix_end_re = word_point_re
    else:
        if add_camel_case_suffixes and add_snake_case_suffixes:
            prefix_end_re = camel_or_snake_case_point_re
        elif add_camel_case_suffixes:
            prefix_end_re = camel_case_point_re
        elif add_snake_case_suffixes:
            prefix_end_re = snake_case_point_re
        else:
            prefix_end_re = None
    if prefix_end_re:
        for m in prefix_end_re.finditer(result.name.lstrip('__')):
            name = result.name[m.start(0)+1:]
            trie.insert(name.lower(), index)
            if has_params:
                trie.insert(name.lower() + '()', index_args, lookahead_barriers=[len(name)])

    # Add keyword aliases for this symbol
    for search, title, suffix_length in result.keywords:
        if not title:
            title = search
        keyword_index = map.add(title, '', alias=index, suffix_length=result.suffix_length)
        trie.insert(search.lower(), keyword_index)

    return len(result.keywords) + 1

def fixup_title_for_search(title):
    title = title.replace('', '')
    title = strip_html_tags(title)
    if not title[-1].isalnum():
        title += '\u2800'  # This fixes an issue that arises because of the rtl text in the search box
    return title

def fixup_titles_for_search(list_of_titles):
    out = []
    for title in list_of_titles:
        out.append(fixup_title_for_search(title))
    return out

def build_search_data(status: Status, add_snake_case_suffixes, add_camel_case_suffixes):
    symbol_count = 0
    trie = Trie()
    map = ResultMap()
    for member in status.members.values():
        if not 'page_id' in member or not member['page_id']:  # Not documented, skip
            continue
        result = Empty()
        result.prefix = walktree.get_prefix(member['id'], status.members)
        result.name = member['name']
        result.flags = ResultFlag.from_type(ResultFlag.DEPRECATED if member['deprecated'] else ResultFlag(0),
                                            entry_type_map[member['member_type']])
        result.url = member['page_id'] + '.html'
        if member['page_id'] != member['id']:
            result.url += '#' + member['id']
        result.keywords = []  # TODO: dox++parse should output a keywords field for all members.

        # Handle function arguments
        result.name_with_args = result.name
        result.suffix_length = 0
        if 'arguments' in member and member['arguments']:
            # Some very heavily templated function parameters might cause the suffix_length to exceed 256,
            # which won't fit into the serialized search data. However that *also* won't fit in the search
            # result list so there's no point in storing so much. Truncate it to 48 chars which should fit
            # the full function name in the list in most cases, yet be still long enough to be able to
            # distinguish particular overloads.
            result.params = ', '.join([arg['typename'] +
                                       (' ' if arg['qualifiers'] and arg['qualifiers'][0] == 'c' else '') +
                                       arg['qualifiers'] for arg in member['arguments']])
            if len(result.params) > 49:
                result.params = result.params[:48] + '…'
            result.name_with_args += '(' + result.params + ')'
            result.suffix_length += len(result.params.encode('utf-8')) + 2
        if 'const' in member and member['const']:
            result.name_with_args += ' const'
            result.suffix_length += len(' const')

        # Add the symbol with all its different prefixes and suffixes and so on
        symbol_count += add_entry_to_search_data(result, '::', trie, map,
                                                 add_snake_case_suffixes, add_camel_case_suffixes)

    for file in status.headers.values():
        if not 'page_id' in file or not file['page_id']:  # Not documented, skip
            continue
        result = Empty()
        result.prefix = file['name'].split('/')
        result.name = result.prefix[-1]
        result.prefix = result.prefix[:-1]
        result.flags = ResultFlag.from_type(ResultFlag(0), entry_type_map['file'])
                        # ResultFlag.DEPRECATED if file['deprecated'] else ResultFlag(0)
        result.url = file['id'] + '.html'
        result.keywords = []  # TODO: dox++parse should output a keywords field for all members.
        result.name_with_args = result.name
        result.suffix_length = 0

        # Add the symbol with all its different prefixes and suffixes and so on
        symbol_count += add_entry_to_search_data(result, '/', trie, map,
                                                 add_snake_case_suffixes, add_camel_case_suffixes)

    for group in status.groups.values():
        if not 'page_id' in group or not group['page_id']:  # Not documented, skip
            continue
        result = Empty()
        result.prefix = walktree.get_prefix(group['id'], status.groups)
        result.name = fixup_title_for_search(group['name'])
        result.flags = ResultFlag.from_type(ResultFlag(0), entry_type_map['module'])
                        # ResultFlag.DEPRECATED if group['deprecated'] else ResultFlag(0)
        result.url = group['id'] + '.html'
        result.keywords = []  # TODO: dox++parse should output a keywords field for all members.
        result.name_with_args = result.name
        result.suffix_length = 0

        # Add the symbol with all its different prefixes and suffixes and so on
        symbol_count += add_entry_to_search_data(result, ' » ', trie, map,
                                                 add_snake_case_suffixes, add_camel_case_suffixes)

    for page in status.pages.values():
        result = Empty()
        result.prefix = fixup_titles_for_search(walktree.get_prefix(page['id'], status.pages, key='title'))
        result.name = fixup_title_for_search(page['title'])
        result.flags = ResultFlag.from_type(ResultFlag(0), entry_type_map['page'])
                        # ResultFlag.DEPRECATED if page['deprecated'] else ResultFlag(0)
        result.url = page['id'] + '.html'
        result.keywords = []  # TODO: dox++parse should output a keywords field for all members.
        result.name_with_args = result.name
        result.suffix_length = 0

        # Add the symbol with all its different prefixes and suffixes and so on
        symbol_count += add_entry_to_search_data(result, ' » ', trie, map,
                                                 add_snake_case_suffixes, add_camel_case_suffixes)

        # Now handle its sections
        def add_section_to_search(sections, result, url_base, prefix):
            symbol_count = 0
            for section in sections:
                result.prefix = prefix
                result.name = fixup_title_for_search(section[1])
                result.url = url_base + '#' + section[0]
                result.name_with_args = result.name
                symbol_count += add_entry_to_search_data(result, ' » ', trie, map,
                                                         add_snake_case_suffixes, add_camel_case_suffixes)
                if section[2]:
                    symbol_count += add_section_to_search(section[2], result, url_base, prefix + [result.name])
            return symbol_count
        url_base = result.url
        result.prefix.append(result.name)
        symbol_count += add_section_to_search(page['sections'], result, url_base, result.prefix)

    # For each node in the trie sort the results so the found items have sane order by default
    log.info("Indexed %d symbols for search data", symbol_count)
    trie.sort(map)
    return serialize_search_data(trie, map, search_type_map, symbol_count)

def createhtml(input_file, output_dir, options, template_params):
    """
    Generates HTML pages for the documentation in the JSON file `input_file`.

    :param input_file: the name of the JSON file that contains the documentation to format (string)
    :param output_dir: directory where the HTML files will be written (string)
    :param options: dictionary with options for how to process things
    :param template_params: dictionary with template parameters

    Options must contain the keys:
    - 'show_private_virtual': include private virtual members in the documentation
    - 'show_private_nonvirtual': include private members that are not virtual in the documentation
    - 'show_protected': include protected members in the documentation
    - 'show_undocumented': include undocumented members in the documentation
    - 'modify_include_statement': a Python function to rewrite the header ID of members
    - 'extra_files': list of extra files to copy to the output directory
    - 'templates': path to templates to use instead of default ones
    - 'source_files': list of source files (header files + markdown files), used to locate
                      image files referenced in documentation.
    - 'doc_link_class': CSS class to add to links to members (must match templates)
    - 'add_snake_case_suffixes': split up names according to snake case for searching
    - 'add_camel_case_suffixes': split up names according to camel case for searching
    - 'math_cache_file': file name for the cache for the mdx_math_svg markdown extension.
    """

    # Load data
    status = Status(walktree.load_data_from_json_file(input_file), options)

    # We need to have an index.html page
    if 'index' not in status.pages:
        page = members.new_page('index', template_params['PROJECT_NAME'], '')
        page['sections'] = []
        page['anchors'] = []
        status.pages['index'] = page

    # Generate fully qualified names
    generate_fully_qualified_names(status.data['members'], status)

    # Find out which pages to create, what is listed in each, and in which page
    # the detailed documentation for each member has to go
    log.info("Assigning members to pages")
    assign_page(status)
    #print('\n\nhtml_pages', status.html_pages)
    #print('\n\nid_map', status.id_map)

    # Parse all Markdown
    log.info("Parsing Markdown")
    parse_markdown(status, options['math_cache_file'])

    # Add group info to classes and namespaces
    log.info("Postprocessing information")
    for group in status.groups.values():
        module = (group['name'], group['id'] + '.html')
        for members in [group['namespaces'], group['classes']]:
            for member in members:
                member['module'] = module

    # Fix base and derived class lists, and related member lists
    process_base_derived_related_lists(status)

    # Convert type name strings into HTML links if appropriate
    parse_types(status, options['doc_link_class'])

    # Create tree structure for index pages
    log.info("Compiling indices")
    index = create_indices(status)

    # Navbar links
    if 'LINKS_NAVBAR1' in template_params:
        template_params['LINKS_NAVBAR1'] = process_navbar_links(template_params['LINKS_NAVBAR1'], status)
    if 'LINKS_NAVBAR2' in template_params:
        template_params['LINKS_NAVBAR2'] = process_navbar_links(template_params['LINKS_NAVBAR2'], status)

    # If no stylesheets were given, use the default one
    if not 'STYLESHEETS' in template_params or not template_params['STYLESHEETS']:
        template_params['STYLESHEETS'] = ['css/m-light-documentation.compiled.css']

    # Fill in default favicon if not given, and get type
    if not template_params['FAVICON']:
        template_params['FAVICON'] = 'html_templates/favicon-light.png'
    template_params['FAVICON'] = (template_params['FAVICON'], mimetypes.guess_type(template_params['FAVICON'])[0])

    # If custom template dir was supplied, use the default template directory as a fallback
    template_paths = [options['templates']]
    if options['templates'] != default_templates:
        template_paths.append(default_templates)
    env = jinja2.Environment(loader=jinja2.FileSystemLoader(template_paths),
                             trim_blocks=True, lstrip_blocks=True, enable_async=True)

    # Filter to return file basename or the full URL, if absolute
    def basename_or_url(path):
        if urllib.parse.urlparse(path).netloc:
            return path
        return os.path.basename(path)
    def rtrim(value):
        return value.rstrip()
    env.filters['rtrim'] = rtrim
    env.filters['basename_or_url'] = basename_or_url
    env.filters['urljoin'] = urllib.parse.urljoin

    # Generate the html for the members
    for id in status.html_pages:
        file = id + '.html'
        compound = status.html_pages[id]
        #if not compound:
        #    log.error("Generating 'compound' data structure for unknown id = %s", id)
        #    continue
        log.info("Generating page %s", file)
        type = compound['member_type']
        if type == 'file':
            compound['breadcrumb'] = [(p, '', p) for p in compound['name'].split('/')]  # TODO: Make sure this works on Windows
            fixup_namespace_compound_members(compound, status)
        elif type == 'module':
            add_breadcrumb(compound, 'name', status.groups)
            fixup_namespace_compound_members(compound, status)
        elif type == 'page':
            add_breadcrumb(compound, 'title', status.pages)
        else:
            add_breadcrumb(compound, 'name', status.members)
            if type == 'namespace':
                fixup_namespace_compound_members(compound, status)
            else:
                fixup_class_compound_members(compound, status)
        template = env.get_template(type + '.html')
        log.debug("Rendering %s to file %s using template %s",
                  compound['fully_qualified_name'] if 'fully_qualified_name' in compound else
                  compound['title'] if 'title' in compound else compound['name'],
                  file, template.filename)
        rendered = template.render(compound=compound,
                                   FILENAME=file,
                                   SEARCHDATA_FORMAT_VERSION=searchdata_format_version,
                                   **template_params)
        with open(os.path.join(output_dir, file), 'w') as f:
            f.write(rendered)

    # Generate indexes for pages, groups (==modules), namespaces, classes/structs/unions (==classes), and headers (==files)
    for file in ['pages.html', 'modules.html', 'namespaces.html', 'classes.html', 'files.html']:
        log.info("Generating page %s", file)
        template = env.get_template(file)
        log.debug("Rendering file %s using template %s", file, template)
        rendered = template.render(index=index,
                                   FILENAME=file,
                                   SEARCHDATA_FORMAT_VERSION=searchdata_format_version,
                                   **template_params)
        with open(os.path.join(output_dir, file), 'w') as f:
            f.write(rendered)

    # Generate search data
    if not template_params['SEARCH_DISABLED']:
        log.info("Compiling search data")
        data = build_search_data(status,
                                 add_snake_case_suffixes=options['add_snake_case_suffixes'],
                                 add_camel_case_suffixes=options['add_camel_case_suffixes'])

        if template_params['SEARCH_DOWNLOAD_BINARY']:
            log.info("Writing search data to %s", searchdata_filename)
            with open(os.path.join(output_dir, searchdata_filename), 'wb') as f:
                f.write(data)
        else:
            log.info("Writing search data to %s", searchdata_filename_b85)
            with open(os.path.join(output_dir, searchdata_filename_b85), 'wb') as f:
                f.write(base85encode_search_data(data))

        # OpenSearch metadata, if we have the base URL
        if template_params['SEARCH_BASE_URL']:
            log.info("writing OpenSearch metadata file")
            template = env.get_template('opensearch.xml')
            rendered = template.render(**template_params)
            output = os.path.join(output_dir, 'opensearch.xml')
            with open(output, 'w') as f:
                f.write(rendered)

    # Copy over all referenced files
    for i in template_params['STYLESHEETS'] + options['extra_files'] + ([template_params['PROJECT_LOGO']] if template_params['PROJECT_LOGO'] else []) + ([template_params['FAVICON'][0]] if template_params['FAVICON'][0] else []):
        if urllib.parse.urlparse(i).netloc:
            continue
        # File is either found relative to the current directory or relative to script directory
        p = i
        if not os.path.exists(p):
            p = os.path.join(doxpp_path, p)
        if not os.path.exists(p):
            log.error("File %s not found", i)
        log.info("Copying %s to output", p)
        shutil.copy(p, os.path.join(output_dir, os.path.basename(p)))
    # The images we need to search for in the input directories
    source_dirs = set()
    for s in options['source_files']:
        if ('*' in s) or ('?' in s):
            for s in glob.glob(s):
                source_dirs.add(os.path.dirname(s))
        else:
            source_dirs.add(os.path.dirname(s))
    for i in status.images:
        found = False
        for s in source_dirs:
            p = os.path.join(s,i)
            if os.path.exists(p):
                log.info("Copying %s to output", p)
                shutil.copy(p, os.path.join(output_dir, os.path.basename(i)))
                found = True
                break
        if not found:
            log.error("File %s not found", i)
    # The search.js is special, we encode the version information into its filename
    if not template_params['SEARCH_DISABLED']:
        p = os.path.join(doxpp_path, 'html_templates/search.js')
        log.info("Copying %s to output as %s", p, search_filename)
        shutil.copy(p, os.path.join(output_dir, search_filename))


import time
import sys
import os
picdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'pic')
libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib')
if os.path.exists(libdir):
    sys.path.append(libdir)

from selenium import webdriver
from pyvirtualdisplay import Display
from waveshare_epd import epd2in13_V2
from PIL import Image,ImageDraw,ImageFont

try:

    print("Initializing Display")
    epd = epd2in13_V2.EPD()
    epd.init(epd.FULL_UPDATE)
    epd.Clear(0xFF)

    refresh_image = Image.new('1', (epd.height, epd.width), 255)
    refresh_draw = ImageDraw.Draw(refresh_image)

    loading_image = Image.new('1', (epd.height, epd.width), 255)
    loading_draw = ImageDraw.Draw(loading_image)
    epd.displayPartBaseImage(epd.getbuffer(loading_image))
    epd.init(epd.PART_UPDATE)

    font8 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 8)
    font15 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 15)

    LOGINURL = "https://sunnyportal.com/Templates/Start.aspx"
    LOGOUTURL = "https://sunnyportal.com/Templates/Logout.aspx"

    loading_draw.text((0, 0), "Loading components...", font = font15, fill = 0)
    loading_draw.text((0, 20), "Please wait...", font = font15, fill = 0)
    loading_draw.text((0, 40), "This could take some time :)", font = font15, fill = 0)
    loading_image = loading_image.transpose(Image.ROTATE_180)
    epd.displayPartial(epd.getbuffer(loading_image))
    loading_image = loading_image.transpose(Image.ROTATE_180)

    print("Starting the Chromium Webdriver...")

    #Create a virtual display to 'show' the Chromium browser
    display = Display(visible=0, size=(800, 600))
    display.start()
    driver = webdriver.Chrome('/usr/bin/chromedriver')

    while True:

        print("Navigating to Login Page...")
        driver.get(LOGINURL)
        email_element = driver.find_element_by_id("txtUserName")
        password_element = driver.find_element_by_id("txtPassword")
        checkbox_element = driver.find_element_by_id("ctl00_ContentPlaceHolder1_Logincontrol1_MemorizePassword")
        login_element = driver.find_element_by_id("ctl00_ContentPlaceHolder1_Logincontrol1_LoginBtn")

        print("Entering Login Credentials...")
        email_element.send_keys("youremail@address.com")
        password_element.send_keys("yourpassword")
        checkbox_element.click()

        print("Navigating to Dashboard...")
        login_element.click()

        refreshcounter = 0
        relogincounter = 0
        keepActive = True

        epd.init(epd.FULL_UPDATE)
        epd.display(epd.getbuffer(refresh_image))
        epd.init(epd.PART_UPDATE)

        time_image = Image.new('1', (epd.height, epd.width), 255)
        time_draw = ImageDraw.Draw(time_image)
        epd.displayPartBaseImage(epd.getbuffer(time_image))
        epd.init(epd.PART_UPDATE)
        bmpleaf = Image.open(os.path.join(picdir, 'leaf_small.bmp'))
        bmpsun = Image.open(os.path.join(picdir, 'sun_small.bmp'))
        bmpenergy = Image.open(os.path.join(picdir, 'energy_small.bmp'))
        time_image.paste(bmpleaf, (167, 41))
        time_image.paste(bmpsun, (0, 41))
        time_image.paste(bmpenergy, (84, 41))

        print("Loading Dashboard...")
        while keepActive:
            MainValues = driver.find_elements_by_class_name("mainValueAmount")
            MainValueUnits = driver.find_elements_by_class_name("mainValueUnit")
            Timestamps = driver.find_elements_by_class_name("widgetSubHead")
            MainValueDescriptions = driver.find_elements_by_class_name("mainValueDescription")

            TotalPVEnergy = driver.find_element_by_id('ctl00_ContentPlaceHolder1_UserControlShowDashboard1_energyYieldWidget_energyYieldTotalValue').text
            TotalPVEnergyUnit = driver.find_element_by_id('ctl00_ContentPlaceHolder1_UserControlShowDashboard1_energyYieldWidget_energyYieldTotalUnit').text
            TotalCO2 = driver.find_element_by_id('ctl00_ContentPlaceHolder1_UserControlShowDashboard1_carbonWidget_carbonReductionTotalValue').text
            TotalCO2Unit = driver.find_element_by_id('ctl00_ContentPlaceHolder1_UserControlShowDashboard1_carbonWidget_carbonReductionTotalUnit').text

            CurrentPVPowerValue = MainValues[0].text
            CurrentPVEnergyValue = MainValues[1].text
            CurrentCO2Avoided = MainValues[2].text

            CurrentPVPowerValueUnit = MainValueUnits[0].text
            CurrentPVEnergyValueUnit = MainValueUnits[1].text
            CurrentCO2AvoidedUnit = MainValueUnits[2].text

            CurrentPVEnergyDescription = MainValueDescriptions[0].text
            CurrentCO2Description = MainValueDescriptions[1].text

            CurrentTime = Timestamps[0].get_attribute("title")
            commasplit = CurrentTime.split(',')
            day = commasplit[0]
            date = (commasplit[1])[:-5]
            hour = commasplit[2]
            CurrentTimeFormatted = day+','+date+','+hour
            print ("Last updated: " + CurrentTimeFormatted)
            print("Current PV Power: " + CurrentPVPowerValue + CurrentPVPowerValueUnit)
            print("PV Energy: " + CurrentPVEnergyValue + CurrentPVEnergyValueUnit + " " + CurrentPVEnergyDescription + " (Total: " + TotalPVEnergy + TotalPVEnergyUnit + ")")
            print("CO2 Avoided: " + CurrentCO2Avoided + CurrentCO2AvoidedUnit + " " + CurrentCO2Description + " (Total: " + TotalCO2 + TotalCO2Unit + ")")
            print()

            if (CurrentPVEnergyDescription != 'Today'):
                CurrentPVEnergyDescription = 'Today'
                CurrentPVEnergyValue = '0'
            if (CurrentCO2Description != 'Today'):
                CurrentCO2Description = 'Today'
                CurrentCO2Avoided = '0'

            time_draw = ImageDraw.Draw(time_image)

            time_draw.rectangle([(0,0),(250,20)], fill = 255)
            time_draw.text((0, 0), CurrentTimeFormatted, font = font15, fill = 0)

            time_draw.rectangle([(23,42),(84,60)], fill = 255)
            time_draw.text((23, 42), CurrentPVPowerValue + CurrentPVPowerValueUnit, font = font15, fill = 0)
            time_draw.rectangle([(106,42),(167,60)], fill = 255)
            time_draw.text((106, 42), CurrentPVEnergyValue + CurrentPVEnergyValueUnit, font = font15, fill = 0)
            time_draw.rectangle([(189,42),(250,60)], fill = 255)
            time_draw.text((189, 42), CurrentCO2Avoided + CurrentCO2AvoidedUnit, font = font15, fill = 0)

            time_draw.text((24, 60), "Current", font = font8, fill = 0)
            time_draw.text((108, 60), CurrentPVEnergyDescription, font = font8, fill = 0)
            time_draw.text((192, 60), CurrentCO2Description, font = font8, fill = 0)

            time_draw.rectangle([(110,80),(250,100)], fill = 255)
            time_draw.text((0, 80), "Total generated: " + TotalPVEnergy + TotalPVEnergyUnit, font = font15, fill = 0)
            time_draw.rectangle([(112,100),(250,120)], fill = 255)
            time_draw.text((0, 100), "Total CO2 saved: " + TotalCO2 + TotalCO2Unit, font =  font15, fill = 0)

            time_image = time_image.transpose(Image.ROTATE_180)
            epd.displayPartial(epd.getbuffer(time_image))
            time_image = time_image.transpose(Image.ROTATE_180)

            #Updating the page every 10 seconds.
            time.sleep(10)
            refreshcounter += 1
            relogincounter += 1

            #10 seconds x 360 loops = Re-login after 30 minutes.
            if (relogincounter == 180):
                print("Logoff")
                driver.get(LOGOUTURL)
                relogincounter = 0
                refreshcounter = 0
                keepActive = False
            #10 seconds x 60 loops = Refreshing the page after 10 minutes.
            if (refreshcounter == 60):
                print("Refreshing Page")
                driver.refresh()
                refreshcounter = 0

except KeyboardInterrupt:
    print("Keyboard Interrupt. Clearing Screen...")
    epd.init(epd.FULL_UPDATE)
    epd.Clear(0xFF)
    epd.sleep()
    epd.Dev_exit()
    os.system('pkill Xvfb')
    os.system('pkill chromedriver')
    os.system('pkill chromium-browse')
    exit()


from django.shortcuts import render
from django.http import HttpResponseRedirect, Http404
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required

from .models import ParaSet
from .forms import ParaSetForm

# Create your views here.
def index(request):
    """The home page for crm_input_app"""
    return render (request, 'crm_input_app/index.html')

@login_required
def parasList(request):
    """Show all parameters sets"""
    paraslist=ParaSet.objects.filter(owner=request.user).order_by('id')
    context={'paraslist':paraslist}
    return render(request, 'crm_input_app/parasList.html',context)

@login_required
def parasDetail(request, paras_id):
    """show parameter set details"""
    paras=ParaSet.objects.get(id=paras_id)
    #make sure the parameter set belongs to the current user.

    if paras.owner!= request.user:
        raise Http404
    context={'paras':paras}
    return render (request, 'crm_input_app/parasDetail.html',context)

@login_required
def new_paras(request):
    """Add new parameter set"""
    if request.method!='POST':
        #No data submitted;create a a blank form
        form=ParaSetForm()
    else:
        #POST data submitted; process data
        form=ParaSetForm(request.POST)
        if form.is_valid():
            new_data=form.save(commit=False)
            new_data.owner=request.user
            new_data.save()
            return HttpResponseRedirect(reverse ('crm_input_app:parasDetail',args=[new_data.id]))

    context= {'form':form}
    return render(request, 'crm_input_app/new_paras.html', context)

@login_required
def getResult(request, paras_id):
    """show parameter set details"""
    paras=ParaSet.objects.get(id=paras_id)
    #make sure the parameter set belongs to the current user.

    if paras.owner!= request.user:
        raise Http404

    context={'paras':paras}
    return render (request, 'crm_input_app/result.html',context)

@login_required
def edit_paras(request, paras_id):
    """Edit an existing parameterset"""

    paras=ParaSet.objects.get(id=paras_id)

    #make sure the parameter set belongs to the current user.

    if paras.owner!= request.user:
        raise Http404

    if request.method!='POST':
        #initial request; pre-fill form with the current parameter set.
        form = ParaSetForm(instance=paras)
    else:
        #Post data submitted
        form=ParaSetForm(instance = paras, data=request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse ('crm_input_app:parasDetail',args=[paras.id]))

    context={'paras':paras,'form':form}
    return render(request,'crm_input_app/edit_paras.html', context)


import re

def parse_instruction(instruction_string):
    m = re.match(r'^(?P<operation>[a-z]{3}) (?P<argument>[0-9\-+]+)(\n)?$', instruction_string)
    operation = m.group('operation')
    argument = int(m.group('argument'))
    return operation, argument

def get_new_instructions(instructions, modified_instruction_index, old_operation, new_operation):
    new_instructions = instructions.copy()
    new_instructions[modified_instruction_index] = \
        new_instructions[modified_instruction_index].replace(old_operation, new_operation)
    return new_instructions

def execute_instruction(operation, argument, instruction_index, accumulator):
    if operation == 'acc':
        accumulator += argument
        instruction_index += 1
    elif operation == 'jmp':
        instruction_index += argument
    elif operation == 'nop':
        instruction_index += 1
    else:
        raise Exception('Invalid operation: {}'.format(operation))

    return instruction_index, accumulator

def test_modification(instructions, modified_instruction_index):
    operation, argument = parse_instruction(instructions[modified_instruction_index])
    new_operation = 'nop' if operation == 'jmp' else 'jmp' if operation == 'nop' else 'acc'
    new_instructions = get_new_instructions(instructions, modified_instruction_index, operation, new_operation)
    try:
        accumulator = run_instructions_until_loop_reached(new_instructions)
    except Exception:
        pass
    else:
        print('Program terminated normally: accumulator = {}'.format(accumulator))

def run_instructions_until_loop_reached(instructions):
    accumulator = 0
    index = 0
    executed_instruction_set = set()

    while index < len(instructions):
        if index in executed_instruction_set:
            raise Exception('Program looped: accumulator = {}'.format(accumulator))
        instruction = instructions[index]
        operation, argument = parse_instruction(instruction)
        executed_instruction_set.add(index)
        index, accumulator = execute_instruction(operation, argument, index, accumulator)

    return accumulator

with open('input.txt', 'r') as f:
    instructions = f.readlines()

# Part 1
accumulator = run_instructions_until_loop_reached(instructions)

# Part 2
for index in range(0, len(instructions)):
    test_modification(instructions, index)


from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QCompleter
from PyQt5.QtCore import QAbstractTableModel, Qt
from tinydb import TinyDB, Query
from PyQt5.QtCore import Qt
import os
import pandas as pd
import math

class pandasModel(QAbstractTableModel):

    def __init__(self, data):
        QAbstractTableModel.__init__(self)
        self._data = data

    def rowCount(self, parent=None):
        return self._data.shape[0]

    def columnCount(self, parnet=None):
        return self._data.shape[1]

    def data(self, index, role=Qt.DisplayRole):
        if index.isValid():
            if role == Qt.DisplayRole:
                return str(self._data.iloc[index.row(), index.column()])
        return None

    def headerData(self, col, orientation, role):
        try:
            if orientation == Qt.Horizontal and role == Qt.DisplayRole:

                return self._data.columns[col]
        except:
            print("nodata")
        return None

class Ui_MainWindow(object):

    def __init__(self):
        super().__init__()

    def setupUi(self, MainWindow):
        #t = read_csv('trucks.csv', names=['Truck_No'])
        truck_names = ["Trucks"]
        tn = pd.read_csv("trucks.csv", names=truck_names)
        self.t1 = tn.Trucks.to_list()
        # print(self.t1)
        column_names = ["City"]
        cn = pd.read_csv("city_list.csv", names=column_names)
        cities = cn.City.to_list()

        self.completer = QCompleter(cities)
        self.completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)

        MainWindow.setObjectName("MainWindow")
        MainWindow.showMaximized()
        font = QtGui.QFont()
        font.setPointSize(24)
        font.setBold(True)
        font.setWeight(75)
        MainWindow.setFont(font)
        MainWindow.setTabShape(QtWidgets.QTabWidget.Rounded)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        #self.centralwidget.setStyleSheet("background-color: yellow;")

        self.Year = QtWidgets.QComboBox(self.centralwidget)
        self.Year.setEnabled(True)
        self.Year.setGeometry(QtCore.QRect(30, 20, 121, 51))
        font = QtGui.QFont()
        font.setPointSize(26)
        font.setBold(True)
        font.setWeight(75)

        self.Year.setFont(font)
        self.Year.setAutoFillBackground(False)
        self.Year.setEditable(False)
        self.Year.setObjectName("Year")
        self.Year.addItem("")
        self.Year.addItem("")
        self.Year.addItem("")

        self.month = QtWidgets.QComboBox(self.centralwidget)
        self.month.setGeometry(QtCore.QRect(180, 20, 151, 51))
        font = QtGui.QFont()
        font.setPointSize(24)
        self.month.setFont(font)
        self.month.setLayoutDirection(QtCore.Qt.LeftToRight)
        self.month.setAutoFillBackground(False)
        self.month.setObjectName("month")
        self.month.addItem("")
        self.month.addItem("")
        self.month.addItem("")
        self.month.addItem("")
        self.month.addItem("")
        self.month.addItem("")
        self.month.addItem("")
        self.month.addItem("")
        self.month.addItem("")
        self.month.addItem("")
        self.month.addItem("")
        self.month.addItem("")
        self.truck = QtWidgets.QComboBox(self.centralwidget)
        self.truck.setGeometry(QtCore.QRect(370, 20, 131, 51))
        self.truck.setObjectName("truck")
        self.d_click = QtWidgets.QTabWidget(self.centralwidget)
        self.d_click.setGeometry(QtCore.QRect(10, 90, 1321, 481))
        self.d_click.setObjectName("d_click")
        self.tab_3 = QtWidgets.QWidget()
        self.tab_3.setObjectName("tab_3")
        self.load_name = QtWidgets.QComboBox(self.tab_3)
        self.load_name.setGeometry(QtCore.QRect(0, 10, 131, 51))
        font = QtGui.QFont()
        font.setPointSize(14)
        self.load_name.setFont(font)
        self.load_name.setMaxVisibleItems(6)
        self.load_name.setObjectName("load_name")
        self.load_name.addItem("")
        self.load_name.addItem("")
        self.load_name.addItem("")
        self.load_name.addItem("")
        self.load_name.addItem("")
        self.load_name.addItem("")
        self.load_name.addItem("")
        self.load_name.addItem("")
        self.load_name.addItem("")
        self.load_name.addItem("")
        self.load_name.addItem("")
        self.load_name.addItem("")
        self.load_name.addItem("")
        self.load_name.addItem("")
        self.load_name.addItem("")
        self.load_name.addItem("")

        self.dateEdit = QtWidgets.QLineEdit(self.tab_3)
        self.dateEdit.setGeometry(QtCore.QRect(150, 10, 131, 47))
        font = QtGui.QFont()
        font.setPointSize(14)
        font.setBold(True)
        font.setWeight(75)
        self.dateEdit.setFont(font)
        self.dateEdit.setObjectName("dateEdit")

        self.frm = QtWidgets.QLineEdit(self.tab_3)
        self.frm.setCompleter(self.completer)

        self.frm.setGeometry(QtCore.QRect(300, 10, 131, 47))
        font = QtGui.QFont()
        font.setPointSize(14)
        font.setBold(True)
        font.setWeight(75)
        self.frm.setFont(font)
        self.frm.setObjectName("frm")

        self.to = QtWidgets.QLineEdit(self.tab_3)
        self.to.setCompleter(self.completer)
        self.to.setGeometry(QtCore.QRect(450, 10, 131, 47))
        font = QtGui.QFont()
        font.setPointSize(14)
        font.setBold(True)
        font.setWeight(75)
        self.to.setFont(font)
        self.to.setObjectName("to")

        self.KM = QtWidgets.QLineEdit(self.tab_3)
        self.KM.setGeometry(QtCore.QRect(600, 10, 118, 47))
        font = QtGui.QFont()
        font.setPointSize(14)
        font.setBold(True)
        font.setWeight(75)
        self.KM.setFont(font)
        self.KM.setObjectName("KM")

        self.arrival = QtWidgets.QLineEdit(self.tab_3)
        self.arrival.setGeometry(QtCore.QRect(740, 10, 118, 47))
        font = QtGui.QFont()
        font.setPointSize(14)
        font.setBold(True)
        font.setWeight(75)
        self.arrival.setFont(font)
        self.arrival.setObjectName("arrival")

        self.unload = QtWidgets.QLineEdit(self.tab_3)
        self.unload.setGeometry(QtCore.QRect(890, 10, 118, 47))
        font = QtGui.QFont()
        font.setPointSize(14)
        font.setBold(True)
        font.setWeight(75)
        self.unload.setFont(font)
        self.unload.setObjectName("unload")

        self.pauch = QtWidgets.QComboBox(self.tab_3)
        self.pauch.setGeometry(QtCore.QRect(1030, 10, 118, 47))
        font = QtGui.QFont()
        font.setPointSize(14)
        font.setBold(True)
        font.setWeight(75)
        self.pauch.setFont(font)
        self.pauch.setObjectName("pauch")
        self.pauch.addItem("")
        self.pauch.addItem("")

        self.pauch.addItem("")

        self.enter = QtWidgets.QPushButton(self.tab_3)
        self.enter.setGeometry(QtCore.QRect(1170, 10, 91, 51))
        font = QtGui.QFont()
        font.setPointSize(16)
        font.setBold(True)
        font.setWeight(75)
        self.enter.setFont(font)
        self.enter.setObjectName("enter")

        self.tableView = QtWidgets.QTableView(self.tab_3)
        self.tableView.setGeometry(QtCore.QRect(10, 90, 1291, 331))
        font = QtGui.QFont()
        font.setPointSize(10)
        font.setBold(True)
        font.setWeight(75)
        self.tableView.setFont(font)
        self.tableView.setObjectName("tableView")

        self.d_click.addTab(self.tab_3, "")
        self.tab = QtWidgets.QWidget()
        self.tab.setObjectName("tab")

        self.a_pay = QtWidgets.QLineEdit(self.tab)
        self.a_pay.setGeometry(QtCore.QRect(300, 10, 111, 41))
        font = QtGui.QFont()
        font.setPointSize(14)
        self.a_pay.setFont(font)
        self.a_pay.setObjectName("a_pay")

        self.a_click = QtWidgets.QPushButton(self.tab)
        self.a_click.setGeometry(QtCore.QRect(440, 10, 81, 41))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.a_click.setFont(font)
        self.a_click.setObjectName("a_click")

        self.a_date = QtWidgets.QLineEdit(self.tab)
        self.a_date.setGeometry(QtCore.QRect(20, 10, 121, 41))
        font = QtGui.QFont()
        font.setPointSize(14)
        self.a_date.setFont(font)
        self.a_date.setObjectName("a_date")

        self.a_place = QtWidgets.QLineEdit(self.tab)
        self.a_place.setCompleter(self.completer)
        self.a_place.setGeometry(QtCore.QRect(160, 10, 111, 41))
        font = QtGui.QFont()
        font.setPointSize(14)
        self.a_place.setFont(font)
        self.a_place.setObjectName("a_place")

        self.tableView_5 = QtWidgets.QTableView(self.tab)
        self.tableView_5.setGeometry(QtCore.QRect(700, 60, 591, 311))
        font = QtGui.QFont()
        font.setPointSize(12)
        font.setBold(True)
        font.setWeight(75)
        self.tableView_5.setFont(font)
        self.tableView_5.setObjectName("tableView_5")

        self.tableView_3 = QtWidgets.QTableView(self.tab)
        self.tableView_3.setGeometry(QtCore.QRect(20, 60, 591, 311))
        font = QtGui.QFont()
        font.setPointSize(12)
        font.setBold(True)
        font.setWeight(75)
        self.tableView_3.setFont(font)
        self.tableView_3.setObjectName("tableView_3")

        self.d_click.addTab(self.tab, "")
        self.tab_2 = QtWidgets.QWidget()
        self.tab_2.setObjectName("tab_2")

        self.d_date = QtWidgets.QLineEdit(self.tab_2)
        self.d_date.setGeometry(QtCore.QRect(20, 9, 121, 41))
        font = QtGui.QFont()
        font.setPointSize(14)
        self.d_date.setFont(font)
        self.d_date.setObjectName("d_date")

        self.d_pump = QtWidgets.QLineEdit(self.tab_2)
        self.d_pump.setCompleter(self.completer)
        self.d_pump.setGeometry(QtCore.QRect(160, 9, 111, 41))
        font = QtGui.QFont()
        font.setPointSize(14)
        self.d_pump.setFont(font)
        self.d_pump.setObjectName("d_pump")

        self.d_quant = QtWidgets.QLineEdit(self.tab_2)
        self.d_quant.setGeometry(QtCore.QRect(300, 9, 111, 41))
        font = QtGui.QFont()
        font.setPointSize(14)
        self.d_quant.setFont(font)
        self.d_quant.setObjectName("d_quant")

        self.d_pay = QtWidgets.QLineEdit(self.tab_2)
        self.d_pay.setGeometry(QtCore.QRect(440, 10, 111, 41))
        font = QtGui.QFont()
        font.setPointSize(14)

        self.d_pay.setFont(font)
        self.d_pay.setObjectName("d_pay")

        self.pushButton = QtWidgets.QPushButton(self.tab_2)
        self.pushButton.setGeometry(QtCore.QRect(580, 10, 81, 41))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.pushButton.setFont(font)
        self.pushButton.setObjectName("pushButton")

        self.tableView_2 = QtWidgets.QTableView(self.tab_2)
        self.tableView_2.setGeometry(QtCore.QRect(20, 60, 591, 311))
        font = QtGui.QFont()
        font.setPointSize(12)
        font.setBold(True)
        font.setWeight(75)
        self.tableView_2.setFont(font)
        self.tableView_2.setObjectName("tableView_2")

        self.tableView_4 = QtWidgets.QTableView(self.tab_2)
        self.tableView_4.setGeometry(QtCore.QRect(700, 60, 591, 311))
        font = QtGui.QFont()
        font.setPointSize(12)
        font.setBold(True)
        font.setWeight(75)
        self.tableView_4.setFont(font)
        self.tableView_4.setObjectName("tableView_4")

        self.d_click.addTab(self.tab_2, "")
        self.tab_4 = QtWidgets.QWidget()
        self.tab_4.setObjectName("tab_4")

        self.d_click.addTab(self.tab_4, "")
        self.tab_5 = QtWidgets.QWidget()
        self.tab_5.setObjectName("tab_5")

        self.truck_2 = QtWidgets.QComboBox(self.tab_5)
        self.truck_2.setGeometry(QtCore.QRect(20, 10, 131, 51))
        font = QtGui.QFont()
        font.setPointSize(16)
        self.truck_2.setFont(font)
        self.truck_2.setObjectName("truck_2")

        self.label = QtWidgets.QLabel(self.tab)
        self.label.setGeometry(QtCore.QRect(250, 380, 231, 51))
        self.label.setObjectName("label")

        self.label_2 = QtWidgets.QLabel(self.tab)
        self.label_2.setGeometry(QtCore.QRect(960, 380, 231, 51))
        self.label_2.setObjectName("label_2")

        self.label_3 = QtWidgets.QLabel(self.tab_2)
        self.label_3.setGeometry(QtCore.QRect(270, 370, 231, 51))
        self.label_3.setObjectName("label_3")

        self.label_4 = QtWidgets.QLabel(self.tab_2)
        self.label_4.setGeometry(QtCore.QRect(980, 370, 231, 51))
        self.label_4.setObjectName("label_4")

        self.label_6 = QtWidgets.QLabel(self.centralwidget)
        self.label_6.setGeometry(QtCore.QRect(1070, 10, 291, 91))
        font = QtGui.QFont()
        font.setFamily("Segoe Script")
        font.setPointSize(28)
        font.setBold(False)
        font.setUnderline(True)
        font.setWeight(50)
        self.label_6.setFont(font)
        self.label_6.setObjectName("label_6")

        self.d_click.addTab(self.tab_5, "")

        MainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        self.month.setCurrentIndex(0)
        self.d_click.setCurrentIndex(0)
        self.pauch.setCurrentIndex(-1)
        self.load_name.setCurrentIndex(-1)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

        # calls
        self.Year.currentIndexChanged.connect(self.openClicked)
        self.Year.currentIndexChanged.connect(self.readItems)
        self.truck.currentIndexChanged.connect(self.openClicked)
        self.truck.currentIndexChanged.connect(self.readItems)
        self.month.currentIndexChanged.connect(self.openClicked)
        self.month.currentIndexChanged.connect(self.readItems)
        self.enter.clicked.connect(self.enterClicked)
        self.load_name.currentIndexChanged.connect(self.readItems)
        self.a_click.clicked.connect(self.advance_click)
        self.pushButton.clicked.connect(self.diesel_click)

    def openClicked(self):
        y = self.Year.currentText()
        m = self.month.currentText()
        t = self.truck.currentText()

        db = TinyDB(y+m+t+'.json')
        j = db.table("j_trip")
        a = db.table("advance")
        d = db.table("diesel")

        df = pd.DataFrame.from_dict(j.all())

        self.model = pandasModel(df)
        self.tableView.setModel(self.model)
        try:
            af = pd.DataFrame.from_dict(a.all())
            af["amount"] = pd.to_numeric(af["amount"], downcast="float")

            af.at['Total', 'amount'] = af['amount'].sum()
            af = af.fillna("")
            self.model = pandasModel(af)
            self.tableView_3.setModel(self.model)
            self.tableView_2.setModel(self.model)

            dif = pd.DataFrame.from_dict(d.all())
            dif["rate"] = pd.to_numeric(dif["rate"], downcast="float")
            dif["quantity"] = pd.to_numeric(dif["quantity"], downcast="float")

            dif["amount"] = dif["quantity"] * dif["rate"]
            print(dif)
            dif.at['Total', 'amount'] = dif['amount'].sum()
            dif = dif.fillna("")
            self.model = pandasModel(dif)
            self.tableView_5.setModel(self.model)
            self.tableView_4.setModel(self.model)
        except:
            af = pd.DataFrame.from_dict(a.all())

            self.model = pandasModel(af)
            self.tableView_3.setModel(self.model)
            self.tableView_2.setModel(self.model)

            dif = pd.DataFrame.from_dict(d.all())

            self.model = pandasModel(dif)
            self.tableView_5.setModel(self.model)
            self.tableView_4.setModel(self.model)

    def readItems(self):

        y = self.Year.currentText()
        m = self.month.currentText()
        t = self.truck.currentText()

        db = TinyDB(y+m+t+'.json')
        j = db.table("j_trip")
        a = db.table("advance")
        d = db.table("diesel")

        q = Query()
        if(j.contains(q.load_name == self.load_name.currentText())):
            print("already exist")
            x = j.search(q.load_name == self.load_name.currentText())
            print(x[0]['load_name'])

            self.dateEdit.setText(x[0]["load_date"])
            self.frm.setText(x[0]["frm"])
            self.to.setText(x[0]["too"])
            self.KM.setText(x[0]["km"])
            self.arrival.setText(x[0]["arrival"])
            self.unload.setText(x[0]["unloading"])

            try:
                self.pauch.setCurrentText(x[0]["pauch"])
            except:
                self.pauch.setCurrentText("Pending")

        else:
            print("x")
            self.dateEdit.setText("")
            self.frm.setText("")
            self.to.setText("")
            self.KM.setText("")
            self.arrival.setText("")
            self.unload.setText("")

            try:
                self.pauch.setCurrentText("")
            except:
                # self.dateEdit.setText(j.search(q.name == 'John'))
                self.pauch.setCurrentText("")

        df = pd.DataFrame.from_dict(j.all())

        self.model = pandasModel(df)
        self.tableView.setModel(self.model)

        try:
            af = pd.DataFrame.from_dict(a.all())
            af["amount"] = pd.to_numeric(af["amount"], downcast="float")

            af.at['Total', 'amount'] = af['amount'].sum()
            af = af.fillna("")
            self.model = pandasModel(af)
            self.tableView_3.setModel(self.model)
            self.tableView_2.setModel(self.model)

            dif = pd.DataFrame.from_dict(d.all())
            dif["rate"] = pd.to_numeric(dif["rate"], downcast="float")
            dif["quantity"] = pd.to_numeric(dif["quantity"], downcast="float")

            dif["amount"] = dif["quantity"] * dif["rate"]
            print(dif)
            dif.at['Total', 'amount'] = dif['amount'].sum()
            dif = dif.fillna("")
            self.model = pandasModel(dif)
            self.tableView_5.setModel(self.model)
            self.tableView_4.setModel(self.model)
        except:
            af = pd.DataFrame.from_dict(a.all())

            self.model = pandasModel(af)
            self.tableView_3.setModel(self.model)
            self.tableView_2.setModel(self.model)

            dif = pd.DataFrame.from_dict(d.all())

            self.model = pandasModel(dif)
            self.tableView_5.setModel(self.model)
            self.tableView_4.setModel(self.model)

    def enterClicked(self):

        y = self.Year.currentText()
        m = self.month.currentText()
        t = self.truck.currentText()

        db = TinyDB(y+m+t+'.json')
        j = db.table("j_trip")
        a = db.table("advance")
        d = db.table("diesel")

        q = Query()

        if(j.contains(q.load_name == self.load_name.currentText())):
            print("already exist")
            j.upsert({"load_date": self.dateEdit.text(), "frm": self.frm.text(), "too": self.to.text(), "km": self.KM.text(), "arrival": self.arrival.text(
            ), "unloading": self.unload.text(), "pauch": self.pauch.currentText()}, q.load_name == self.load_name.currentText())

        else:
            j.insert({"load_name": self.load_name.currentText(), "load_date": self.dateEdit.text(), "frm": self.frm.text(), "too": self.to.text(
            ), "km": self.KM.text(), "arrival": self.arrival.text(), "unloading": self.unload.text(), "pauch": self.pauch.currentText()})

        #write data to cloud storage

        #upload data when internet is connected

        df = pd.DataFrame.from_dict(j.all())

        self.model = pandasModel(df)
        self.tableView.setModel(self.model)

    def advance_click(self):

        y = self.Year.currentText()
        m = self.month.currentText()
        t = self.truck.currentText()

        db = TinyDB(y+m+t+'.json')
        j = db.table("j_trip")
        a = db.table("advance")
        d = db.table("diesel")

        q = Query()

        a.insert({"date": self.a_date.text(),
                  "place": self.a_place.text(), "amount": self.a_pay.text()})

        af = pd.DataFrame.from_dict(a.all())
        af["amount"] = pd.to_numeric(af["amount"], downcast="float")

        af.at['Total', 'amount'] = af['amount'].sum()
        af = af.fillna("")
        self.model = pandasModel(af)
        self.tableView_3.setModel(self.model)
        self.tableView_2.setModel(self.model)

    def diesel_click(self):

        y = self.Year.currentText()
        m = self.month.currentText()
        t = self.truck.currentText()

        db = TinyDB(y+m+t+'.json')
        j = db.table("j_trip")
        a = db.table("advance")
        d = db.table("diesel")

        q = Query()

        d.insert({"date": self.d_date.text(), "pump": self.d_pump.text(
        ), "quantity": self.d_quant.text(), "rate": self.d_pay.text()})

        dif = pd.DataFrame.from_dict(d.all())

        dif["rate"] = pd.to_numeric(dif["rate"], downcast="float")
        dif["quantity"] = pd.to_numeric(dif["quantity"], downcast="float")

        dif["amount"] = dif["quantity"] * dif["rate"]
        print(dif)
        dif.at['Total', 'amount'] = dif['amount'].sum()
        dif = dif.fillna("")

        self.model = pandasModel(dif)
        self.tableView_5.setModel(self.model)
        self.tableView_4.setModel(self.model)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.Year.setCurrentText(_translate("MainWindow", "2020"))
        self.Year.setItemText(0, _translate("MainWindow", "2020"))
        self.Year.setItemText(1, _translate("MainWindow", "2021"))
        self.Year.setItemText(2, _translate("MainWindow", "2022"))
        self.month.setItemText(0, _translate("MainWindow", "Jan"))
        self.month.setItemText(1, _translate("MainWindow", "Feb"))
        self.month.setItemText(2, _translate("MainWindow", "March"))
        self.month.setItemText(3, _translate("MainWindow", "April"))
        self.month.setItemText(4, _translate("MainWindow", "May"))
        self.month.setItemText(5, _translate("MainWindow", "June"))
        self.month.setItemText(6, _translate("MainWindow", "July"))
        self.month.setItemText(7, _translate("MainWindow", "August"))
        self.month.setItemText(8, _translate("MainWindow", "Sept"))
        self.month.setItemText(9, _translate("MainWindow", "Oct"))
        self.month.setItemText(10, _translate("MainWindow", "Nov"))
        self.month.setItemText(11, _translate("MainWindow", "Dec"))
        self.truck.setAccessibleName(_translate("MainWindow", "Truck"))
        self.enter.setText(_translate("MainWindow", "Submit"))
        self.KM.setPlaceholderText(_translate("MainWindow", "Kilometers"))
        self.to.setPlaceholderText(_translate("MainWindow", "To"))
        self.pauch.setItemText(0, _translate("MainWindow", "Pending"))

        self.pauch.setItemText(1, _translate("MainWindow", "Ok"))
        self.pauch.setItemText(2, _translate("MainWindow", "Damaged"))

        self.unload.setPlaceholderText(
            _translate("MainWindow", "Unloading Date"))
        self.dateEdit.setPlaceholderText(_translate("MainWindow", "Load Date"))
        self.frm.setPlaceholderText(_translate("MainWindow", "_From"))
        self.arrival.setPlaceholderText(
            _translate("MainWindow", "Arrival Date"))
        self.load_name.setItemText(0, _translate("MainWindow", "Bajaj Load"))
        self.load_name.setItemText(1, _translate("MainWindow", "Return 1"))
        self.load_name.setItemText(2, _translate("MainWindow", "Return 2"))
        self.load_name.setItemText(3, _translate("MainWindow", "Return 3"))
        self.load_name.setItemText(4, _translate("MainWindow", "Return 4"))
        self.load_name.setItemText(5, _translate("MainWindow", "Return 5"))
        self.load_name.setItemText(6, _translate("MainWindow", "Return 6"))
        self.load_name.setItemText(7, _translate("MainWindow", "Return 7"))
        self.load_name.setItemText(8, _translate("MainWindow", "Return 8"))
        self.load_name.setItemText(9, _translate("MainWindow", "Return 9"))
        self.load_name.setItemText(10, _translate("MainWindow", "Return 10"))
        self.load_name.setItemText(11, _translate("MainWindow", "Return 11"))
        self.load_name.setItemText(12, _translate("MainWindow", "Return 12"))
        self.load_name.setItemText(13, _translate("MainWindow", "Return 13"))
        self.load_name.setItemText(14, _translate("MainWindow", "Return 14"))
        self.load_name.setItemText(15, _translate("MainWindow", "Return 15"))
        self.d_click.setTabText(self.d_click.indexOf(
            self.tab_3), _translate("MainWindow", "Load Details"))
        self.a_pay.setPlaceholderText(_translate("MainWindow", "Amount"))
        self.a_click.setText(_translate("MainWindow", "Submit"))
        self.a_date.setPlaceholderText(_translate("MainWindow", "Date"))
        self.a_place.setPlaceholderText(_translate("MainWindow", "Place"))
        self.d_click.setTabText(self.d_click.indexOf(
            self.tab), _translate("MainWindow", "Advance"))
        self.d_date.setPlaceholderText(_translate("MainWindow", "Date"))
        self.d_pump.setPlaceholderText(_translate("MainWindow", "Pump"))
        self.d_quant.setPlaceholderText(_translate("MainWindow", "Quantity"))
        self.d_pay.setPlaceholderText(_translate("MainWindow", "Rate"))
        self.pushButton.setText(_translate("MainWindow", "Submit"))
        self.d_click.setTabText(self.d_click.indexOf(
            self.tab_2), _translate("MainWindow", "Diesel"))
        self.d_click.setTabText(self.d_click.indexOf(
            self.tab_4), _translate("MainWindow", "Repairing"))
        self.truck_2.setAccessibleName(_translate("MainWindow", "Truck"))
        self.d_click.setTabText(self.d_click.indexOf(
            self.tab_5), _translate("MainWindow", "Documents"))

        self.label_6.setText(_translate("MainWindow", "Viren Logistics"))

        self.truck.addItems(self.t1)

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())


from SimpleCV import *
import numpy as np

cam = Kinect()
depth = cam.getDepth()
width = depth.width
height = depth.height
screensize = width * height
threshold = 255

while True:
  depth = cam.getDepth()
  avgcolor = depth.meanColor()
  R, G, B = avgcolor
  if (R > 255 or B > 255 or G > 255):
    depth.drawText('Threshold Exceeded.')
  depth.show()