import logging
logger = logging.getLogger(__name__)

import math
import enum
import pathlib
import tempfile
import mimetypes
import webbrowser
import collections
from urllib.request import url2pathname
from urllib.parse import urlsplit

import gi
gi.require_version('Poppler', '0.18')
from gi.repository import Poppler

from pympress.util import fileopen

def get_extension(mime_type):
    """ Returns a valid filename extension (recognized by python) for a given mime type.

    Args:
        mime_type (`str`): The mime type for which to find an extension

    Returns:
        `str`: A file extension used for the given mimetype
    """
    if not mimetypes.inited:
        mimetypes.init()
    for ext in mimetypes.types_map:
        if mimetypes.types_map[ext] == mime_type:
            return ext

class PdfPage(enum.IntEnum):
    """ Represents the part of a PDF page that we want to draw.
    """
    #: No notes on PDF page, only falsy value
    NONE    = 0
    #: Full PDF page (without notes)
    FULL    = 1
    #: Bottom half of PDF page
    BOTTOM  = 2
    #: Top half of PDF page
    TOP     = 3
    #: Right half of PDF page
    RIGHT   = 4
    #: Left half of PDF page
    LEFT    = 5
    #: Full page + draw another page for notes, which is after the slides
    AFTER   = 6
    #: Complementary of AFTER: for a notes page, the slide page is BEFORE by half a document
    BEFORE  = 7
    #: Slides on even pages (0-indexed), notes on uneven pages
    ODD     = 8
    #: Complementary of ODD
    EVEN    = 9
    #: An arbitrary mapping of notes pages to slide pages
    MAP     = 10
    #: Reverse the arbitrary mapping MAP
    RMAP    = 11

    def complement(val):
        """ Return the enum value for the other part of the page.
        """
        return PdfPage(val ^ 1)

    def scale(val):
        """ Return the enum value that does only scaling not shifting.
        """
        return PdfPage(val | 1)

    def direction(val):
        """ Returns whether the pdf page/notes mode is horizontal or vertical.

        Returns:
            `str`: a string representing the direction that can be used as the key in the config section
        """
        if val == PdfPage.LEFT or val == PdfPage.RIGHT:
            return 'horizontal'
        elif val == PdfPage.TOP or val == PdfPage.BOTTOM:
            return 'vertical'
        elif val == PdfPage.AFTER or val == PdfPage.BEFORE:
            return 'page number'
        elif val == PdfPage.EVEN or val == PdfPage.ODD:
            return 'page parity'
        elif val == PdfPage.MAP or val == PdfPage.RMAP:
            return 'page mapping'
        else:
            return None

    def from_screen(val, x, y, x2 = None, y2 = None):
        """ Transform visible part of the page coordinates to full page coordinates.

        Pass 2 floats to transform coordinates, 4 to transform margins,
        i.e. the second pair of coordinates is taken from the opposite corner.

        Args:
            x (`float`): x coordinate on the screen, on a scale 0..1
            y (`float`): y coordinate on the screen, on a scale 0..1
            x2 (`float`): second x coordinate on the screen, from the other side, on a scale 0..1
            y2 (`float`): second y coordinate on the screen, from the other side, on a scale 0..1
        """
        if val == PdfPage.RIGHT:
            page = ((1 + x) / 2., y)
        elif val == PdfPage.LEFT:
            page = (x / 2., y)
        elif val == PdfPage.BOTTOM:
            page = (x, (1 + y) / 2.)
        elif val == PdfPage.TOP:
            page = (x, y / 2.)
        else:
            page = (x, y)

        if x2 is None or y2 is None:
            return page
        else:
            return page + val.complement().from_screen(x2, y2)

    def to_screen(val, x, y, x2 = None, y2 = None):
        """ Transform full page coordinates to visible part coordinates.

        Pass 2 floats to transform coordinates, 4 to transform margins,
        i.e. the second pair of coordinates is taken from the opposite corner.

        Args:
            x (`float`): x coordinate on the page, on a scale 0..1
            y (`float`): y coordinate on the page, on a scale 0..1
            x2 (`float`): second x coordinate on the page, from the other side, on a scale 0..1
            y2 (`float`): second y coordinate on the page, from the other side, on a scale 0..1
        """
        if val == PdfPage.RIGHT:
            screen = (x * 2 - 1, y)
        elif val == PdfPage.LEFT:
            screen = (x * 2, y)
        elif val == PdfPage.BOTTOM:
            screen = (x, y * 2 - 1)
        elif val == PdfPage.TOP:
            screen = (x, y * 2)
        else:
            screen = (x, y)

        if x2 is None or y2 is None:
            return screen
        else:
            return screen + val.complement().to_screen(x2, y2)

class Link(object):
    """ This class encapsulates one hyperlink of the document.

    Args:
        x1 (`float`):  first x coordinate of the link rectangle
        y1 (`float`):  first y coordinate of the link rectangle
        x2 (`float`):  second x coordinate of the link rectangle
        y2 (`float`):  second y coordinate of the link rectangle
        action (`function`):  action to perform when the link is clicked
    """

    #: `float`, first x coordinate of the link rectangle
    x1 = None
    #: `float`, first y coordinate of the link rectangle
    y1 = None
    #: `float`, second x coordinate of the link rectangle
    x2 = None
    #: `float`, second y coordinate of the link rectangle
    y2 = None
    #: `function`, action to be perform to follow this link
    follow = lambda *args, **kwargs: logger.error(_("no action defined for this link!"))

    def __init__(self, x1, y1, x2, y2, action):
        self.x1, self.y1, self.x2, self.y2 = x1, y1, x2, y2
        self.follow = action

    def is_over(self, x, y):
        """ Tell if the input coordinates are on the link rectangle.

        Args:
            x (`float`):  input x coordinate
            y (`float`):  input y coordinate

        Returns:
            `bool`: `True` if the input coordinates are within the link rectangle, `False` otherwise
        """
        return ((self.x1 <= x) and (x <= self.x2) and (self.y1 <= y) and (y <= self.y2))

    @staticmethod
    def build_closure(fun, *args, **kwargs):
        r""" Return a lambda that calls fun(\*args, \**kwargs), with the current value of args and kwargs.

        By creating the lambda in a new scope, we bind the arguments.

        Args:
            fun (`function`): The function to be called
            args (`tuple`): non-keyworded variable-length argument list to pass to fun()
            kwargs (`dict`): keyworded variable-length argument dict to pass to fun()
        """
        return lambda *a, **k: fun(*(tuple(args) + tuple(a)), **dict(kwargs, **k))

#: A class that holds all the properties for media files
Media = collections.namedtuple('Media', ['relative_margins', 'filename', 'autoplay', 'repeat', 'poster',
                                         'show_controls', 'type', 'start_pos', 'duration'],
                               defaults=[False, False, False, False, '', 0., 0.])

class Page(object):
    """ Class representing a single page.

    It provides several methods used by the GUI for preparing windows for
    displaying pages, managing hyperlinks, etc.

    Args:
        doc (:class:`~Poppler.Page`):  the poppler object around the page
        number (`int`):  number of the page to fetch in the document
        parent (:class:`~pympress.document.Document`):  the parent Document class
    """

    #: Page handled by this class (instance of :class:`~Poppler.Page`)
    page = None
    #: `int`, number of the current page (starting from 0)
    page_nb = -1
    #: `str` representing the page label
    page_label = None
    #: All the links in the page, as a `list` of :class:`~pympress.document.Link` instances
    links = []
    #: All the media in the page, as a `list` of :class:`~pympress.document.Media`
    medias = []
    #: `float`, page width
    pw = 0.
    #: `float`, page height
    ph = 0.
    #: All text annotations
    annotations = []
    #: Instance of :class:`~pympress.document.Document` that contains this page.
    parent = None

    def __init__(self, page, number, parent):
        self.page = page
        self.page_nb = number
        self.parent = parent
        self.links = []
        self.medias = []
        self.annotations = []

        if self.page is None:
            return

        # Get page label
        self.page_label = self.page.get_label()

        # Read page size
        self.pw, self.ph = self.page.get_size()

        # Read links on the page
        for link in self.page.get_link_mapping():
            action = self.get_link_action(link.action.type, link.action)
            my_link = Link(link.area.x1, link.area.y1, link.area.x2, link.area.y2, action)
            self.links.append(my_link)

        # Read annotations, in particular those that indicate media
        for annotation in self.page.get_annot_mapping():
            annot_type = annotation.annot.get_annot_type()

            if annot_type == Poppler.AnnotType.LINK:
                # just an Annot, not subclassed -- probably redundant with links
                continue
            elif annot_type == Poppler.AnnotType.MOVIE:
                movie = annotation.annot.get_movie()
                filepath = self.parent.get_full_path(movie.get_filename())
                if not filepath:
                    logger.error(_("Pympress can not find file ") + movie.get_filename())
                    continue

                relative_margins = Poppler.Rectangle()
                relative_margins.x1 = annotation.area.x1 / self.pw        # left
                relative_margins.x2 = 1.0 - annotation.area.x2 / self.pw  # right
                relative_margins.y1 = annotation.area.y1 / self.ph        # bottom
                relative_margins.y2 = 1.0 - annotation.area.y2 / self.ph  # top

                movie_options = {'show_controls': movie.show_controls(), 'poster': movie.need_poster()}
                try:
                    movie_options['repeat'] = movie.get_play_mode() == Poppler.MoviePlayMode.REPEAT
                    movie_options['start_pos'] = movie.get_start() / 1e9
                    movie_options['duration'] = movie.get_duration() / 1e9
                    # NB: autoplay not part of Poppler’s MovieActivationParameters struct
                except AttributeError:
                    pass  # Missing functions in pre-21.04 Poppler versions

                media = Media(relative_margins, filepath, **movie_options)
                self.medias.append(media)
                action = Link.build_closure(self.parent.play_media, hash(media))

            elif annot_type == Poppler.AnnotType.SCREEN:
                action_obj = annotation.annot.get_action()
                if not action_obj:
                    continue
                action = self.get_annot_action(action_obj.any.type, action_obj, annotation.area)
                if not action:
                    continue
            elif annot_type == Poppler.AnnotType.FILE_ATTACHMENT:
                attachment = annotation.annot.get_attachment()
                filename = pathlib.Path(attachment.name)
                with tempfile.NamedTemporaryFile('wb', suffix=filename.suffix, prefix=filename.stem, delete=False) as f:
                    # now the file name is shotgunned
                    filename = pathlib.Path(f.name)
                    self.parent.remove_on_exit(filename)
                if not attachment.save(str(filename)):
                    logger.error(_("Pympress can not extract attached file"))
                    continue
                action = Link.build_closure(fileopen, filename)
            elif annot_type in {Poppler.AnnotType.TEXT, Poppler.AnnotType.POPUP,
                                Poppler.AnnotType.FREE_TEXT}:
                # text-only annotations, hide them from screen and show them in annotations popup
                content = annotation.annot.get_contents()
                if content:
                    self.annotations.append(annotation.annot)

                self.page.remove_annot(annotation.annot)
                continue
            elif annot_type in {Poppler.AnnotType.STRIKE_OUT, Poppler.AnnotType.HIGHLIGHT,
                                Poppler.AnnotType.UNDERLINE, Poppler.AnnotType.SQUIGGLY,
                                Poppler.AnnotType.POLYGON, Poppler.AnnotType.POLY_LINE,
                                Poppler.AnnotType.SQUARE, Poppler.AnnotType.CIRCLE,
                                Poppler.AnnotType.CARET, Poppler.AnnotType.LINE,
                                Poppler.AnnotType.STAMP, Poppler.AnnotType.INK}:
                # Poppler already renders annotation of these types, nothing more can be done
                # even though the rendering isn't always perfect.
                continue
            else:
                logger.warning(_("Pympress can not interpret annotation of type:") + " {} ".format(annot_type))
                continue

            my_annotation = Link(annotation.area.x1, annotation.area.y1, annotation.area.x2, annotation.area.y2, action)
            self.links.append(my_annotation)

    def get_link_action(self, link_type, action):
        """ Get the function to be called when the link is followed.

        Args:
            link_type (:class:`~Poppler.ActionType`): The type of action to be performed
            action (:class:`~Poppler.Action`): The atcion to be performed

        Returns:
            `function`: The function to be called to follow the link
        """
        # Poppler.ActionType.RENDITION should only appear in annotations, right? Otherwise how do we know
        # where to render it? Any documentation on which action types are admissible in links vs in annots
        # is very welcome. For now, link is fallback to annot so contains all action types.
        if link_type == Poppler.ActionType.NONE:
            return lambda: None

        elif link_type == Poppler.ActionType.GOTO_DEST:
            dest_type = action.goto_dest.dest.type
            if dest_type == Poppler.DestType.NAMED:
                dest = self.parent.doc.find_dest(action.goto_dest.dest.named_dest)
                if dest:
                    return Link.build_closure(self.parent.goto_page, dest.page_num - 1)
                else:
                    warning = _('Unrecognized named destination: ') + str(action.goto_dest.dest.named_dest)
            elif dest_type != Poppler.DestType.UNKNOWN:
                return Link.build_closure(self.parent.goto_page, action.goto_dest.dest.page_num - 1)

        elif link_type == Poppler.ActionType.NAMED:
            dest_name = action.named.named_dest
            dest = self.parent.doc.find_dest(dest_name)

            if dest:
                return Link.build_closure(self.parent.goto_page, dest.page_num)
            elif dest_name == "GoBack":
                return self.parent.goto_prev_hist
            elif dest_name == "GoForward":
                return self.parent.goto_next_hist
            elif dest_name == "FirstPage":
                return Link.build_closure(self.parent.goto_page, 0)
            elif dest_name == "PrevPage":
                return Link.build_closure(self.parent.goto_page, self.page_nb - 1)
            elif dest_name == "NextPage":
                return Link.build_closure(self.parent.goto_page, self.page_nb + 1)
            elif dest_name == "LastPage":
                return Link.build_closure(self.parent.goto_page, self.parent.pages_number() - 1)
            elif dest_name == "GoToPage":
                # Same as the 'G' action which allows one to pick a page to jump to
                return Link.build_closure(self.parent.start_editing_page_number, )
            elif dest_name == "Find":
                # TODO popup a text box and search results with Page.find_text
                # http://lazka.github.io/pgi-docs/Poppler-0.18/classes/Page.html#Poppler.Page.find_text
                warning = _("Pympress does not yet support link type \"{}\" to \"{}\"").format(link_type, dest_name)
            else:
                # TODO find out other possible named actions?
                warning = _("Pympress does not recognize link type \"{}\" to \"{}\"").format(link_type, dest_name)

        elif link_type == Poppler.ActionType.LAUNCH:
            launch = action.launch
            if launch.params:
                logger.warning("ignoring params: " + str(launch.params))

            filepath = self.parent.get_full_path(launch.file_name)
            if not filepath:
                logger.error("can not find file " + launch.file_name)
                return lambda: None

            else:
                return Link.build_closure(fileopen, filepath)

        elif link_type == Poppler.ActionType.URI:
            return Link.build_closure(webbrowser.open_new_tab, action.uri.uri)

        elif link_type == Poppler.ActionType.RENDITION:  # Poppler 0.22
            warning = _("Pympress does not yet support link type \"{}\"").format(link_type)
        elif link_type == Poppler.ActionType.MOVIE:  # Poppler 0.20
            warning = _("Pympress does not yet support link type \"{}\"").format(link_type)
        elif link_type == Poppler.ActionType.GOTO_REMOTE:
            warning = _("Pympress does not yet support link type \"{}\"").format(link_type)
        elif link_type == Poppler.ActionType.OCG_STATE:
            warning = _("Pympress does not yet support link type \"{}\"").format(link_type)
        elif link_type == Poppler.ActionType.JAVASCRIPT:
            warning = _("Pympress does not yet support link type \"{}\"").format(link_type)
        elif link_type == Poppler.ActionType.UNKNOWN:
            warning = _("Pympress does not yet support link type \"{}\"").format(link_type)
        else:
            warning = _("Pympress does not recognize link type \"{}\"").format(link_type)

        logger.info(warning)
        return Link.build_closure(logger.warning, _('Unsupported link clicked. ') + warning)

    def get_annot_action(self, link_type, action, rect):
        """ Get the function to be called when the link is followed.

        Args:
            link_type (:class:`~Poppler.ActionType`): The link type
            action (:class:`~Poppler.Action`): The action to be performed when the link is clicked
            rect (:class:`~Poppler.Rectangle`): The region of the page where the link is

        Returns:
            `function`: The function to be called to follow the link
        """
        if link_type == Poppler.ActionType.RENDITION:
            media = action.rendition.media
            if media.is_embedded():
                ext = get_extension(media.get_mime_type())
                with tempfile.NamedTemporaryFile('wb', suffix=ext, prefix='pdf_embed_', delete=False) as f:
                    # now the file name is shotgunned
                    filename = pathlib.Path(f.name)
                    self.parent.remove_on_exit(filename)
                if not media.save(str(filename)):
                    logger.error(_("Pympress can not extract embedded media"))
                    return None
            else:
                filename = self.parent.get_full_path(media.get_filename())
                if not filename:
                    logger.error(_("Pympress can not find file ") + media.get_filename())
                    return None

            relative_margins = Poppler.Rectangle()
            relative_margins.x1 = rect.x1 / self.pw        # left
            relative_margins.x2 = 1.0 - rect.x2 / self.pw  # right
            relative_margins.y1 = rect.y1 / self.ph        # bottom
            relative_margins.y2 = 1.0 - rect.y2 / self.ph  # top

            media_options = {'type': media.get_mime_type()}
            try:
                media_options['autoplay'] = media.get_auto_play()
                media_options['show_controls'] = media.get_show_controls()
                media_options['repeat'] = media.get_repeat_count() - 1
                # NB: no poster in Poppler’s MediaParameters
            except AttributeError:
                pass
            media = Media(relative_margins, filename, **media_options)
            self.medias.append(media)
            return Link.build_closure(self.parent.play_media, hash(media))

        else:
            return self.get_link_action(link_type, action)

    def number(self):
        """ Get the page number.
        """
        return self.page_nb

    def label(self):
        """ Get the page label.
        """
        return self.page_label

    def get_link_at(self, x, y, dtype=PdfPage.FULL):
        """ Get the :class:`~pympress.document.Link` corresponding to the given position.

        Returns `None` if there is no link at this position.

        Args:
            x (`float`):  horizontal coordinate
            y (`float`):  vertical coordinate
            dtype (:class:`~pympress.document.PdfPage`):  the type of document to consider

        Returns:
            :class:`~pympress.document.Link`: the link at the given coordinates
            if one exists, `None` otherwise
        """
        x, y = dtype.from_screen(x, y)

        xx = self.pw * x
        yy = self.ph * (1. - y)

        for link in self.links:
            if link.is_over(xx, yy):
                return link

        return None

    def get_size(self, dtype=PdfPage.FULL):
        """ Get the page size.

        Args:
            dtype (:class:`~pympress.document.PdfPage`):  the type of document to consider

        Returns:
            `(float, float)`: page size
        """
        return dtype.scale().from_screen(self.pw, self.ph)

    def get_aspect_ratio(self, dtype=PdfPage.FULL):
        """ Get the page aspect ratio.

        Args:
            dtype (:class:`~pympress.document.PdfPage`):  the type of document to consider

        Returns:
            `float`: page aspect ratio
        """
        w, h = self.get_size(dtype)
        return w / h

    def get_annotations(self):
        """ Get the list of text annotations on this page.

        Returns:
            `list` of `str`: annotations on this page
        """
        return self.annotations

    def new_annotation(self, pos, rect=None):
        """ Add an annotation to this page

        Args:
            pos (`int`): The position in the list of annotations in which to insert this annotation
            rect (:class:`~Poppler.Rectangle`): A rectangle for the position of this annotation

        Returns:
            :class:`~Poppler.Annot`: A new annotation on this page
        """
        if pos < 0:
            pos = 0
        if pos > len(self.annotations):
            pos = len(self.annotations)

        if rect is None:
            rect = Poppler.Rectangle()
            rect.x1 = self.pw - 20
            rect.x2 = rect.x1 + 20
            rect.y2 = self.ph - len(self.annotations) * 20
            rect.y1 = rect.y2 - 20

        new_annot = Poppler.AnnotText.new(self.parent.doc, rect)
        new_annot.set_icon(Poppler.ANNOT_TEXT_ICON_NOTE)
        self.annotations.insert(pos, new_annot)
        self.parent.made_changes()
        return new_annot

    def set_annotation(self, pos, value):
        """ Add an annotation to this page

        Args:
            pos (`int`): The number of the annotation
            value (`str`): The new contents of the annotation
        """
        rect = self.annotations[pos].get_rectangle()
        self.remove_annotation(pos)
        self.new_annotation(pos, rect).set_contents(value)

    def remove_annotation(self, pos):
        """ Add an annotation to this page

        Args:
            pos (`int`): The number of the annotation
        """
        self.parent.made_changes()
        del self.annotations[pos]

    def get_media(self):
        """ Get the list of medias this page might want to play.

        Returns:
            `list`: medias in this page
        """
        return self.medias

    def render_cairo(self, cr, ww, wh, dtype=PdfPage.FULL):
        """ Render the page on a Cairo surface.

        Args:
            cr (:class:`~Gdk.CairoContext`):  target surface
            ww (`int`):  target width in pixels
            wh (`int`):  target height in pixels
            dtype (:class:`~pympress.document.PdfPage`):  the type of document that should be rendered
        """
        pw, ph = self.get_size(dtype)

        cr.set_source_rgb(1, 1, 1)

        # Scale
        scale = min(ww / pw, wh / ph)
        cr.scale(scale, scale)

        cr.rectangle(0, 0, pw, ph)
        cr.fill()

        # For "regular" pages, there is no problem: just render them.
        # For other pages (i.e. half of a page), the widget already has correct
        # dimensions so we don't need to deal with that. But for right and bottom
        # halfs we must translate the output in order to only show the correct half.
        if dtype == PdfPage.RIGHT:
            cr.translate(-pw, 0)
        elif dtype == PdfPage.BOTTOM:
            cr.translate(0, -ph)

        self.page.render(cr)

    def can_render(self):
        """ Informs that rendering *is* necessary (avoids checking the type).

        Returns:
            `bool`: `True`, do rendering
        """
        return True

class Document(object):
    """ This is the main document handling class.

    The page numbering starts as 0 and is aware of notes (i.e. number of pages may change to account for note pages).
    The document page numbers are the same as in Poppler, and also start at 0 but do not depend on notes.

    Args:
        builder (:class:`pympress.builder.Builder`):  A builder to load callbacks
        pop_doc (:class:`~pympress.Poppler.Document`):  Instance of the Poppler document that this class will wrap
        uri (`str`):  URI of the PDF file to open
        page (`int`):  page number to which the file should be opened
    """

    #: Current PDF document (:class:`~Poppler.Document` instance)
    doc = None
    #: `str` full path to pdf
    uri = None
    #: :class:`~pathlib.Path` to pdf if uri is a file: URI
    path = None
    #: Number of pages in the document
    nb_pages = -1
    #: Pages cache (`dict` of :class:`~pympress.document.Page`). This makes
    #: navigation in the document faster by avoiding calls to Poppler when loading
    #: a page that has already been loaded.
    pages_cache = {}
    #: `set` of :class:`~pathlib.Path` representing the temporary files which need to be removed
    temp_files = set()
    #: History of pages we have visited, using note-aware page numbers
    history = []
    #: Our position in the history
    hist_pos = -1
    #: `list` of slide page labels, indexed on note-aware page numbers
    page_labels = []
    #: `list` of all the page labels, indexed on document page numbers
    doc_page_labels = []
    #: `list` of (slide's document page number, notes' document page number) tuples, or `None` if there are no notes
    notes_mapping = None
    #: `bool` indicating whether there were modifications to the document
    changes = False

    #: callback, to be connected to :func:`~pympress.extras.Media.play`
    play_media = lambda *args: None
    #: callback, to be connected to :func:`~pympress.editable_label.PageNumber.start_editing`
    start_editing_page_number = lambda *args: None
    #: callback, to be connected to :func:`~pympress.ui.UI.goto_page`
    navigate = lambda *args: None

    def __init__(self, builder, pop_doc, uri):
        if builder is not None:
            # Connect callbacks
            self.play_media                = builder.get_callback_handler('medias.play')
            self.start_editing_page_number = builder.get_callback_handler('page_number.start_editing')
            self.goto_page                 = builder.get_callback_handler('goto_page')
            self.goto_next_hist            = builder.get_callback_handler('doc_hist_next')
            self.goto_prev_hist            = builder.get_callback_handler('doc_hist_prev')

        # Setup PDF file
        self.uri = uri
        self.doc = pop_doc
        self.changes = False

        if uri is not None:
            uri_parts = urlsplit(uri, scheme='file')
            self.path = pathlib.Path(url2pathname(uri_parts.path))
            if uri_parts.scheme == 'file':
                self.path = pathlib.Path.cwd().joinpath(self.path.name)
        else:
            self.path = None

        # Pages numbers and labels
        self.nb_pages = 0 if pop_doc is None else self.doc.get_n_pages()
        self.doc_page_labels = [self.doc.get_page(n).get_label() for n in range(self.nb_pages)]
        self.page_labels = self.doc_page_labels

        # Pages cache
        self.pages_cache = {}

    def get_structure(self, index_iter = None):
        """ Gets the structure of the document from its index.

        Recursive, pass the iterator.

        Args:
            index_iter (:class:`~Poppler.IndexIter` or `None`): the iterator for the child index to explore.

        Returns:
            `list`: A list of tuples (depth, page number, title)
        """
        try:
            if index_iter is None:
                index_iter = Poppler.IndexIter(self.doc)
        except TypeError:
            return {}
        if index_iter is None:
            return {}

        index = {}
        while True:
            action = index_iter.get_action()
            title = ''
            page = None
            try:
                if action.type == Poppler.ActionType.GOTO_DEST:
                    title = action.goto_dest.title
                    if action.goto_dest.dest.type == Poppler.DestType.NAMED:
                        page = self.doc.find_dest(action.goto_dest.dest.named_dest).page_num - 1
                    elif action.goto_dest.dest.type == Poppler.DestType.UNKNOWN:
                        raise AssertionError('Unknown type of destination')
                    else:
                        page = action.goto_dest.dest.page_num - 1
                else:
                    raise AssertionError('Unexpected type of action')

            except Exception:
                logger.error(_('Unexpected action in index "{}"').format(action.type))
                page = None

            new_entry = {'title': title}
            child = index_iter.get_child()
            if child:
                new_entry['children'] = self.get_structure(child)
                if page is None:
                    page = min(new_entry['children'])

            # there should not be synonymous sections, correct the page here to a better guess
            if page in index:
                lower_bound = max(index.keys())
                find = index[lower_bound]
                while 'children' in find:
                    lower_bound = max(find['children'].keys())
                    find = find['children'][lower_bound]

                try:
                    page = min(number for number, label in enumerate(self.page_labels)
                               if label == self.page_labels[page] and number > lower_bound)
                except ValueError:  # empty iterator
                    page = lower_bound + 1

            if page is not None:
                index[page] = new_entry

            if not index_iter.next():
                break

        return index

    @staticmethod
    def create(builder, uri):
        """ Initializes a Document by passing it a :class:`~Poppler.Document`.

        Args:
            builder (:class:`pympress.builder.Builder`):  A builder to load callbacks
            uri (`str`):  URI to the PDF file to open
            page (`int`):  page number to which the file should be opened

        Returns:
            :class:`~pympress.document.Document`: The initialized document
        """
        if uri is None:
            doc = EmptyDocument()
        else:
            poppler_doc = Poppler.Document.new_from_file(uri, None)
            doc = Document(builder, poppler_doc, uri)

        return doc

    def made_changes(self):
        """ Notify the document that some changes were made (e.g. annotations edited)
        """
        self.changes = True

    def has_changes(self):
        """ Return whether that some changes were made (e.g. annotations edited)
        """
        return self.changes

    def save_changes(self, dest_uri=None):
        """ Save the changes

        Args:
            dest_uri (`str` or `None`): The URI where to save the file, or None to save in-place
        """
        if self.doc is None:
            return

        for page in self.pages_cache.values():
            for annot in page.get_annotations():
                page.page.add_annot(annot)

        if dest_uri is not None and dest_uri != self.uri:
            if self.doc.save(dest_uri):
                self.changes = False
        else:
            # We can’t overwrite the current file directly, so create a temporary file and then overwrite
            with tempfile.NamedTemporaryFile('wb', suffix=self.path.suffix, prefix=self.path.stem, dir=self.path.parent,
                                             delete=False) as f:
                temp_path = pathlib.Path(f.name)

            if self.doc.save(temp_path.as_uri()):
                self.changes = False
                temp_path.replace(self.path)

        for page in self.pages_cache.values():
            for annot in page.get_annotations():
                page.page.remove_annot(annot)

    def guess_notes(self, horizontal, vertical, current_page=0):
        """ Get our best guess for the document mode.

        Args:
            horizontal (`str`): A string representing the preference for horizontal slides
            vertical (`str`): A string representing the preference for vertical slides

        Returns:
            :class:`~pympress.document.PdfPage`: the notes mode
        """
        if any(label.startswith('notes:') for label in self.page_labels):
            return PdfPage.MAP

        page = self.page(current_page)
        if page is None or not page.can_render():
            return PdfPage.NONE

        ar = page.get_aspect_ratio()

        # Check whether we have N slides with one aspect ratio then N slides with a different aspect ratio
        # that is the sign if Libreoffice notes pages
        if self.nb_pages and self.nb_pages % 2 == 0:
            half_doc = self.nb_pages // 2
            ar_slides = self.page(0).get_aspect_ratio()
            ar_notes = self.page(half_doc).get_aspect_ratio()
            if ar_slides != ar_notes and \
                    all(self.page(p).get_aspect_ratio() == ar_slides for p in range(1, half_doc)) and \
                    all(self.page(half_doc + p).get_aspect_ratio() == ar_notes for p in range(1, half_doc)):
                return PdfPage.AFTER

        # "Regular" slides will have an aspect ratio of 4/3, 16/9, 16/10... i.e. in the range [1..2]
        # So if the aspect ratio is >= 2, we can assume it is a document with notes on the side.
        if ar >= 2:
            try:
                return PdfPage[horizontal.upper()]
            except KeyError:
                return PdfPage.RIGHT

        # Make exception for classic american letter format and ISO (A4, B5, etc.)
        if abs(ar - 8.5 / 11) < 1e-3 or abs(ar - 1 / math.sqrt(2)) < 1e-3:
            return PdfPage.NONE

        # If the aspect ratio is < 1, we can assume it is a document with notes above or below.
        if ar < 1:
            try:
                return PdfPage[vertical.upper()]
            except KeyError:
                return PdfPage.BOTTOM

        return PdfPage.NONE

    def set_notes_pos(self, notes_direction):
        """ Set whether where the notes pages are relative to normal pages

        Valid values are returned by :meth:`~pympress.document.PdfPage.direction`
        - page number (aka Libreoffice notes mode)
        - page parity (can not be detected automatically, where every other page contains notes)
        - page mapping (where labels of notes pages are corresponding slide labels prefixed with “notes:”)

        Args:
            notes_direction (`str`):  Where the notes pages are
        """
        if notes_direction == 'page number':
            self.notes_mapping = [(n, n + self.nb_pages // 2) for n in range(self.nb_pages // 2)]
        elif notes_direction == 'page parity':
            self.notes_mapping = [(n, n + 1) for n in range(0, self.nb_pages, 2)]
        elif notes_direction == 'page mapping':
            notes_mapping = collections.OrderedDict()
            for n, (label, prev_label) in enumerate(zip(self.doc_page_labels, [None, *self.doc_page_labels[:-1]])):
                # Here the condition (could be adjusted) is 2 successive pages labeled <label> and notes:<label>,
                # with the prior page not being a notes page.
                if n - 1 in notes_mapping and label == 'notes:' + prev_label:
                    notes_mapping[n - 1] = n
                else:
                    notes_mapping[n] = None
            self.notes_mapping = list(notes_mapping.items())
        else:
            self.notes_mapping = None
            self.page_labels = self.doc_page_labels
            return

        self.page_labels = [self.doc_page_labels[page] for page, note in self.notes_mapping]

    def page(self, number):
        """ Get the specified page.

        Args:
            number (`int`):  number of the page to return

        Returns:
            :class:`~pympress.document.Page`: the wanted page, or `None` if it does not exist
        """
        if number >= self.pages_number() or number < 0:
            return None

        if self.notes_mapping is not None:
            number = self.notes_mapping[number][0]
            if number < 0:
                return None

        if number not in self.pages_cache:
            self.pages_cache[number] = Page(self.doc.get_page(number), number, self)
        return self.pages_cache[number]

    def notes_page(self, number):
        """ Get the specified page.

        Args:
            number (`int`):  number of the page to return

        Returns:
            :class:`~pympress.document.Page`: the wanted page, or `None` if it does not exist
        """
        if number >= self.pages_number() or number < 0:
            return None

        if self.notes_mapping is not None:
            number = self.notes_mapping[number][1]

        if number is None:
            return None

        if number not in self.pages_cache:
            self.pages_cache[number] = Page(self.doc.get_page(number), number, self)
        return self.pages_cache[number]

    def pages_number(self):
        """ Get the number of pages in the document.

        Returns:
            `int`: the number of pages in the document
        """
        return len(self.notes_mapping) if self.notes_mapping is not None else self.nb_pages

    def has_labels(self):
        """ Return whether this document has useful labels.

        Returns:
            `bool`: False iff there are no labels or they are just the page numbers
        """
        return self.doc_page_labels != [str(n + 1) for n in range(self.nb_pages)]

    def get_last_label_pages(self):
        """ Return the last page number for each consecutively distinct page label

        In other words, squash together consecutive same labels
        """
        last = None
        pages = []
        for page, label in enumerate(self.page_labels):
            if label != last:
                pages.append(page)
            last = label
        return pages

    def lookup_label(self, label, prefix_unique=True):
        """ Find a page from its label.

        Args:
            label (`str`): the label we are searching for
            prefix_unique (`bool`): whether a prefix match should be unique, e.g. when the user is still typing

        Returns:
            `int`: the page
        """
        # somehow this always returns None:
        # page = self.doc.get_page_by_label(label).get_index()

        # make a shortlist: squash synonymous labels, keeping the last one
        compatible_labels = {l: n for n, l in enumerate(self.page_labels) if l.lower().startswith(label.lower())}

        if len(compatible_labels) == 1:
            return set(compatible_labels.values()).pop()

        # try exact match
        try:
            return compatible_labels[label]
        except KeyError:
            pass

        # try case-insensitive match, prefix case-sensitive match, prefix case-insensitive match (unless prefix_unique)
        filters = [lambda lbl: len(lbl) == len(label), lambda lbl: lbl.startswith(label), lambda lbl: not prefix_unique]
        for filtering in filters:
            try:
                found = next(label for label in compatible_labels if filtering(label))
            except StopIteration:
                pass
            else:
                return compatible_labels[found]

        return None

    def goto(self, number):
        """ Switch to another page. Validates the number and returns one in the correct range. Also updates history.

        Args:
            number (`int`):  number of the destination page
        """
        if number < 0:
            number = 0
        if number >= self.pages_number():
            number = self.pages_number() - 1

        if 0 <= self.hist_pos < len(self.history) and self.history[self.hist_pos] == number:
            return number

        # chop off history where we were and append
        self.hist_pos = min(len(self.history), self.hist_pos + 1)
        del self.history[self.hist_pos:]
        self.history.append(number)

        return number

    def label_after(self, page):
        """ Switch to the next page with different label.

        If we're within a set of pages with the same label we want to go to the last one.
        """
        labels_after = enumerate(self.page_labels[page + 1:], page + 1)

        try:
            next_page, next_label = next(labels_after)
        except StopIteration:
            # we're already at the last page!
            return page

        # will stop as soon as next_page + 1 (aka following_page) is a different label or due to end of iterator
        for following_page, following_label in labels_after:
            if following_label == next_label:
                next_page = following_page
            else:
                break

        return next_page

    def label_before(self, page):
        """ Switch to the previous page with different label.

        If we're within a set of pages with the same label we want to go *before* the first one.
        """
        # will stop as soon as we find a different label or due to end of iterator
        for prev_page, prev_label in enumerate(reversed(self.page_labels[:page])):
            if prev_label != self.page_labels[page]:
                return page - 1 - prev_page

        return 0

    def hist_next(self, *args):
        """ Switch to the page we viewed next.
        """
        if self.hist_pos + 1 == len(self.history):
            return None

        self.hist_pos += 1
        return self.history[self.hist_pos]

    def hist_prev(self, *args):
        """ Switch to the page we viewed before.
        """
        if self.hist_pos == 0:
            return None

        self.hist_pos -= 1
        return self.history[self.hist_pos]

    def get_uri(self):
        """ Gives access to the URI, rather than the path, of this document.

        Returns:
            `str`: the URI to the file currently opened.
        """
        return self.uri

    def get_full_path(self, filename):
        """ Returns full path, extrapolated from a path relative to this document or to the current directory.

        Args:
            filename (:class:`~pathlib.Path` or `str`):  Name of the file or relative path to it

        Returns:
            :class:`~pathlib.Path`: the full path to the file or None if it doesn't exist
        """
        filename = pathlib.Path(filename)
        if filename.is_absolute():
            return filename.resolve() if filename.exists() else None

        for dirname in [pathlib.Path(url2pathname(urlsplit(self.uri).path)).parent, pathlib.Path.cwd()]:
            filepath = dirname.joinpath(filename)
            if filepath.exists():
                return filepath

    def remove_on_exit(self, filename):
        """ Remember a temporary file to delete later.

        Args:
            filename (:class:`~pathlib.Path`): The path to the file to delete
        """
        self.temp_files.add(filename)

    def cleanup_media_files(self):
        """ Removes all files that were extracted from the pdf into the filesystem.
        """
        for f in self.temp_files:
            if f.exists():
                f.unlink()
        self.temp_files.clear()

class EmptyPage(Page):
    """ A dummy page, placeholder for when there are no valid pages around.

    This page is a non-notes page with an aspect ratio of 1.3 and nothing else inside.
    Also, it has no "rendering" capability, and is made harmless by overriding its render function.
    """

    def __init__(self):
        super(EmptyPage, self).__init__(None, -1, None)
        self.page_label = None
        # by default, anything that will have a 1.3 asapect ratio
        self.pw, self.ph = 1.3, 1.0

    def render_cairo(self, cr, ww, wh, dtype=PdfPage.FULL):
        """ Overriding this purely for safety: make sure we do not accidentally try to render.

        Args:
            cr (:class:`~Gdk.CairoContext`):  target surface
            ww (`int`):  target width in pixels
            wh (`int`):  target height in pixels
            dtype (:class:`~pympress.document.PdfPage`):  the type of document that should be rendered
        """
        pass

    def can_render(self):
        """ Informs that rendering is *not* necessary (avoids checking the type).

        Returns:
            `bool`: `False`, no rendering
        """
        return False

class EmptyDocument(Document):
    """ A dummy document, placeholder for when no document is open.
    """
    def __init__(self):
        super(EmptyDocument, self).__init__(None, None, None)
        self.pages_cache[-1] = EmptyPage()

    def page(self, number):
        """ Retrieve a page from the document.

        Args:
            number (`int`): page number to be retrieved

        Returns:
            :class:`~pympress.document.EmptyPage` or `None`: -1 returns the empty page so we can display something.
        """
        return self.pages_cache[number] if number in self.pages_cache else None

    def notes_page(self, number):
        """ Retrieve a page from the document.

        Args:
            number (`int`): page number to be retrieved

        Returns:
            :class:`~pympress.document.EmptyPage` or `None`: -1 returns the empty page so we can display something.
        """
        return self.pages_cache[number] if number in self.pages_cache else None

##
# Local Variables:
# mode: python
# indent-tabs-mode: nil
# py-indent-offset: 4
# fill-column: 80
# end:


# https://pypi.org/project/pinyin/
# %%
import re
from pathlib import Path
import sys
import file_lib as f_lib
import pinyin

pinyin.get(u'你好')
pinyin.get('你好', format="strip", delimiter="")
pinyin.get('john smith', format="strip", delimiter="")

# %%
# ANCHOR 1. Loading data :

# setting up path to the data file

def load_data():
    file_name = Path(
        'C:\Local\Work\ML_Name\Code\Test\data\multi_class_tst_cn.csv')
    df = f_lib.load_csv_file(file_name)
    return(df)

# df = load_data()

# %%
# ANCHOR 3. Data Preprocessing
# Data Preprocessing :
# 1. Vectorization:  converting all kind input into unique digit vector as input matrix
# 3. Convert all output label into unique output

def data_preprocessing(df):
    # Func:
    out_put_file_name = Path("C:\Local\Work\ML_Name\Code\Test\multi_cn.csv")
    with open(out_put_file_name,   encoding='utf-8', mode="w") as file:
        file.write('code,name\n')
        for i in range(df['name'].size):
            code = list(df['code'])[i]
            x = list(df['name'])[i]
            x = pinyin.get(x, format="strip", delimiter="")
            x = x.title()
            file.write(f"{code},{x}\n")
    return(1)

# data_preprocessing(df)

# %%
if __name__ == '__main__':
    df = load_data()
    _ = data_preprocessing(df)
    print(_)

# %%


import collections
import functools
import os
import re
import traceback
from colors import color
import boto3
from botocore.exceptions import (ClientError,
                                 NoCredentialsError,
                                 NoRegionError,
                                 WaiterError)
import json

_ec2 = None
_cfn_client = None
_cfn_resource = None

def print_success():
    print(color('ok.', fg='green'))

def print_error(message):
    print(color('error!', fg='red') + ' ' + message)

def has_keys(mapping, keys):
    for key in keys:
        if key not in mapping:
            return False
    else:
        return True

def transform_aws_to_dict(mapping):
    """Transform an AWS-style dict to a natural dict. If a key appears multiple
    times, the last value is used.

    Arguments:
        mapping {list} -- A list of the form [{'__Key': KeyValue, '__Value': Value}]

    Returns:
        dict -- A dict of the form {'KeyValue': Value}
    """
    def flatten_item(item):
        out_dict = {}
        for key, value in item.items():
            if key.endswith('Key'):
                out_key = value
            elif key.endswith('Value'):
                out_value = value
        out_dict[out_key] = out_value
        return out_dict

    natural_dict = {
        key: value
        for i in map(
            flatten_item,
            mapping
        )
        for key, value in i.items()
    }

    return natural_dict

def transform_dict_to_aws(mapping, prefix='Parameter'):
    """Transform a natural dict to an AWS-style dict

    Arguments:
        mapping {dict} -- A dict of the form {'KeyValue': 'Value'}

    Keyword Arguments:
        prefix {str} -- The prefix to use before '__Key' and '__Value' (default: {'Parameter'})

    Returns:
        dict -- A dict of the form {'PrefixKey': KeyValue, 'PrefixValue': Value}
    """
    return [
      { prefix + 'Key': key,  prefix + 'Value': value }
      for key, value in mapping.items()
    ]

def initialize_boto():
    print('Initializing boto3... ', end='')
    try:
        global _ec2
        global _cfn_client
        global _cfn_resource
        _ec2 = boto3.client('ec2')
        _cfn_client = boto3.client('cloudformation')
        _cfn_resource = boto3.resource('cloudformation')
    except (NoCredentialsError, NoRegionError) as err:
        print_error(err.fmt)
        exit(1)
    except:
        print_error('An unexpected error occurred')
        traceback.print_exc()
        exit(1)
    else:
        print_success()

def create_stack(template_file_name, stack_inputs):
    script_path = os.path.dirname(os.path.abspath(__file__))
    template_path = os.path.join(script_path, template_file_name)
    with open(template_path, mode='r') as file:
        template_body = file.read()

    action = _cfn_client.create_stack(
        Parameters=stack_inputs['Parameters'],
        StackName=stack_inputs['StackName'],
        TemplateBody=template_body,
        TimeoutInMinutes=30
    )

    waiter = _cfn_client.get_waiter('stack_create_complete')
    waiter.wait(StackName=action['StackId'])
    return action['StackId']

def extract_gateway_props(stack_props):
    """Map the properties for each gateway to the gateway ID

    Arguments:
        stack_props {dict} -- A dict containing all stack properties

    Returns:
        dict -- A dict mapping gateway IDs to properties
    """
    matching_props = collections.defaultdict(dict)
    pattern = r"^Gateway(?P<id>[A-Z])(?P<attr>\w*)$"
    for key, value in stack_props.items():
        match = re.match(pattern, key)
        if match is not None:
            gateway_key = match['id']
            prop_key = match['attr']
            matching_props[gateway_key][prop_key] = value
    return matching_props

def map_attachments_to_vpns(gateway_props, transit_gateway_id):
    """Map transit gateway attachments to VPN connections based on ID

    Arguments:
        gateway_props {dict} -- A dict mapping gateway IDs to gateway properties
        transit_gateway_id {string} -- The ID of a Transit Gateway
    """
    attachments = _ec2.describe_transit_gateway_attachments(
        Filters=[{'Name': 'transit-gateway-id', 'Values': [transit_gateway_id]}]
    )['TransitGatewayAttachments']

    for attachment in attachments:
        for props in gateway_props.values():
            if props['VpnConnection'] == attachment['ResourceId']:
                props['TransitGatewayAttachment'] = attachment['TransitGatewayAttachmentId']

def tag_vpn_attachments(gateway_props):
    """Assign a Name tag to each VPN attachment based on gateway ID

    Arguments:
        gateway_props {dict} -- A dict mapping gateway IDs to gateway properties
    """
    for gateway_id, props in gateway_props.items():
        resource_id = props['TransitGatewayAttachment']
        try:
            _ec2.create_tags(
                Resources=[resource_id],
                Tags=[
                    {
                        'Key': 'Name',
                        'Value': 'Attachment to Gateway {0} VPN'.format(gateway_id)
                    }
                ]
            )
        except ClientError:
            raise

def associate_vpn_attachments(gateway_props, tg_route_table_id):
    """Associate each VPN attachment with a Transit Gateway route table

    Arguments:
        gateway_props {dict} -- A dict mapping gateway IDs to gateway properties
        tg_route_table_id {string} -- The ID of a Transit Gateway route table
    """
    for gateway_id, props in gateway_props.items():
        attachment_id = props['TransitGatewayAttachment']

        try:
            _ec2.associate_transit_gateway_route_table(
                TransitGatewayRouteTableId=tg_route_table_id,
                TransitGatewayAttachmentId=attachment_id
            )
        except ClientError as err:
            if err.response['Error']['Code'] != 'Resource.AlreadyAssociated':
                raise

def pre_cfn(tg_stack_inputs, evpc_stack_inputs):
    print('Creating Transit Gateway stack... ', end='', flush=True)
    try:
        tg_inputs = json.JSONDecoder().decode(tg_stack_inputs)
        tg_stack_id = create_stack('transit-gateway.yml', tg_inputs)
    except (ClientError, WaiterError) as e:
        print_error(e.response['Error']['Message'])
        exit(1)
    except json.JSONDecodeError as e:
        print_error('Invalid JSON in --stack-inputs.')
        exit(1)
    except KeyError as e:
        missing_key = e.args[0]
        print_error('Missing key \'{0}\' in --stack-inputs.'.format(missing_key))
        exit(1)
    except:
        print_error('An unexpected error occurred')
        traceback.print_exc()
        exit(1)
    else:
        print_success()

    # The Egress VPC stack needs the Transit Gateway ID from the Transit
    # Gateway stack as an input.
    tg_stack = _cfn_resource.Stack(tg_stack_id) # pylint: disable=no-member
    tg_stack_outputs = transform_aws_to_dict(tg_stack.outputs)
    tg_id = tg_stack_outputs['TransitGateway']

    print('Creating Egress VPC stack... ', end='', flush=True)
    try:
        evpc_inputs = json.JSONDecoder().decode(evpc_stack_inputs)
        evpc_parameters = evpc_inputs['Parameters']

        # Upsert the Transit Gateway ID in the Egress VPC stack inputs.
        for inp in evpc_parameters:
            if inp['ParameterKey'] == 'TransitGatewayId':
                inp['ParameterValue'] = tg_id
                break
        else:
            evpc_parameters.append({ 'ParameterKey': 'TransitGatewayId', 'ParameterValue': tg_id })

        evpc_stack_id = create_stack('egress-vpc.yml', evpc_inputs)
    except (ClientError, WaiterError) as e:
        print_error(e.response['Error']['Message'])
        exit(1)
    except json.JSONDecodeError as e:
        print_error('Invalid JSON in --stack-inputs.')
        exit(1)
    except KeyError as e:
        missing_key = e.args[0]
        print_error('Missing key \'{0}\' in --stack-inputs.'.format(missing_key))
        exit(1)
    except:
        print_error('An unexpected error occurred')
        traceback.print_exc()
        exit(1)
    else:
        print_success()

    return tg_stack_id, evpc_stack_id

def post_cfn(tg_stack_id, evpc_stack_id):
    tg_stack = _cfn_resource.Stack(tg_stack_id) # pylint: disable=no-member
    evpc_stack = _cfn_resource.Stack(evpc_stack_id) # pylint: disable=no-member

    try:
        tg_stack_properties = transform_aws_to_dict(tg_stack.outputs)
        evpc_stack_properties = transform_aws_to_dict(evpc_stack.outputs)
    except ClientError as e:
        print_error(e.response['Error']['Message'])
        exit(1)
    except:
        print_error('An unexpected error occurred')
        traceback.print_exc()
        exit(1)

    # Pull out specific values from stack outputs
    tg_id = tg_stack_properties['TransitGateway']
    ingress_rt_id = tg_stack_properties['IngressTransitGatewayRouteTable']
    egress_rt_id = tg_stack_properties['EgressTransitGatewayRouteTable']
    tg_vpc_attachment = evpc_stack_properties['TransitGatewayAttachment']

    # Pick out the properties for each gateway from the Transit Gateway
    # stack properties
    gateway_properties = extract_gateway_props(tg_stack_properties)
    map_attachments_to_vpns(gateway_properties, tg_id)

    # Use the gateway properties to tag and associate VPN attachments
    print('Tagging VPN attachments... ', end='')
    try:
        tag_vpn_attachments(gateway_properties)
    except ClientError as e:
        print_error(e.response['Error']['Message'])
        exit(1)
    except:
        print_error('An unknown error occurred')
        exit(1)
    else:
        print_success()

    print('Associating VPN attachments with the Egress Transit Gateway route table... ', end='')
    try:
        associate_vpn_attachments(gateway_properties, egress_rt_id)
    except ClientError as e:
        print_error(e.response['Error']['Message'])
        exit(1)
    except:
        print_error('An unknown error occurred')
        exit(1)
    else:
        print_success()

    print('Associating Egress VPC attachment with Ingress Transit Gateway route table... ', end='')
    try:
        _ec2.associate_transit_gateway_route_table(
            TransitGatewayRouteTableId=ingress_rt_id,
            TransitGatewayAttachmentId=tg_vpc_attachment
        )
    except ClientError as err:
        if err.response['Error']['Code'] != 'Resource.AlreadyAssociated':
            print_error(err.response['Error']['Message'])
            exit(1)
    else:
        print_success()

    print('Done.')

def create_stack_skeleton():
    skeleton = {
        'StackName': '',
        'Parameters': [
            {
                'ParameterKey': '',
                'ParameterValue': ''
            }
        ]
    }

    return json.dumps(skeleton, indent=2)

def create_routes_skeleton():
    skeleton = {
        'vpn-0123456abcde': [],
        'vpn-1234567bcdef': [],
        'vpn-2345678cdefg': []
    }

    return json.dumps(skeleton, indent=2)

def create_routes_for_gateways(tg_stack_id, evpc_stack_id, route_input_json):
    """Create routes in the Transit Gateway and the Egress VPC for each VPN
    connection

    Arguments:
        tg_stack_id {string} -- The ID of the Transit Gateway stack
        evpc_stack_id {string} -- The ID of the Egress VPC stack
        route_inputs {dict} -- A dict mapping VPN connections to CIDR ranges
    """
    tg_stack = _cfn_resource.Stack(tg_stack_id)     # pylint: disable=no-member
    evpc_stack = _cfn_resource.Stack(evpc_stack_id) # pylint: disable=no-member

    try:
        tg_stack_outputs = transform_aws_to_dict(tg_stack.outputs)
        evpc_stack_outputs = transform_aws_to_dict(evpc_stack.outputs)
    except ClientError as e:
        print_error(e.response['Error']['Message'])
        exit(1)
    except:
        print_error('An unexpected error occurred')
        traceback.print_exc()
        exit(1)

    # Pull out specific values from stack outputs
    tg_id = tg_stack_outputs['TransitGateway']
    egress_tg_rt_id = tg_stack_outputs['EgressTransitGatewayRouteTable']
    ingress_tg_rt_id = tg_stack_outputs['IngressTransitGatewayRouteTable']
    tg_vpc_attachment = evpc_stack_outputs['TransitGatewayAttachment']
    evpc_public_rt = evpc_stack_outputs['PublicSubnetRouteTable']
    evpc_private_rt = evpc_stack_outputs['PrivateSubnetRouteTable']

    # Create the default 0.0.0.0/0 route in the Ingress Transit Gateway
    # route table
    print('Creating default route to Egress VPC in Egress Transit Gateway route table... ', end='')
    try:
        _ec2.create_transit_gateway_route(
            DestinationCidrBlock='0.0.0.0/0',
            TransitGatewayRouteTableId=egress_tg_rt_id,
            TransitGatewayAttachmentId=tg_vpc_attachment,
            Blackhole=False
        )
    except ClientError as e:
        if e.response['Error']['Code'] != 'RouteAlreadyExists':
            print_error(e.response['Error']['Message'])
            exit(1)

    print_success()

    print('Creating return routes to VPN connections in the Ingress Transit Gateway route table... ', end='')
    try:
        route_inputs = json.JSONDecoder().decode(route_input_json)
    except json.JSONDecodeError as e:
        print_error('Invalid JSON in --route-inputs.')
        exit(1)
    else:
        print('')

    # Match the VPN connection ID keys provided to the user to the IDs
    # returned by describing the Transit Gateway attachments.
    tg_attachments = _ec2.describe_transit_gateway_attachments(
        Filters=[{'Name': 'transit-gateway-id', 'Values': [tg_id]}]
    )['TransitGatewayAttachments']

    keys_from_describe = set([
        item['ResourceId']
        for item in tg_attachments
        if item['ResourceType'] == 'vpn'
    ])
    user_keys = set(route_inputs.keys())
    matching_keys = user_keys & keys_from_describe

    # Create routes for each VPN in the Ingress Transit Gateway route table
    for key in user_keys:
        for item in tg_attachments:
            if item['ResourceId'] == key:
                attachment = item['TransitGatewayAttachmentId']

        for cidr in route_inputs[key]:
            print('\tCreating route {0} to {1}... '.format(cidr, color(key, fg='magenta')), end='')
            if key in matching_keys:
                try:
                    _ec2.create_transit_gateway_route(
                        DestinationCidrBlock=cidr,
                        TransitGatewayRouteTableId=ingress_tg_rt_id,
                        TransitGatewayAttachmentId=attachment,
                        Blackhole=False
                    )
                except ClientError as e:
                    if e.response['Error']['Code'] != 'RouteAlreadyExists':
                        print_error(e.response['Error']['Message'])
                        exit(1)

                print_success()
            else:
                print(color('skipped!', fg='cyan') + ' The VPN connection does not exist.')

    # Create routes for each gateway in the Egress VPC public route table
    print('Creating return routes to the Transit Gateway in the Egress VPC public route table... ')
    for key in user_keys:
        for cidr in route_inputs[key]:
            print('\tCreating route {0} to {1}... '.format(cidr, color(tg_id, fg='magenta')), end='')
            if key in matching_keys:
                try:
                    _ec2.create_route(
                        DestinationCidrBlock=cidr,
                        RouteTableId=evpc_public_rt,
                        TransitGatewayId=tg_id
                    )
                except ClientError as e:
                    if e.response['Error']['Code'] != 'RouteAlreadyExists':
                        print_error(e.response['Error']['Message'])
                        exit(1)

                print_success()
            else:
                print(color('skipped!', fg='cyan') + ' The VPN connection does not exist.')

    # Create routes for each gateway in the Egress VPC private route table
    print('Creating return routes to the Transit Gateway in the Egress VPC private route table... ')
    for key in user_keys:
        for cidr in route_inputs[key]:
            print('\tCreating route {0} to {1}... '.format(cidr, color(tg_id, fg='magenta')), end='')
            if key in matching_keys:
                try:
                    _ec2.create_route(
                        DestinationCidrBlock=cidr,
                        RouteTableId=evpc_private_rt,
                        TransitGatewayId=tg_id
                    )
                except ClientError as e:
                    if e.response['Error']['Code'] != 'RouteAlreadyExists':
                        print_error(e.response['Error']['Message'])
                        exit(1)

                print_success()
            else:
                print(color('skipped!', fg='cyan') + ' The VPN connection does not exist.')

    print('Done.')

def delete_transit_gateway_routes(tg_rt_id):
    """Deletes all static routes from a Transit Gateway route table

    Arguments:
        tg_rt_id {string} -- The ID of a Transit Gateway route table
    """
    routes = _ec2.search_transit_gateway_routes(
        TransitGatewayRouteTableId=tg_rt_id,
        Filters=[{'Name': 'type', 'Values': ['static']}]
    )['Routes']

    for route in routes:
        cidr = route['DestinationCidrBlock']
        print('\tDeleting route {0}... '.format(color(cidr, fg='magenta')), end='')
        _ec2.delete_transit_gateway_route(
            TransitGatewayRouteTableId=tg_rt_id,
            DestinationCidrBlock=cidr
        )
        print_success()

def delete_vpc_routes(tg_id, vpc_rt_id):
    """Deletes all Transit Gateway routes from a VPC subnet route table

    Arguments:
        tg_id {string} -- The ID of a Transit Gateway
        vpc_rt_id {string} -- The ID of a VPC subnet route table
    """
    routes = _ec2.describe_route_tables(
        RouteTableIds=[vpc_rt_id]
    )['RouteTables'][0]['Routes']

    tg_routes = filter(
        lambda item: 'TransitGatewayId' in item and item['TransitGatewayId'] == tg_id,
        routes
    )

    for route in tg_routes:
        cidr = route['DestinationCidrBlock']
        print('\tDeleting route {0}... '.format(color(cidr, fg='magenta')), end='')
        _ec2.delete_route(
            RouteTableId=vpc_rt_id,
            DestinationCidrBlock=cidr
        )
        print_success()

def delete_all_routes(tg_stack_id, evpc_stack_id):
    """Delete all routes from the Transit Gateway route tables and the
    Transit Gateway routes from the Egress VPC public subnet

    Arguments:
        tg_stack_id {string} -- The ID of the Transit Gateway stack
        evpc_stack_id {string} -- The ID of the Egress VPC stack
    """
    tg_stack = _cfn_resource.Stack(tg_stack_id)     # pylint: disable=no-member
    evpc_stack = _cfn_resource.Stack(evpc_stack_id) # pylint: disable=no-member

    try:
        tg_stack_outputs = transform_aws_to_dict(tg_stack.outputs)
        evpc_stack_outputs = transform_aws_to_dict(evpc_stack.outputs)
    except ClientError as e:
        print_error(e.response['Error']['Message'])
        exit(1)
    except:
        print_error('An unexpected error occurred')
        traceback.print_exc()
        exit(1)

    tg_id = tg_stack_outputs['TransitGateway']
    ingress_tg_rt_id = tg_stack_outputs['IngressTransitGatewayRouteTable']
    egress_tg_rt_id = tg_stack_outputs['EgressTransitGatewayRouteTable']
    evpc_public_rt = evpc_stack_outputs['PublicSubnetRouteTable']
    evpc_private_rt = evpc_stack_outputs['PrivateSubnetRouteTable']

    # Delete all routes from the Ingress and Egress Transit Gateway route tables
    print('Deleting routes in the Ingress Transit Gateway route table...')
    try:
        delete_transit_gateway_routes(ingress_tg_rt_id)
    except ClientError as e:
        print_error(e.response['Error']['Message'])
        exit(1)
    except:
        print_error('An unexpected error occurred')
        traceback.print_exc()
        exit(1)

    print('Deleting routes in the Egress Transit Gateway route table...')
    try:
        delete_transit_gateway_routes(egress_tg_rt_id)
    except ClientError as e:
        print_error(e.response['Error']['Message'])
        exit(1)
    except:
        print_error('An unexpected error occurred')
        traceback.print_exc()
        exit(1)

    # Delete routes associated with the Transit Gateway from the Egress VPC
    # public route table
    print('Deleting Transit Gateway routes in the Egress VPC public subnet... ')
    try:
        delete_vpc_routes(tg_id, evpc_public_rt)
    except ClientError as e:
        print_error(e.response['Error']['Message'])
        exit(1)
    except:
        print_error('An unexpected error occurred')
        traceback.print_exc()
        exit(1)

    # Delete routes associated with the Transit Gateway from the Egress VPC
    # private route table
    print('Deleting Transit Gateway routes in the Egress VPC private subnet... ')
    try:
        delete_vpc_routes(tg_id, evpc_private_rt)
    except ClientError as e:
        print_error(e.response['Error']['Message'])
        exit(1)
    except:
        print_error('An unexpected error occurred')
        traceback.print_exc()
        exit(1)

    print('Done.')


import sys
import requests
import json
from bs4 import BeautifulSoup

def main():

    # git link available in argv[1]
    git_link = sys.argv[1]

    try:
        response = requests.get(git_link)
        soup = BeautifulSoup(response.text, "html.parser")

        repo_name = soup.find(class_='repohead').find(
            'strong', attrs={"itemprop": "name"}).find('a')['href'][1:].split('/')

        star_count = soup.find(class_='pagehead-actions').find_all(
            'li')[1].find(class_="js-social-count")['aria-label'].split(' ')[0]

        response_obj = {
            'repository': repo_name,
            'star': star_count
        }
        sys.stdout.write(json.JSONEncoder().encode(response_obj))

    except:
        response_obj = {
            'error': "Invalid URL supplied"
        }
        sys.stderr.write(json.JSONEncoder().encode(response_obj))
        sys.exit(1)

if __name__ == "__main__":
    main()


# this project is licensed under the WTFPLv2, see COPYING.txt for details

"""Color scheme application

This plugin allows to load color scheme definition from a file and apply them to editor and syntax coloring.

Color scheme format
===================

Color scheme files are in INI format (as read by `configparser`). A scheme file sets style attributes for multiples
lexers and such descriptions can be applied to an editor.

Sections
--------

A color scheme file consist in one or more INI sections, each corresponding to a lexer.
The special section `*` applies to all lexers. When applying a color scheme to an editor, the `*` section is applied
first if it exists, then the section for the lexer of the editor is applied, if it exists and if the editor has
a lexer.

Attributes
----------

Within a section, there are multiple entries in the form: `item_kind.item_name.style_property = style_value`.

The `item_kind.item_name` part specifies for which items of the lexer it should apply, for example:
the "keyword" token, the "search" indicator, or simply the caret style.

The `style_property` indicates what attribute should be set, for example: the font size, the background color.

Finally, the `style_value` is the value the `style_property` should be set to, for example: a color, a size, a
font name.

Item types
----------

Editor properties
+++++++++++++++++

If `item_kind` is `base`, `item_name` can be:

* `text`: applies to normal text
* `selection`': applies to selected text
* `whitespace`
* `caret`: applies to caret, for `foreground`, or whole line under cursor, for `background` (if set visible)
* `hotspot`
* `matchedbrace`: for a brace under cursor, which has a corresponding matching brace
* `unmatchedbrace`: for a brace under cursor, which doesn't have a corresponding matching brace
* `margin`: for the margin column

The valid `style_property` are `foreground`, `background`, `font`, `points`, `bold`, `italic`, `underline`.

Tokens
++++++

If `item_kind` is `token`, `item_name` is the token name, which corresponds to one style in `QsciLexer`.

The token name `*` is special: it matches all tokens, and is applied first, so it can be overwritten by more
specific color scheme entries.

The valid `style_property` are `foreground`, `background`, `font`, `points`, `bold`, `italic`, `underline`.

Styles
++++++

If `item_kind` is `style`, `item_name` is the style name, as registered in :any:`eye.helpers.styles`.

The valid `style_property` are `foreground`, `background`, `font`, `points`, `bold`, `italic`, `underline` and
`eolfill`.

Indicators
++++++++++

If `item_kind` is `indicator`, `item_name` is the indicator name (see :any:`eye.widgets.editor.Editor.indicators`)

The valid `style_property` are: `foreground`, `background` and `style`.

Property types
--------------

* `foreground` (or `fg`, or `color`) specifies the text color
* `background` (or `bg`) specifies the background color

* `font` specifies the font family (font name)
* `points` specifies the font size in points

* `bold` specifies whether the font is bold
* `italic` specifies whether the font is italic
* `underline` specifies whether the font is underline

* `eolfill` specifies whether the background color applies to the rest of the line, after last character

* `style` specifies which indicator style should be used, e.g. `StraightBoxIndicator`

Module contents
===============

"""

from abc import ABC, abstractmethod
from configparser import SafeConfigParser
from logging import getLogger

from eye.colorutils import QColorAlpha
from eye.connector import category_objects, register_signal, register_setup, disabled
from eye.helpers._lexercolorgroups import get_id_and_aliases
from eye.helpers.styles import STYLES
from eye.lexers import styles_from_lexer

__all__ = (
	'set_enabled', 'use_scheme_file', 'add_scheme_file',
	'lexer_set_font_family', 'lexer_set_font_point_size',
	'apply_scheme_to_editor', 'apply_scheme_dict_to_editor',
	'apply_scheme_on_lexer_change', 'apply_scheme_on_create',
)

LOGGER = getLogger(__name__)

FG_ATTRS = frozenset(['fg', 'foreground', 'color'])
BG_ATTRS = frozenset(['bg', 'background'])

SCHEME = None

def new_scheme():
	parser = SafeConfigParser()
	parser.optionxform = str
	return parser

def fuzzy_equals(a, b):
	def norm(name):
		return name.lower().replace(' ', '_')
	return norm(a) == norm(b)

def get_style_by_desc(lexer, desc, fuzzy=False):
	for i in range(1 << lexer.styleBitsNeeded()):
		idesc = lexer.description(i)
		if idesc == desc or (fuzzy and fuzzy_equals(desc, idesc)):
			return i

def parse_bool(s):
	s = s.lower()
	if s in ['1', 'true', 'yes', 'y', 'on']:
		return True
	elif s in ['0', 'false', 'no', 'n', 'off']:
		return False
	raise ValueError('%r is not a boolean value' % s)

class UnsupportedModification(Exception):
	pass

class Modificator(ABC):
	def __init__(self, editor, key, strvalue):
		self.editor = editor
		self.key = key
		self.strvalue = strvalue

	@abstractmethod
	def set_font(self, attr, value, *args):
		...

	@abstractmethod
	def set_color(self, value, *args):
		...

	@abstractmethod
	def set_paper(self, value, *args):
		...

	def apply_generic(self, attr, *args):
		if attr == 'font':
			self.set_font('Family', self.strvalue, *args)
		elif attr == 'points':
			self.set_font('PointSizeF', float(self.strvalue), *args)
		elif attr == 'bold':
			self.set_font('Bold', parse_bool(self.strvalue), *args)
		elif attr == 'italic':
			self.set_font('Italic', parse_bool(self.strvalue), *args)
		elif attr == 'underline':
			self.set_font('Underline', parse_bool(self.strvalue), *args)
		elif attr in FG_ATTRS:
			self.set_color(QColorAlpha(self.strvalue), *args)
		elif attr in BG_ATTRS:
			self.set_paper(QColorAlpha(self.strvalue), *args)
		else:
			raise UnsupportedModification()

class LexerModificator(Modificator):
	def apply(self):
		tokenname, attr = self.key.split('.')

		lexer = self.editor.lexer()
		if not lexer:
			return

		if tokenname == '*':
			ids = styles_from_lexer(lexer).values()
		elif tokenname == '_default':
			ids = [self.editor.STYLE_DEFAULT]
		else:
			ids = get_id_and_aliases(lexer, tokenname)

		for id in ids:
			self.apply_one(id, attr)

	def apply_one(self, style_id, attr):
		lexer = self.editor.lexer()

		self.apply_generic(attr, lexer, style_id)

	def apply_generic(self, attr, lexer, style_id):
		if attr == 'eolfill':
			lexer.setEolFill(parse_bool(self.strvalue))
		else:
			super().apply_generic(attr, lexer, style_id)

	def set_color(self, qc, lexer, style_id):
		lexer.setColor(QColorAlpha(self.strvalue), style_id)

	def set_paper(self, qc, lexer, style_id):
		lexer.setPaper(QColorAlpha(self.strvalue), style_id)

	def set_font(self, font_attr, value, lexer, style_id):
		font = lexer.font(style_id)
		font_attr = 'set%s' % font_attr
		getattr(font, font_attr)(value)
		lexer.setFont(font, style_id)

class EditorModificator(Modificator):
	def apply(self):
		element, attr = self.key.split('.')
		self.apply_generic(attr, element)

	def apply_caret(self, attr, strvalue):
		if attr in FG_ATTRS:
			qc = QColorAlpha(strvalue)
			self.editor.setCaretForegroundColor(qc)
		elif attr in BG_ATTRS:
			qc = QColorAlpha(strvalue)
			self.editor.setCaretLineBackgroundColor(qc)
		else:
			raise UnsupportedModification('only elements in %s are supported for caret' % (FG_ATTRS + BG_ATTRS))

	def set_color(self, qc, element):
		attrs = {
			'text': 'setColor',
			'selection': 'setSelectionForegroundColor',
			'whitespace': 'setWhitespaceForegroundColor',
			'caret': 'setCaretForegroundColor',
			'hotspot': 'setHotspotForegroundColor',
			'matchedbrace': 'setMatchedBraceForegroundColor',
			'unmatchedbrace': 'setUnmatchedBraceForegroundColor',
			'margin': 'setMarginsForegroundColor',
		}

		getattr(self.editor, attrs[element])(qc)

	def set_paper(self, qc, element):
		attrs = {
			'text': 'setPaper',
			'selection': 'setSelectionBackgroundColor',
			'whitespace': 'setWhitespaceBackgroundColor',
			'caret': 'setCaretLineBackgroundColor',
			'hotspot': 'setHotspotBackgroundColor',
			'matchedbrace': 'setMatchedBraceBackgroundColor',
			'unmatchedbrace': 'setUnmatchedBraceBackgroundColor',
			'margin': 'setMarginsBackgroundColor',
		}

		getattr(self.editor, attrs[element])(qc)

	def set_font(self, font_attr, value, element):
		if element != 'text':
			raise UnsupportedModification('only "text" is supported')
		# margin.* lacks a Editor.marginsFont()

		font = self.editor.font()
		font_attr = 'set%s' % font_attr
		getattr(font, font_attr)(value)
		self.editor.setFont(font)

class IndicatorModificator(Modificator):
	def apply(self):
		name, attr = self.key.split('.')

		indicator = self.editor.indicators.get(name)
		if not indicator:
			indicator = self.editor.create_indicator(name, self.editor.PlainIndicator)

		self.apply_generic(attr, indicator)

	def apply_generic(self, attr, indicator):
		if attr == 'style':
			indicator.set_style(getattr(self.editor, self.strvalue))
		else:
			super().apply_generic(attr, indicator)

	def set_color(self, qc, indicator):
		indicator.set_color(qc)

	def set_paper(self, qc, indicator):
		indicator.setOutlineColor(qc)

	def set_font(self, *args):
		raise UnsupportedModification('font cannot be set for indicators')

class StyleModificator(Modificator):
	def apply(self):
		name, attr = self.key.split('.')

		self.apply_generic(attr, STYLES[name])

	def apply_generic(self, attr, style):
		if attr == 'eolfill':
			style.setEolFill(parse_bool(self.strvalue))
		else:
			super().apply_generic(attr, style)

	def set_font(self, font_attr, value, style):
		font = style.font()
		font_attr = 'set%s' % font_attr
		getattr(font, font_attr)(value)
		style.setFont(font)

	def set_color(self, qc, style):
		style.set_color(qc)

	def set_paper(self, qc, style):
		style.set_paper(qc)

def get_modificator(name):
	modificators = {
		'token': LexerModificator,
		'indicator': IndicatorModificator,
		'base': EditorModificator,
		'style': StyleModificator,
	}

	return modificators.get(name)

def apply_scheme_dict_to_editor(dct, editor):
	for key in sorted(dct):
		value = dct[key]
		try:
			styletype, subkey = key.split('.', 1)
		except ValueError:
			LOGGER.info('ignoring malformed style key %r', key)
			continue

		modificator_type = get_modificator(styletype)
		if modificator_type is None:
			LOGGER.info('ignoring unknown style type %r', styletype)
			continue

		mod = modificator_type(editor, subkey, value)
		try:
			mod.apply()
		except UnsupportedModification as exc:
			LOGGER.warning('%s is not supported: %s', key, exc)
			continue

		LOGGER.debug('applied %r=%r to %r', key, value, editor)

def apply_scheme_to_editor(parser, editor):
	lexer = editor.lexer()

	lexer_name = lexer.language() if lexer else 'None'
	for section in ('*', lexer_name):
		if parser.has_section(section):
			LOGGER.debug('using section %r for file %r', section, editor.path)

			dict_scheme = dict(parser.items(section))
			apply_scheme_dict_to_editor(dict_scheme, editor)

def use_scheme_file(path, apply_to_all=True):
	"""Use a color scheme file

	Reset current color scheme and load a new scheme file.

	:param path: color scheme file
	:param applyToAll: if True, apply to existing editor widgets
	"""
	global SCHEME

	if path is None:
		SCHEME = None
		LOGGER.info('unsetting scheme file')
		return

	SCHEME = None
	add_scheme_file(path, apply_to_all)

def add_scheme_file(path, apply_to_all=True):
	"""Load a color scheme file

	Unlike :any:`use_scheme_file`, it does not reset the current color scheme but adds new definitions.
	If a previous scheme file had some definitions which are redefined in the new scheme, they are replaced by the
	new one.

	:param path: color scheme file
	:param apply_to_all: if True, apply to existing editor widgets
	"""
	global SCHEME

	if SCHEME is None:
		LOGGER.info('starting with empty scheme')
		SCHEME = new_scheme()

	LOGGER.info('adding scheme %r', path)
	SCHEME.read([path])

	if apply_to_all:
		for ed in category_objects('editor'):
			apply_scheme_to_editor(SCHEME, ed)

@register_setup('editor')
@disabled
def apply_scheme_on_create(editor):
	if SCHEME:
		apply_scheme_to_editor(SCHEME, editor)

@register_signal('editor', 'lexer_changed')
@disabled
def apply_scheme_on_lexer_change(editor, lexer):
	if SCHEME:
		apply_scheme_to_editor(SCHEME, editor)

def set_enabled(enabled=True):
	"""Enabled/disable automatic color scheme application to editors"""
	apply_scheme_on_create.enabled = enabled
	apply_scheme_on_lexer_change.enabled = enabled

def _lexer_set_font(lexer, cb, style):
	if style >= 0:
		font = lexer.font(style)
		cb(font)
		lexer.setFont(font, style)
		return

	for i in range(1 << lexer.styleBitsNeeded()):
		desc = lexer.description(i)
		if desc:
			_lexer_set_font(lexer, cb, i)

def lexer_set_font_family(lexer, family, style=-1):
	"""Set the font family of a lexer

	Set just the font family for `style` of `lexer`.
	Like :any:`QsciLexer.setFont`, but only change the font family, not font size or weight.

	:param lexer: the lexer to change
	:type lexer: QsciLexer
	:param family: font family to use
	:param style: if negative, modifies all styles of `lexer`
	"""
	def cb(font):
		font.setFamily(family)

	_lexer_set_font(lexer, cb, style)

def lexer_set_font_point_size(lexer, size, style=-1):
	"""Set the font size of a lexer

	Set just the font size for `style` of `lexer`.
	Like :any:`QsciLexer.setFont`, but only change the font size, not font family or weight.

	:param lexer: the lexer to change
	:type lexer: QsciLexer
	:param size: font size to use (in points)
	:param style: if negative, modifies all styles of `lexer`
	"""
	def cb(font):
		font.setPointSize(size)

	_lexer_set_font(lexer, cb, style)


"""
Script execution for script fragments in content.
"""

from __future__ import absolute_import, division, print_function, unicode_literals

import textwrap

import six

def run_script(pycode):
    """Run the Python in `pycode`, and return a dict of the resulting globals."""
    # Fix up the whitespace in pycode.
    if pycode[0] == "\n":
        pycode = pycode[1:]
    pycode.rstrip()
    pycode = textwrap.dedent(pycode)

    # execute it.
    globs = {}
    six.exec_(pycode, globs, globs)  # pylint: disable=W0122

    return globs


from typing import List

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        # okay.
        # binary search is all about looking in smaller version of the
        # original list by slicing the list or moving list index pointer
        # around. this particular one is set up for solution by recursion.

        # base case
        # not in the list
        if len(nums) == 0:
            return -1

        middle = len(nums) // 2
        index = 0

        # base case
        # target is the only one in the list
        if len(nums) == 1 and nums[0] == target:
            return middle

        if target < nums[middle]:
            index -= middle
            self.search(nums[0:middle], target)
        elif target == nums[middle]:
            return middle
        elif target > nums[middle]:
            index += middle
            self.search(nums[middle:], target)

        return middle

sol = Solution()
list_ = [-1,0,3,5,9,12]
print(sol.search(list_, -1))
print(sol.search(list_, 0))
print(sol.search(list_, 3))
print(sol.search(list_, 5))
print(sol.search(list_, 9))


# -*- coding: utf-8 -*-
"""
Created on Tue Apr 26 22:33:38 2022

@author: salva
"""

import pandas as pd
import numpy as np
import pyphi_batch as phibatch
import matplotlib.pyplot as plt
import pyphi_plots as pp

bdata        = pd.read_excel('Batch Dryer Case Study.xlsx',sheet_name='Trajectories')
cqa          = pd.read_excel('Batch Dryer Case Study.xlsx',sheet_name='ProductQuality')
char         = pd.read_excel('Batch Dryer Case Study.xlsx',sheet_name='ProcessCharacteristics')
initial_chem = pd.read_excel('Batch Dryer Case Study.xlsx',sheet_name='InitialChemistry')
cat          = pd.read_excel('Batch Dryer Case Study.xlsx',sheet_name='classifiers')

phibatch.plot_var_all_batches(bdata)
#%%
samples={'Deagglomerate':20,'Heat':30,'Cooldown':40};
bdata_aligned_phase=phibatch.phase_simple_align(bdata,samples)
phibatch.plot_var_all_batches(bdata_aligned_phase,
                  plot_title='simple alignment /phase',
                  phase_samples=samples)
#%%
mpca_obj=phibatch.mpca(bdata_aligned_phase,3,phase_samples=samples)
pp.score_scatter(mpca_obj,[1,2],CLASSID=cat,colorby='Quality 2')

#%% Show a forecast
mon_data =phibatch.monitor(mpca_obj, bdata_aligned_phase,which_batch='Batch 5')

#Show forecast of x
var='Dryer Temp'
point_in_time=20
batch2forecast='Batch 5'
forecast=mon_data['forecast']
mdata=bdata_aligned_phase[bdata_aligned_phase['Batch number']==batch2forecast]

plt.figure()
f=forecast[point_in_time]
plt.plot(mdata[var][:point_in_time],'o',label='Measured')
aux=[np.nan]*point_in_time
aux.extend(f[var].values[point_in_time:].tolist())
plt.plot(np.array(aux),label='Forecast')
plt.plot(mdata[var][point_in_time:],'o',label='Known trajectory',alpha=0.3)
plt.xlabel('sample')
plt.ylabel(var)
plt.legend()
plt.title('Forecast for '+var+' at sample '+str(point_in_time)+' for '+batch2forecast )

#%% PLS with trajectories
mpls_obj_t=phibatch.mpls(bdata_aligned_phase,cqa,5,phase_samples=samples)
pp.r2pv(mpls_obj_t)
pp.score_scatter(mpls_obj_t, [1,2],CLASSID=cat,colorby='Quality')

#%% PLS with initial chemistry
mpls_obj_i=phibatch.mpls(bdata_aligned_phase,cqa,5,zinit=initial_chem,
                       phase_samples=samples)
pp.score_scatter(mpls_obj_i, [1,2],CLASSID=cat,colorby='Quality')

#%% PLS with initial chemistry MB

mpls_obj_i_mb=phibatch.mpls(bdata_aligned_phase,cqa,5,zinit=initial_chem,
                      phase_samples=samples,mb_each_var='True')
pp.score_scatter(mpls_obj_i_mb, [1,2],CLASSID=cat,colorby='Quality')
pp.mb_weights(mpls_obj_i_mb)
phibatch.loadings_abs_integral(mpls_obj_i_mb)
phibatch.loadings(mpls_obj_i_mb,1)
#What process reccomendation would we do to minimize off-spec product ?

plt.show()

Process Analytics Course Materials 2023/advanced_datasets/1 Batch Process Analysis/FMC Case Study/batch_dryer_case_study.py


import click
from cli import cli
from cli.deploy.actions import actions

help_txt = """
Usage: project deploy [OPTIONS] SUBCOMMAND

Options:
  --help Show this message and exit.

Subcommands:
  frontend   Build and deploy frontend assets
  models     Deploy built models
  services   Deploy backend services
  database   Deploy database migrations and changes
"""

@cli.command()
@click.argument('subcommand')
def deploy(subcommand):
    """
    Deployment options
    """
    sc = subcommand.lower()

    if sc in actions: actions[sc]()
    else: print(help_txt)


import pandas as pd
import re
from time import sleep
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import argparse

# This script requires the following packages:
# pandas, openpyxl

parser = argparse.ArgumentParser()
parser.add_argument('-m', metavar='Max Age', action='store', default=7, type=int, help='Skips rows older than this (Default 7)')
parser.add_argument('-r', dest='reporter', type=str, metavar='Reporter', default='rsalazar15', action='store', help='Jira reporter')
parser.add_argument('-a', dest='assignee', type=str, metavar='Assignee', default='rsalazar15', action='store', help='Jira assignee')
parser.add_argument('-d', dest='delay', type=int, metavar='Delay', default=2, action='store', help='Delay between emails in seconds (Default 2)')
group = parser.add_mutually_exclusive_group()
group.add_argument('-v', '--verbose', action='store_true')
group.add_argument('-q', '--quiet', action='store_true')
parser.add_argument('files', nargs='*', type=str, help="List of Execl files")

def fix_name(name):
    temp = name.split(", ", 1)
    return f"{temp[1]} {temp[0]}"

def send_mail(arg):
    ois_email = ""
    jira_email = ""
    service_desk_email = ""

    for n in range(0, len(arg.files)):
        if arg.verbose:
            print("Reading from file %i" % n)
        # Read data from the spreadsheet
        in_file = arg.files[n]
        try:
            data = pd.read_excel(in_file)
        except FileNotFoundError:
            print("%s not Found" % arg.files[n])
            exit(1)

        # Iterate through each row of the spreadsheet
        size = data.shape[0]
        for row in range(0, size):
            # skip rows older than 7 days by default
            if int(data.iloc[row, 10]) > arg.m:
                if arg.verbose:
                    print("Skipping row %i" % row)
                continue

            # Check if student or Faculty
            if data.iloc[row, 5] == "Student":
                template_file = "Student.html"
            else:
                template_file = "Faculty.html"

            # Determine Course Title and acronym
            course_title = data.iloc[row, 8]
            if course_title == "Information Security Awareness":
                course_acronym = "ISA"
            elif course_title == "FERPA":
                course_acronym = "FERPA"
            elif course_title == "Digital Accessibility Awareness - TAMUCC":
                course_title = "Digital Accessibility Awareness"
                course_acronym = "DAA"
            else:
                raise Exception("Unknown Training type on row %s" % row)

            # Read the template into the email body
            template = open(template_file)
            name = fix_name(data.iloc[row, 0])
            body = template.read()
            template.close()

            # Replace placeholders with data from spreadsheet
            body = re.sub("NAME", name, body)
            body = re.sub("REPTR", arg.reporter, body)
            body = re.sub("ASIGN", arg.assignee, body)
            body = re.sub("UID", str(data.iloc[row, 1]), body)
            body = re.sub("ESD", str(data.iloc[row, 2]), body)
            body = re.sub("LPD", str(data.iloc[row, 3]), body)
            body = re.sub("TCD", str(data.iloc[row, 4]), body)
            body = re.sub("SSFD", str(data.iloc[row, 5]), body)
            body = re.sub("EMAIL", str(data.iloc[row, 6]), body)
            body = re.sub("PCID", str(data.iloc[row, 7]), body)
            body = re.sub("CT", str(data.iloc[row, 8]), body)
            body = re.sub("TDD", str(data.iloc[row, 9]), body)
            body = re.sub("DPD", str(data.iloc[row, 10]), body)
            body = re.sub("PDR", str(data.iloc[row, 11]), body)
            body = re.sub("DESC", str(data.iloc[row, 12]), body)
            body = re.sub("ADLOC", str(data.iloc[row, 13]), body)
            body = re.sub("SLOC", str(data.iloc[row, 14]), body)
            body = re.sub("SUPER", str(data.iloc[row, 15]), body)
            body = re.sub("SMAIL", str(data.iloc[row, 16]), body)
            body = re.sub("CRSTITLE", course_title, body)
            body = re.sub("CRSA", course_acronym, body)

            # Prepare Message
            msg = MIMEMultipart()
            body = MIMEText(body, 'html')
            msg.attach(body)
            msg['From'] = ois_email
            msg['Subject'] = course_title + " training for " + name
            msg['To'] = str(data.iloc[row, 6])
            msg['CC'] = "%s; %s" % (str(data.iloc[row, 16]), service_desk_email)
            msg['BCC'] = ois_email

            # Send Email
            s = smtplib.SMTP('smtp.tamucc.edu')
            s.send_message(msg)
            s.quit()

            if not arg.quiet:
                print("Sent %s email to: %s" % (course_acronym, name))

            # Avoid Spam Filter
            sleep(arg.delay)

    print("DONE")

if __name__ == '__main__':
    args = parser.parse_args()
    if args.files:
        send_mail(args)
    else:
        print("No files specified.")


from PIL import Image
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
import numpy as np

# Load the digits dataset
digits = load_digits()

# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target, test_size=0.2)

# Train an SVM classifier on the training data
clf = SVC()
clf.fit(X_train, y_train)

# Load the image file
img = Image.open("C:/Users/elvis/OneDrive/Desktop/Handwriting/images/handwritten_digit.png").convert("L")

# Resize the image to 8x8 pixels
img = img.resize((8, 8))

# Convert the image to a numpy array
image = np.array(img)

# Flatten the image into a 64-element array
image = image.reshape((64,))

# Predict the digit using the classifier
prediction = clf.predict([image])[0]

# Print the predicted digit
print("Predicted digit:", prediction)

# Test the classifier on the testing data and print the accuracy
accuracy = clf.score(X_test, y_test)
print("Accuracy:", accuracy)


import argparse
import os
from os.path import join as opj
import time
from datetime import datetime
import time

import cv2
import numpy as np

parser = argparse.ArgumentParser(description="Streaming webcam",
                                 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-d", "--device", type=int, default=0, help="device id")

font = cv2.FONT_HERSHEY_SIMPLEX

def create_folder():
    current_datetime = datetime.now()
    current_time = current_datetime.strftime("Date_%d_%m_%Y_Time_%H_%M_%S")
    os.makedirs(current_time, exist_ok=True)
    return current_time

def main(device):
    folder_path = create_folder()
    new_frametime = 0
    prev_frametime = 0
    vid = cv2.VideoCapture(device)
    frame_count = 0
    while(True):
        # Capture the video frame
        # by frame
        ret, ori_image = vid.read()
        frame_count += 1
        # Display the resulting frame
        if not ret:
            continue
        frame = ori_image.copy()
        new_frametime = time.time()
        frametime = new_frametime - prev_frametime
        fps = 'FPS: {}'.format(str(int(1/frametime)))
        prev_frametime = new_frametime
        frametime *= 1000
        delay_str = 'Delay: {}ms'.format(int(frametime))
        cv2.putText(
            frame, fps, (7, 30), font, 1, (0, 0, 139), 2, cv2.LINE_AA
        )
        cv2.putText(
            frame, delay_str, (200, 30), font, 1, (0, 0, 139), 2, cv2.LINE_AA
        )
        cv2.imshow('frame', frame)
        image_name = 'frame_{}.jpg'.format(frame_count)
        cv2.imwrite(opj(folder_path, image_name), ori_image)
        # the 'q' button is set as the
        # quitting button you may use any
        # desired button of your choice
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    # After the loop release the cap object
    vid.release()
    # Destroy all the windows
    cv2.destroyAllWindows()

if __name__ == '__main__':
    args = parser.parse_args()
    main(args.device)