from dataclasses import dataclass
from reportlab.lib.styles import ParagraphStyle

import datetime
from decimal import ROUND_HALF_UP, Decimal

from dateutil.parser import parse
from reportlab.lib import colors, fonts, pagesizes, styles
from reportlab.lib.units import inch
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import (
    BaseDocTemplate,
    SimpleDocTemplate,
    Paragraph,
    PageBreak,
    Spacer,
    PageTemplate,
    FrameBreak,
    Table,
    TableStyle,
    Frame,
)
from reportlab.lib.enums import TA_CENTER, TA_RIGHT
from reportlab.platypus.flowables import KeepTogether
import reportlab.rl_config

from xlsxwriter import Workbook
from xlsxwriter.utility import xl_col_to_name

class DataFormatter:
    def __init__(self, data=None, filename=None, user_id=None):
        self.filename = filename
        self.user_id = user_id
        self.username = self.get_username()
        self.title = "Report Title"
        self.sub_title = ""

        self.columns = []
        self.data = data if data else []

        self.sections = []

        self.scalar_heading_column = 1

    def get_username(self):
        """
        override this method to build your username from the passed user id
        """
        return self.user_id

    def to_pdf(
        self,
        headings=None,
        orientation="letter",
        top_margin=None,
        filename=None,
        default_tablestyle=None,
    ):
        if not headings:
            headings = []
            if self.title:
                headings.append(self.title)
            if self.sub_title:
                headings.append(self.sub_title)

        filename = (
            filename
            if filename
            else self.filename.replace("xlsx", "pdf")
            if self.filename
            else None
        )

        pr = PDFReport(
            data=self.data,
            sections=self.sections,
            headings=headings,
            orientation=orientation,
            scalar_heading_column=self.scalar_heading_column,
            filename=filename,
            username=self.username,
        )
        pr.columns = self.columns
        if top_margin:
            pr.top_margin = top_margin
        if default_tablestyle:
            pr.DEFAULT_TABLE_STYLE = default_tablestyle

        pr.build()

    def to_excel(
        self,
        filename=None,
        sheetname=None,
        format_table=None,
    ):

        filename = (
            filename
            if filename
            else self.filename.replace("pdf", "xlsx")
            if self.filename
            else None
        )

        xr = XLSXReport(
            data=self.data,
            sections=self.sections,
            sheetname=sheetname,
            filename=filename,
            format_table=format_table,
            username=self.username,
        )
        xr.columns = self.columns

        xr.build()

@dataclass
class ReportSection:
    header: str
    columns: list
    data: list
    pagebreak: bool = False
    tablestyle: list = None
    footer: str = None
    footer_style: ParagraphStyle = None
    format_table: bool = False
    include_column_headers: bool = True
    space_before = 0.25 * inch
    space_after = 0.25 * inch

@dataclass
class Column:
    name: str
    heading: str = None
    font_size: float = 10
    bold: bool = False
    justify: str = "LEFT"
    scalar: str = ""
    decimal_positions: int = None
    currency: bool = False
    excel_formula: str = None
    datatype: str = "str"
    wrap: bool = False
    width: int = 10
    max_width: int = None
    paragraph_style = None
    include_commas: bool = False

    def __post_init__(self):
        self.heading = (
            self.heading.upper()
            if self.heading
            else self.name.replace("_", " ").upper()
        )
        self.justify = self.justify.upper()
        self.scalar = self.scalar.upper()

    def set_width(self, text_length):
        if not self.max_width or text_length > self.max_width:
            self.max_width = text_length if text_length > 10 else 10

@dataclass
class ExcelFormula:
    formula: str

class XLSXReport:
    def __init__(
        self,
        data=None,
        sections=None,
        headings=None,
        sheetname=None,
        filename="xlsx_report.xlsx",
        format_table=False,
        username=None,
        row_column_formats=None,
    ):
        self.data = data
        self.sections = sections
        self.sheetname = sheetname
        self.headings = headings
        self.filename = filename
        self.username = username
        self.format_table = format_table
        self.row_column_formats = row_column_formats

        if not self.headings:
            self.headings = ["XLSX Report"]

        self.columns = []

        self.workbook = None

    def setup_sheet(self):
        pass

    def pre_build(self):
        pass

    def post_build(self):
        pass

    def build(self):
        self.setup_sheet()

        self.workbook = Workbook(self.filename)
        self.pre_build()

        if self.data and len(self.data) > 0:
            self.build_section(
                ReportSection(
                    header=self.sheetname,
                    columns=self.columns,
                    data=self.data,
                    format_table=self.format_table,
                )
            )
        else:
            for section in self.sections:
                self.build_section(section)

        self.post_build()

        self.workbook.close()

    def build_section(self, section):
        headings = [x.heading for x in section.columns]

        #  data, format_table=False, sheetname="Sheet1"
        if len(section.data) > 0:
            #  set default columns widths
            for column in section.columns:
                heading_length = len(column.heading) + 2
                if heading_length > column.width:
                    if column.max_width and heading_length >= column.max_width:
                        column.width = column.max_width
                    else:
                        if heading_length > column.width:
                            column.width = heading_length

            #  determine column widths
            for row in section.data:
                for column in section.columns:
                    if not column.excel_formula:
                        if isinstance(row, dict):
                            if column.name in row:
                                value = row[column.name]
                            else:
                                value = ""
                        else:
                            if column.name in row.__dict__:
                                value = row.__dict__[column.name]
                            else:
                                value = ""

                        if not isinstance(value, ExcelFormula):
                            value_length = len(str(value).strip())
                            if value_length > column.width:
                                if (
                                    column.max_width
                                    and value_length >= column.max_width
                                ):
                                    column.width = column.max_width
                                else:
                                    if value_length > column.width:
                                        column.width = value_length

                        if not column.max_width:
                            column.set_width(
                                max(len(x) for x in str(value).split("
"))
                            )

            ws = self.workbook.add_worksheet(
                name=section.header.replace("/", "-").replace(",", "")[:31]
                if section.header
                else "Sheet1"
            )

            if section.include_column_headers:
                #  write header row
                ws.write_row(
                    row=0,
                    col=0,
                    data=headings,
                )

            for i, row in enumerate(section.data):
                height_rows = 1
                for j, column in enumerate(section.columns):
                    if isinstance(row, dict):
                        if column.name in row:
                            value = row[column.name]
                        else:
                            value = ""
                    else:
                        if column.name in row.__dict__:
                            value = row.__dict__[column.name]
                        else:
                            value = ""

                    cell_format = None
                    if isinstance(value, ExcelFormula):
                        value = (
                            value.formula.replace("?column-1", xl_col_to_name(j - 1))
                            .replace("?column-2", xl_col_to_name(j - 2))
                            .replace("?column+2", xl_col_to_name(j + 2))
                            .replace("?column", xl_col_to_name(j))
                        )
                        #
                        # value = (
                        #     value.formula.replace("?column-1", xl_col_to_name(j - 1))
                        #         .replace("?column-2", xl_col_to_name(j - 2))
                        #         .replace("?column+2", xl_col_to_name(j + 2))
                        #         .replace("?column", xl_col_to_name(j))
                        # )
                        #
                    elif column.excel_formula:
                        value = (
                            column.excel_formula.replace("?row", str(i + 2))
                            .replace("?column-1", xl_col_to_name(j - 1))
                            .replace("?column-2", xl_col_to_name(j - 2))
                            .replace("?column+2", xl_col_to_name(j + 2))
                            .replace("?column", xl_col_to_name(j))
                        )
                    else:
                        #  handle br in row for line breaks
                        if isinstance(value, str):
                            value = value.strip("\n")
                            num_rows = len(value.split("
"))
                            if num_rows > 1:
                                value = value.replace("
", "\n")
                                if num_rows > height_rows:
                                    height_rows = num_rows

                        if section.tablestyle:
                            for fmt in section.tablestyle:
                                attr = fmt[0]
                                from_cell = fmt[1]
                                to_cell = fmt[2]
                                modifier = fmt[3]
                                if from_cell == to_cell:
                                    if attr.lower() == "background":
                                        if from_cell[0] == j and from_cell[1] == i + 1:
                                            if from_cell == to_cell:
                                                if attr.lower() == "background":
                                                    if (
                                                        from_cell[0] == j
                                                        and from_cell[1] == i + 1
                                                    ):
                                                        f = self._get_column_format(
                                                            section.columns[j]
                                                        )
                                                        f[
                                                            "bg_color"
                                                        ] = modifier.hexval().replace(
                                                            "0x", "#"
                                                        )
                                                        cell_format = (
                                                            self.workbook.add_format(f)
                                                        )
                    if cell_format:
                        ws.write(i + 1, j, value, cell_format)
                    else:
                        ws.write(i + 1, j, value)

                if height_rows > 1:
                    ws.set_row(i + 1, 15.001 * height_rows)

            last_column_letter = xl_col_to_name(len(section.columns) - 1)

            if section.format_table:
                columns = []
                for i, column in enumerate(section.columns):
                    if column.scalar and column.scalar.upper() == "TOTAL":
                        columns.append(
                            {"header": column.heading, "total_function": "sum"}
                        )
                    elif column.scalar and column.scalar.upper() == "AVG":
                        columns.append(
                            {"header": column.heading, "total_function": "average"}
                        )
                    else:
                        columns.append({"header": column.heading})

                ws.add_table(
                    "A1:%s%s" % (last_column_letter, len(section.data) + 3),
                    {
                        "style": "Table Style Medium 15",
                        "total_row": 1,
                        "columns": columns,
                    },
                )
            else:
                for i, column in enumerate(section.columns):
                    if column.scalar == "TOTAL":
                        ws.write(
                            len(section.data) + 2,
                            i,
                            f"=SUM({xl_col_to_name(i)}2:{xl_col_to_name(i)}{len(section.data)+1})",
                        )
                    elif column.scalar == "AVG":
                        pass

            for i, column in enumerate(section.columns):
                ws.set_column(
                    i,
                    i,
                    column.max_width
                    if column.max_width and column.max_width < column.width
                    else column.width + 5,
                    self.workbook.add_format(self._get_column_format(column)),
                )

    @staticmethod
    def _get_column_format(column):
        f = dict(valign="top")

        if column.datatype.lower() in ["int", "integer"]:
            f["num_format"] = "#,##0"
        elif column.decimal_positions and column.decimal_positions > 0:
            f["num_format"] = "#,##0.000000000000"[: column.decimal_positions + 6]

        if column.datatype == "date":
            f["num_format"] = "mm/dd/yyyy"
        if column.datatype == "datetime":
            f["num_format"] = "mm/dd/yyyy HH:MM AM/PM"

        if column.justify == "CENTER":
            f["align"] = "center"
        elif column.justify == "RIGHT":
            f["align"] = "right"
        else:
            f["align"] = "left"

        if column.currency:
            if not column.decimal_positions:
                column.decimal_positions = 2
            f["num_format"] = "$ #,##0.000000000000"[: column.decimal_positions + 8]
            f["align"] = "right"

        if column.wrap:
            f["text_wrap"] = True

        return f

FORMAT_INT = "{0:,.0f}"

class PDFReport:
    def __init__(
        self,
        data=None,
        sections=None,
        headings=None,
        orientation="letter",
        scalar_heading_column=1,
        filename="pdf_report.pdf",
        username=None,
        date_format="%m/%d/%Y",
        include_column_headers=True,  # used only if sections is None
    ):
        self.headings = headings
        self.orientation = orientation
        self.data = data
        self.scalar_heading_column = scalar_heading_column
        self.filename = filename
        self.username = username
        self.sections = sections
        self.include_column_headers = include_column_headers

        #  Report Setup
        reportlab.rl_config.warnOnMissingFontGlyphs = 0
        pdfmetrics.registerFont(TTFont("Arial", "arial.ttf"))
        pdfmetrics.registerFont(TTFont("Arial Bold", "arialbd.ttf"))
        pdfmetrics.registerFont(TTFont("Arial Bold Italic", "arialbi.ttf"))
        pdfmetrics.registerFont(TTFont("Arial Italic", "ariali.ttf"))

        fonts.addMapping("Arial", 0, 0, "Arial")
        fonts.addMapping("Arial", 0, 1, "Arial Italic")
        fonts.addMapping("Arial", 1, 0, "Arial Bold")
        fonts.addMapping("Arial", 1, 1, "Arial Bold Italic")

        self.base_font = "Arial"
        self.bold_font = "Arial Bold"
        self.italic_font = "Arial Italic"
        self.bold_italic_font = "Arial Bold Italic"

        self.PAGE_HEIGHT = pagesizes.letter[1]
        self.PAGE_WIDTH = pagesizes.letter[0]

        self.DETAIL_STYLE = TableStyle(
            [
                ("GRID", (0, 0), (-1, -1), 0.25, colors.gray),
                ("FONTNAME", (0, 0), (-1, -1), self.base_font),
                ("FONTSIZE", (0, 0), (-1, -1), 8),
                ("BACKGROUND", (0, 0), (-1, 0), colors.transparent),
                ("TEXTCOLOR", (0, 0), (-1, 0), colors.black),
                ("ALIGNMENT", (0, 0), (-1, 0), "CENTER"),  # Center all Headings
                (
                    "ALIGNMENT",
                    (0, 1),
                    (-1, -1),
                    "LEFT",
                ),  # Left justify all remaining
                ("VALIGN", (0, 1), (-1, -1), "TOP"),
                ("VALIGN", (0, 0), (-1, 0), "BOTTOM"),
            ]
        )

        self.DEFAULT_TABLE_STYLE = [
            ("GRID", (0, 0), (-1, -1), 0.25, colors.gray),
            ("FONTNAME", (0, 0), (-1, -1), self.base_font),
            ("FONTSIZE", (0, 0), (-1, -1), 8),
            ("BACKGROUND", (0, 0), (-1, 0), colors.transparent),
            ("TEXTCOLOR", (0, 0), (-1, 0), colors.black),
            ("ALIGNMENT", (0, 0), (-1, 0), "CENTER"),  # Center all Headings
            ("ALIGNMENT", (0, 1), (-1, -1), "LEFT"),  # Left justify all remaining
            ("VALIGN", (0, 1), (-1, -1), "TOP"),
            ("VALIGN", (0, 0), (-1, 0), "BOTTOM"),
        ]

        self.HEADER_STYLE = TableStyle(
            [
                # ("GRID", (0, 0), (-1, -1), 0.25, colors.gray),
                ("FONTNAME", (0, 0), (-1, -1), self.base_font),
                ("FONTSIZE", (0, 0), (-1, -1), 10),
                ("FONTSIZE", (1, 1), (1, -1), 12),
                ("FONTSIZE", (1, 0), (1, 0), 16),
                ("BACKGROUND", (0, 0), (-1, 0), colors.transparent),
                ("TEXTCOLOR", (0, 0), (-1, 0), colors.black),
                ("ALIGNMENT", (0, 0), (0, -1), "LEFT"),
                ("ALIGNMENT", (1, 0), (1, -1), "CENTER"),
                ("ALIGNMENT", (2, 0), (2, -1), "RIGHT"),
                # ("BOTTOMPADDING", (0, 0), (-1, 0), 0.1 * inch),
                ("VALIGN", (0, 1), (-1, -1), "MIDDLE"),
            ]
        )

        self.style = styles.getSampleStyleSheet()
        self.styleN = self.style["Normal"]
        self.styleH = self.style["Heading1"]
        self.styleH2 = self.style["Heading2"]
        self.style_center = styles.getSampleStyleSheet()
        self.style_center_n = self.style_center["Normal"]
        self.style_center_n.alignment = TA_CENTER
        self.style_center_h = self.style_center["Heading1"]
        self.style_center_h.alignment = TA_CENTER
        self.style_right = styles.getSampleStyleSheet()
        self.style_right_n = self.style_right["Normal"]
        self.style_right_n.alignment = TA_RIGHT
        self.style_right_h = self.style_right["Heading1"]
        self.style_center_h.alignment = TA_RIGHT
        self.style_bullet = styles.getSampleStyleSheet()
        self.style_bullet_n = self.style_bullet["Bullet"]
        self.style_bullet_n.bulletFontName = "Symbol"
        self.style_bullet_n.leftIndent = 10

        self.report_story = []
        self.header_story = []

        self.row_heights = []
        self.report_period = ""

        if not self.headings:
            self.headings = ["QLF PDF Report"]

        self.report_story = []
        self.columns = []
        self.doc = None

        if self.orientation.upper() == "LANDSCAPE":
            self.pagesize = pagesizes.landscape(pagesizes.letter)
        else:
            self.pagesize = pagesizes.letter

        self.top_margin = 1.25 * inch + (0.5 * len(self.headings) - 1) * inch
        self.bottom_margin = 0.5 * inch
        self.left_margin = 0.5 * inch
        self.right_margin = 0.5 * inch

        self.date_format = date_format

    def setup_document(self):
        self.PAGE_HEIGHT = self.pagesize[1]
        self.PAGE_WIDTH = self.pagesize[0]

        self.doc = SimpleDocTemplate(
            self.filename,
            pagesize=self.pagesize,
            topMargin=self.top_margin,
            bottomMargin=self.bottom_margin,
            leftMargin=self.left_margin,
            rightMargin=self.right_margin,
        )

    def first_page(self, canvas, doc):
        """
        handle layout of the first page of the report

        NOTE: override this method to put custom headers in place

        Args:
            canvas (_type_): _description_
            doc (_type_): _description_
        """
        canvas.saveState()

        #  Print the headings
        style = self.styleN
        style.fontSize = 10

        header_data = []
        for i, heading in enumerate(self.headings):
            column_3 = ""
            if i == 0 and self.username:
                column_3 = f"Printed by: {self.username}"
            elif i == len(self.headings) - 1:
                column_3 = (
                    f"Printed: {datetime.date.today().strftime(self.date_format)}"
                )
            header_data.append(
                [
                    "",
                    heading,
                    column_3,
                ]
            )

        col_width = (self.PAGE_WIDTH - 1.0 * inch) / 3
        header_col_widths = (col_width, col_width, col_width)

        header_frame = Frame(
            x1=0.5 * inch,
            y1=self.PAGE_HEIGHT - 1.5 * inch,
            width=self.PAGE_WIDTH - 1.0 * inch,
            height=1 * inch,
        )
        header_table = Table(header_data, header_col_widths)
        header_table.setStyle(self.HEADER_STYLE)
        self.header_story.append(header_table)
        header_frame.addFromList(self.header_story, canvas)

        #  Print the page number
        canvas.setFont("Arial", 8)
        canvas.drawRightString(
            self.PAGE_WIDTH - 0.5 * inch, 0.4 * inch, "Page %d" % doc.page
        )
        canvas.restoreState()

    def subsequent_pages(self, canvas, doc):
        self.first_page(canvas, doc)

    def pre_build(self):
        pass

    def post_build(self):
        pass

    def build(self):
        self.setup_document()

        self.pre_build()

        if self.data and len(self.data) > 0:
            self.build_section(
                ReportSection(
                    header="",
                    columns=self.columns,
                    data=self.data,
                    include_column_headers=self.include_column_headers,
                )
            )
        else:
            for section in self.sections:
                self.build_section(section)

        self.post_build()

        self.doc.build(
            self.report_story,
            onFirstPage=self.first_page,
            onLaterPages=self.subsequent_pages,
        )

    def build_section(self, section):
        if section.header and section.header != "":
            self.report_story.append(Paragraph(section.header, style=self.styleH2))
        else:
            self.report_story.append(Spacer(1, section.space_before))

        for column in section.columns:
            if column.excel_formula and len(column.excel_formula) > 0:
                section.columns.remove(column)

        for col_number, column in enumerate(section.columns):
            #  add alignments from column defaults
            if column.justify != "LEFT":
                self.DEFAULT_TABLE_STYLE.append(
                    ("ALIGNMENT", (col_number, 1), (col_number, -1), column.justify)
                )
            if column.font_size and column.font_size > 0:
                self.DEFAULT_TABLE_STYLE.append(
                    (
                        "FONTSIZE",
                        (col_number, 1),
                        (col_number, -1),
                        column.font_size,
                    )
                )
            if column.bold:
                self.DEFAULT_TABLE_STYLE.append(
                    ("FONTNAME", (col_number, 1), (col_number, -1), self.bold_font)
                )

        report_data = []
        if section.include_column_headers:
            report_data.append([x.heading for x in section.columns])

        scalars = dict(TOTAL=dict(), AVG=dict(), COUNT=dict())
        for row in section.data:
            rr = []
            for column in section.columns:
                column_style = (
                    column.paragraph_style if column.paragraph_style else self.styleN
                )
                if column.justify == "CENTER":
                    column_style = self.style_center_n
                elif column.justify == "RIGHT" or column.currency:
                    column_style = self.style_right_n

                if isinstance(row, dict):
                    value = row[column.name]
                else:
                    value = row.__dict__[column.name]

                if value:
                    if column.datatype.lower() in ["int", "integer"]:
                        scalar_rounding = "0"
                        value = Decimal(value).quantize(
                            Decimal(scalar_rounding), rounding=ROUND_HALF_UP
                        )
                        if column.include_commas:
                            value = FORMAT_INT.format(value)
                    elif column.datatype.lower() == "date":
                        if not isinstance(value, datetime.date):
                            value = parse(value).date()
                        value = value.strftime("%m/%d/%Y")
                    elif column.datatype.lower() == "datetime":
                        if not isinstance(value, datetime.datetime):
                            value = parse(value)
                        value = value.strftime("%m/%d/%Y %I:%M%p")
                    elif column.decimal_positions:
                        scalar_rounding = "0.0000000000"[: column.decimal_positions + 2]
                        value = Decimal(value).quantize(
                            Decimal(scalar_rounding), rounding=ROUND_HALF_UP
                        )
                        if column.include_commas:
                            fmt = "{0:,.%sf}" % column.decimal_positions
                            value = fmt.format(value)
                    column_style.fontSize = column.font_size
                    display_value = value
                    if column.currency:
                        display_value = f"${value}"
                    rr.append(Paragraph(str(display_value), style=column_style))
                else:
                    rr.append("")
                string_value = str(value)
                x = max(len(x) for x in string_value.split("
"))
                # column.set_width(
                #     len(str(max(len(x) for x in string_value.split("
"))))
                # )
                column.set_width(max(len(x) for x in string_value.split("
")))
                for scalar in scalars:
                    if column.scalar == scalar:
                        if column.name not in scalars[scalar]:
                            scalars[scalar][column.name] = 0
                        if value:
                            scalar_rounding = "0.00"
                            if column.datatype.lower() in ["int", "integer"]:
                                scalar_rounding = "0"
                            if isinstance(value, str):
                                value = value.replace(",", "")
                            if column.decimal_positions:
                                scalar_rounding = "0.0000000000"[
                                    : column.decimal_positions + 2
                                ]
                            scalars[scalar][column.name] += Decimal(value).quantize(
                                Decimal(scalar_rounding),
                                rounding=ROUND_HALF_UP,
                            )

            report_data.append(rr)

        data_rows = len(report_data)
        for scalar in sorted(scalars):
            scalar_label_written = False
            if len(scalars[scalar]) > 0:
                #  add summary row:
                rr = []
                for column_number, column in enumerate(section.columns):
                    if column.name in scalars[scalar]:
                        if scalar == "TOTAL":
                            if column.include_commas:
                                if column.datatype.lower() in ["int", "integer"]:
                                    scalars[scalar][column.name] = FORMAT_INT.format(
                                        scalars[scalar][column.name]
                                    )
                                elif column.decimal_positions:
                                    fmt = "{0:,.%sf}" % column.decimal_positions
                                    scalars[scalar][column.name] = fmt.format(
                                        scalars[scalar][column.name]
                                    )
                            rr.append(scalars[scalar][column.name])
                        elif scalar == "AVG":
                            rr.append(
                                Decimal(
                                    scalars[scalar][column.name] / data_rows
                                ).quantize(Decimal("0.00"))
                            )
                        elif scalar == "COUNT":
                            rr.append(Decimal(data_rows).quantize(Decimal("0")))
                    else:
                        if not scalar_label_written and self.scalar_heading_column == (
                            column_number + 1
                        ):
                            column_style = (
                                column.paragraph_style
                                if column.paragraph_style
                                else self.styleN
                            )
                            rr.append(
                                Paragraph(
                                    f"{scalar} {section.header}", style=column_style
                                )
                            )
                            scalar_label_written = True
                        else:
                            rr.append("")
                report_data.append(rr)

        if len(self.row_heights) > 0:
            data_table = Table(
                report_data,
                colWidths=self.get_column_widths(section.columns),
                repeatRows=1,
                rowHeights=self.row_heights,
            )
        else:
            data_table = Table(
                report_data,
                colWidths=self.get_column_widths(section.columns),
                repeatRows=1,
            )

        data_table.setStyle(
            TableStyle(
                section.tablestyle if section.tablestyle else self.DEFAULT_TABLE_STYLE
            )
        )
        self.report_story.append(data_table)

        if section.footer and section.footer != "":
            self.report_story.append(Spacer(1, 0.1 * inch))
            self.report_story.append(
                Paragraph(
                    section.footer,
                    style=section.footer_style if section.footer_style else self.styleN,
                )
            )
        else:
            self.report_story.append(Spacer(1, section.space_after))

        if section.pagebreak:
            self.report_story.append(PageBreak())

    def get_column_widths(self, columns=None):
        if not columns:
            columns = self.columns

        column_widths = []

        #  get total width
        total_width = sum([x.max_width for x in columns])
        page_width = self.PAGE_WIDTH - self.left_margin - self.right_margin

        for column in columns:
            column_widths.append(page_width * column.max_width / total_width)

        return column_widths


import fixapp
import argparse
import logging
import quickfix as fix
from fixapp import (FixDecoder, parse_fix_options, print0,printv,printvv,printvvv)
from fixapp import BaseFixClient as FixClient
import datetime as dt
import time
import random
# from utils import *
class Session(object):
    def __init__(self,args):
        self.args         = args
        self.config_file  = args.config
        self.settings     = fix.SessionSettings(self.config_file)
        self.app          = FixClient()
        self.storeFactory = fix.FileStoreFactory(self.settings)
        self.logFactory   = fix.FileLogFactory(self.settings)
        self.initiator    = fix.SocketInitiator(self.app,self.storeFactory,self.settings,self.logFactory)

def trading_vol(order_dict):
    vol_dict={'MSFT':0,'AAPL':0,'BAC':0}
    list_id=list(order_dict)
    for id in list_id:
        vol_dict[order_dict[id][55]]+=int(order_dict[id][38])
    print("total trading volume",vol_dict)

# setting_dict={'SenderCompID':"OPS_CANDIDATE_3_8639",'TargetCompID':"DTL",'SocketConnectPort':5100,'SocketConnectHost':"fix.dytechlab.com"}
if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='FIX Client')
    parser.add_argument('--config', default='configs/fix/DTL.cfg',type=str, help='Name of configuration file')
    args = parser.parse_args()

    #-==========================================================================

    logging.basicConfig(level=logging.DEBUG,format='%(message)s')#logging.ERROR,logging.WARNING,logging.INFO, logging.DEBUG
    random_list=[0,1,2,3,4,5,6,7,8,9,10,11]
    try:
        app = Session(args)
        app.initiator.start()
        # reset sequence when logon
        time.sleep(3)
        # app.app.resetSeqLogOn()

        #### REQUIREMENT 9^th

        _, o1 = parse_fix_options("  -54 1 -55 MSFT -40 1 -44 1.9 -38 10") # Limit order
        _, o2 = parse_fix_options("  -54 2 -55 MSFT -40 2 -44 1.9 -38 10") # Limit order
        _, o3 = parse_fix_options("  -54 1 -55 MSFT -40 1  -38 10") #Market order, no price
        _, o4 = parse_fix_options("  -54 2 -55 MSFT -40 1  -38 10") #Market order, no price

        _, o5 = parse_fix_options("  -54 1 -55 AAPL -40 1 -44 1.9 -38 10") # Limit order
        _, o6 = parse_fix_options("  -54 2 -55 AAPL -40 2 -44 1.9 -38 10") # Limit order
        _, o7 = parse_fix_options("  -54 1 -55 AAPL -40 1  -38 10") #Market order, no price
        _, o8 = parse_fix_options("  -54 2 -55 AAPL -40 1  -38 10") #Market order, no price

        _, o9 = parse_fix_options("  -54 1 -55 AAPL -40 1 -44 1.9 -38 10") # Limit order
        _, o10 = parse_fix_options("  -54 2 -55 AAPL -40 2 -44 1.9 -38 10") # Limit order
        _, o11 = parse_fix_options("  -54 1 -55 AAPL -40 1  -38 10") #Market order, no price
        _, o12 = parse_fix_options("  -54 2 -55 AAPL -40 1  -38 10") #Market order, no price

        # _, sell_options2 = parse_fix_options("  -54 2 -55 BAC -40 1  -38 10")#Short, Market order, no price

        # for SHORT order, change tag 54 to 5, but I won't list here to calculate PnL in 10^th requirement
        # for simple, I only list these order above, not list all.
        # list_options=[buy_options1,buy_options2,sell_options1,sell_options2]
        list_options=[o1,o2,o3,o4,o5,o6,o7,o8,o9,o10,o11,o12]
        for i in range(1000):
            random_option=list_options[random.choice(random_list)]
            app.app.OneOrder(**random_option)
            # app.app.buy(**random_option)
            # app.app.sell(**sell_options)
            time.sleep(0.25) # 5min=300sec, mean that we need to successfully sent each order in about 0.3sec
            if random.choice(random_list)==1:
                app.app.cancel_order(**random_option)

        time.sleep(1)
        #### REQUIREMENT 10^th
        transaction_list=app.app.transaction_list
        # trans_dict={'MSFT':[[],[]],'AAPL':[[None,None],[None,None]],'BAC':[[None,None],[None,None]]}
        trans_dict={'MSFT':[[],[]],'AAPL':[[],[]],'BAC':[[],[]]}
        # for a symbol:
            # [[all buy transaction],[all sell transaction]],total bought vol, total sold vol, average
# each symbol contain [total bought volume, total sold volume, bought trade vol, sold trade vol ]

        for trans in transaction_list:
            trans_dict[trans['Symbol']][int(trans['Side'])-1].append([trans['LastShares'],trans['LastPx']])
        import numpy as np

        PnL_all=0
        trading_vol_in_USD=0
        # print(transaction_list)
        for symbol in ['MSFT','AAPL','BAC']:
            PnL=0.0
            buy_trans=np.array(trans_dict[symbol][0])
            if len(buy_trans)==0:
                total_vol_buy=0.0
                total_buy_value=0.0
                avg_buy_price=0.0
            else:
                total_vol_buy=np.sum(buy_trans[:,0],axis=0)
                total_buy_value=buy_trans[:,0]@buy_trans[:,1] #multily by numpy arr will faster than seperated multiply in for loop
                avg_buy_price=total_buy_value/total_vol_buy #this number is also VWAP for buy side
                print("buy average price of {}: {} USD".format(symbol,avg_buy_price))
            sell_trans=np.array(trans_dict[symbol][1])
            if len(sell_trans)==0:
                total_vol_sell=0.0
                total_sell_value=0.0
                avg_sell_price=0.0
            else:
                total_vol_sell=np.sum(sell_trans[:,0],axis=0)
                total_sell_value=sell_trans[:,0]@sell_trans[:,1]
                avg_sell_price=total_sell_value/total_vol_sell#this number is also VWAP for sell side
                print("sell average price of {}: {} USD".format(symbol,avg_sell_price))
            # import pdb;pdb.set_trace()
            PnL +=(avg_sell_price-avg_buy_price)*min(total_buy_value,total_sell_value)
            PnL_all+=PnL
            # I use absolute value here because these orders are random, so we can't assure that the buy vol alway greater than the sell vol
            #vwap of the remaining stock
            # vwap=(total_sell_value)
            trading_vol_in_USD+=total_buy_value+total_sell_value
            print("total trading volume of {}:{} USD".format(symbol,total_buy_value+total_sell_value))
            if PnL>=0:
                print("profit of trading on {}: {} USD".format(symbol,PnL))
            else:
                print("loss of trading on {}: {} USD".format(symbol,-1*PnL))

            # trading_vol_in_USD+=float(trans['LastPx'])*float(trans['LastShares'])

        print("total trading volume of this session:{} USD".format(trading_vol_in_USD))
        if PnL_all>=0:
            print("profit of trading on {}: {} USD".format(symbol,PnL_all))
        else:
            print("loss of trading on {}: {} USD".format(symbol,-1*PnL_all))
        app.initiator.stop()

    except (fix.ConfigError , fix.RuntimeError) as e:
        print(e)


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
import django.utils.timezone

class Migration(migrations.Migration):

    dependencies = [
        ('home', '0002_auto_20151001_0731'),
    ]

    operations = [
        migrations.CreateModel(
            name='ResumeFolderID',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('folder_id', models.CharField(max_length=127)),
                ('date_created', models.DateTimeField(default=django.utils.timezone.now)),
            ],
        ),
    ]


from tkinter import *
from PIL import Image, ImageTk

root=Tk()

def search():
    uid1=str(uid.get())
    fn=str(fnn.get())
    ln=str(lnn.get())
    age1=int(agen.get())
    gender1=str(gendern.get())
    from1=str(ffrom.get())
    to1=str(tto.get())

#[6:20 pm, 01/04/2022] Aryan Mehta: %sql INSERT INTO searchbook1 (userid,fname,lname,age,gender,ffrom,fto) values (:uid1,:fn,:ln,:age1,:gender1,:from1,:to1);
#[6:27 pm, 01/04/2022] Aryan Mehta: %sql select * from flights1 where ffrom=:from1 and fto=:to1;\

def clearmain1():
    mainpage.grid_forget()
    bookbutton = Button(bookticket, text="SEARCH FLIGHTS!", padx=10, pady=10,command=search)
    bookbutton.grid(row=25, column=3)
    goback1=Button(bookticket,text="GO TO MAIN PAGE!",font="comicsansms 9",command=gomain1)
    goback1.grid(row=26,column=3,pady=15)
    bookticket.grid()

def clearmain2():
    mainpage.grid_forget()

def clearmain3():
    mainpage.grid_forget()

def gomain1():
    bookticket.grid_forget()
    mainpage.grid()

root.geometry("1000x1000")
root.title("AIRLINE MANAGEMENT SYSTEM")
root.wm_iconbitmap("flightlogo.ico")

titleframe=Frame(root,width=800,height=35)
img=Image.open("plane.jpg")
img=img.resize((200,150),Image.ANTIALIAS)
pht=ImageTk.PhotoImage(img)
Label(titleframe,image=pht,pady=10).grid(row=0,column=5,padx=30)
title=Label(titleframe,text="AIR LINE MANAGEMENT SYSTEM",font="comicsansms 20 bold underline",fg="white",bg="red",padx=220,pady=20)
title.grid(row=0,column=6)
titleframe.grid(row=0,column=0)

mainpage=Frame(root)
b1=Button(mainpage,text="PRESS HERE TO BOOK FLIGHT TICKET!    ",command=clearmain1,font="comicsansms 10 bold",padx=10,pady=50,borderwidth=5)
b2=Button(mainpage,text="PRESS HERE TO VIEW AVAILABLE FLIGHTS!",command=clearmain2,font="comicsansms 10 bold",padx=3,pady=50,borderwidth=5)
b3=Button(mainpage,text="PRESS HERE TO VIEW USERDATA!         ",command=clearmain3,font="comicsansms 10 bold",padx=17,pady=50,borderwidth=5)

b1.grid(column=10)
b2.grid(column=10)
b3.grid(column=10)
mainpage.grid()

uid=StringVar()
fnn=StringVar()
lnn=StringVar()
agen=StringVar()
gendern=StringVar()
ffrom=StringVar()
tto=StringVar()

bookticket=Frame(root)

e1=Entry(bookticket,textvariable=uid)
l1=Label(bookticket,text="ENTER USERID:")
l1.grid(row=5,column=2,pady=5,padx=10)
e1.grid(row=5,column=3,pady=5,padx=10)
e2=Entry(bookticket,textvariable=fnn)
l2=Label(bookticket,text="ENTER FIRSTNAME:")
l2.grid(row=6,column=2,pady=5,padx=10)
e2.grid(row=6,column=3,pady=5,padx=10)
e3=Entry(bookticket,textvariable=lnn)
l3=Label(bookticket,text="ENTER LASTNAME:")
l3.grid(row=7,column=2,pady=5,padx=10)
e3.grid(row=7,column=3,pady=5,padx=10)
e4=Entry(bookticket,textvariable=agen)
l4=Label(bookticket,text="ENTER AGE:")
l4.grid(row=8,column=2,pady=5,padx=10)
e4.grid(row=8,column=3,pady=5,padx=10)
e5=Entry(bookticket,textvariable=gendern)
l5=Label(bookticket,text="ENTER GENDER:")
l5.grid(row=9,column=2,pady=5,padx=10)
e5.grid(row=9,column=3,pady=5,padx=10)
e6=Entry(bookticket,textvariable=ffrom)
l6=Label(bookticket,text="ENTER FROM WHERE YOU WANT BOARD:")
l6.grid(row=10,column=2,pady=5,padx=10)
e6.grid(row=10,column=3,pady=5,padx=10)
e7=Entry(bookticket,textvariable=tto)
l7=Label(bookticket,text="ENTER WHERE YOU WANT TO REACH:")
l7.grid(row=11,column=2,pady=5,padx=10)
e7.grid(row=11,column=3,pady=5,padx=10)

root.mainloop()


import json
import random
import string
import botocore
import boto3

from datetime import datetime

def format_response(status_code, result, message, log, **kwargs):
    response = {'outcome': result}
    if message:
        response['message'] = message
    if kwargs:
        for k, v in kwargs.items():
            if v:
                response[k] = v
    if log:
        log['response'] = response
        print(log)
    return {'statusCode': status_code, 'body': json.dumps(response)}

class Task:

    def __init__(self, deployment_name, task_name, region, detail: dict, user_id, log):
        self.deployment_name = deployment_name
        self.task_name = task_name
        self.region = region
        self.detail = detail
        self.user_id = user_id
        self.log = log
        self.__aws_dynamodb_client = None
        self.__aws_s3_client = None

    @property
    def aws_dynamodb_client(self):
        """Returns the boto3 DynamoDB session (establishes one automatically if one does not already exist)"""
        if self.__aws_dynamodb_client is None:
            self.__aws_dynamodb_client = boto3.client('dynamodb', region_name=self.region)
        return self.__aws_dynamodb_client

    @property
    def aws_s3_client(self):
        """Returns the boto3 S3 session (establishes one automatically if one does not already exist)"""
        if self.__aws_s3_client is None:
            self.__aws_s3_client = boto3.client('s3', region_name=self.region)
        return self.__aws_s3_client

    def get_task_entry(self):
        return self.aws_dynamodb_client.get_item(
            TableName=f'{self.deployment_name}-tasks',
            Key={
                'task_name': {'S': self.task_name}
            }
        )

    def get_task_type(self, task_type):
        return self.aws_dynamodb_client.get_item(
            TableName=f'{self.deployment_name}-task-types',
            Key={
                'task_type': {'S': task_type}
            }
        )

    def set_task_busy(self, i_ids, i_id, instances, instance, command, args, timestamp):
        task_status = 'busy'
        try:
            self.aws_dynamodb_client.update_item(
                TableName=f'{self.deployment_name}-tasks',
                Key={
                    'task_name': {'S': self.task_name}
                },
                UpdateExpression='set task_status=:task_status, '
                                'instruct_ids=:instruct_ids, '
                                'instruct_instances=:instruct_instances, '
                                'last_instruct_user_id=:last_instruct_user_id, '
                                'last_instruct_id=:last_instruct_id, '
                                'last_instruct_instance=:last_instruct_instance, '
                                'last_instruct_command=:last_instruct_command, '
                                'last_instruct_args=:last_instruct_args, '
                                'last_instruct_time=:last_instruct_time',
                ExpressionAttributeValues={
                    ':task_status': {'S': task_status},
                    ':instruct_ids': {'SS': i_ids},
                    ':instruct_instances': {'SS': instances},
                    ':last_instruct_user_id': {'S': self.user_id},
                    ':last_instruct_id': {'S': i_id},
                    ':last_instruct_instance': {'S': instance},
                    ':last_instruct_command': {'S': command},
                    ':last_instruct_args': {'M': args},
                    ':last_instruct_time': {'S': timestamp}
                }
            )
        except botocore.exceptions.ClientError as error:
            return error
        except botocore.exceptions.ParamValidationError as error:
            return error
        return 'task_set_as_busy'

    def upload_object(self, instruct_id, instruct_instance, instruct_command, instruct_args, end_time, timestamp):
        payload = {
            'instruct_user_id': self.user_id, 'instruct_id': instruct_id, 'instruct_instance': instruct_instance,
            'instruct_command': instruct_command, 'instruct_args': instruct_args, 'timestamp': timestamp,
            'end_time': end_time
        }
        payload_bytes = json.dumps(payload).encode('utf-8')
        try:
            self.aws_s3_client.put_object(
                Body=payload_bytes,
                Bucket=f'{self.deployment_name}-workspace',
                Key=self.task_name + '/' + timestamp
            )
        except botocore.exceptions.ClientError as error:
            return error
        except botocore.exceptions.ParamValidationError as error:
            return error
        return 'object_uploaded'

    def instruct(self):
        timestamp = datetime.now().strftime('%s')
        try:
            instruct_command = self.detail['instruct_command']
        except:
            return format_response(400, 'failed', 'missing instruct_command', self.log)

        if 'instruct_instance' in self.detail and self.detail['instruct_instance']:
            instruct_instance = self.detail['instruct_instance']
        else:
            instruct_instance = 'havoc'
        if 'instruct_args' in self.detail and self.detail['instruct_args']:
            instruct_args = self.detail['instruct_args']
        else:
            instruct_args = {'no_args': 'True'}
        if 'end_time' in self.detail and self.detail['end_time']:
            end_time = self.detail['end_time']
        else:
            end_time = 'None'

        # Validate that task exists and error if it does not
        task_entry = self.get_task_entry()
        if 'Item' not in task_entry:
            return format_response(404, 'failed', f'task_name {self.task_name} not found', self.log)

        # Get task capabilities from the task and validate instruct_command
        task_type = task_entry['Item']['task_type']['S']
        task_type_details = self.get_task_type(task_type)
        capabilities = task_type_details['Item']['capabilities']['SS']
        if instruct_command not in capabilities:
            return format_response(400, 'failed', f'{instruct_command} not valid for task_name {self.task_name}',
                                   self.log)

        # Validate that task is idle
        if task_entry['Item']['task_status']['S'] == 'starting':
            return format_response(409, 'failed', f'task {self.task_name} is still starting', self.log)
        if task_entry['Item']['task_status']['S'] == 'busy':
            return format_response(409, 'failed', f'task {self.task_name} is busy', self.log)
        if task_entry['Item']['task_status']['S'] == 'terminated':
            return format_response(409, 'failed', f'task {self.task_name} no longer running', self.log)

        # Add new instruct_id to instruct_ids list
        instruct_id = ''.join(random.choice(string.ascii_letters) for i in range(6))
        instruct_ids = task_entry['Item']['instruct_ids']['SS']
        if instruct_ids[0] == 'None':
            instruct_ids.clear()
        instruct_ids.append(instruct_id)

        # Add new instruct_instance to instruct_instances list
        instruct_instances = task_entry['Item']['instruct_instances']['SS']
        if instruct_instances[0] == 'None':
            instruct_instances.clear()
        if instruct_instance not in instruct_instances:
            instruct_instances.append(instruct_instance)

        # Convert instruct_args to DynamoDB map format
        instruct_args_fixup = {}
        for k, v in instruct_args.items():
            if isinstance(v, str):
                instruct_args_fixup[k] = {'S': f'{v}'}
            if isinstance(v, int) and not isinstance(v, bool):
                instruct_args_fixup[k] = {'N': f'{v}'}
            if isinstance(v, bool):
                instruct_args_fixup[k] = {'BOOL': v}
            if isinstance(v, bytes):
                instruct_args_fixup[k] = {'B': f'{v}'}

        # Set task to busy and send instructions to the task
        set_task_busy_response = self.set_task_busy(
            instruct_ids, instruct_id, instruct_instances, instruct_instance, instruct_command, instruct_args_fixup, timestamp
        )
        if set_task_busy_response != 'task_set_as_busy':
            return format_response(500, 'failed', f'interact failed with error {set_task_busy_response}', self.log)
        upload_object_response = self.upload_object(instruct_id, instruct_instance, instruct_command, instruct_args, end_time, timestamp)
        if upload_object_response != 'object_uploaded':
            return format_response(500, 'failed', f'interact failed with error {upload_object_response}', self.log)

        # Send response
        return format_response(200, 'success', f'interact with {self.task_name} succeeded', None, instruct_id=instruct_id)


import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm
from scipy.sparse import csr_matrix
from scipy.sparse import eye as sparse_eye
from scipy.sparse import bmat as sparse_block
from openfermion.ops import QubitOperator
from scipy.sparse import lil_matrix
from scipy.sparse import find

class VectorPauliWord():
    """
    Object of PauliWord that gives vector repesentation of Openfermion QubitOperator.
    Vector form first lists PauliX terms then PauliZ terms... as a ( 1 x 2 Nqubits)

    see pg8 of https://arxiv.org/pdf/1907.09386.pdf

    Args:
        n_qubits (int): Number of qubits
        QubitOp (Openfermion.ops.QubitOperator): Openfermion QubitOperator of 1 operator ONLY

    ** Example **

    >> from openfermion.ops import QubitOperator
    >> A = QubitOperator('X0 X1 X2', 1)
    >> Vec_A = VectorPauliWord(3, A)
    >> print(Vec_A.Pvec)

    output:
        [1 1 1 0 0 0]

    """

    def __init__(self, n_qubits, QubitOp):
        self.n_qubits = n_qubits
        self.QubitOp = QubitOp

        self.Pvec = None
        self._init_vector_form()

    def _init_vector_form(self):
        """
        Build vector from of PauliWord, as a ( 1 x 2 Nqubits) vector
        First N sites are Pauli X terms. Last N sites are Pauli Z terms
        """

        Pvec = np.zeros((2*self.n_qubits), dtype=int)

        PauliStr, const = tuple(*self.QubitOp.terms.items())
        for qNo, Pstr in PauliStr:
            if Pstr == 'X':
                Pvec[qNo] = 1

            elif Pstr == 'Z':
                Pvec[qNo+self.n_qubits] = 1

            elif Pstr == 'Y':
                Pvec[qNo] = 1
                Pvec[qNo+self.n_qubits] = 1
            else:
                raise ValueError(f'not Pauli operator: {Pstr} on qubit {qNo}')

        self.Pvec = csr_matrix(Pvec, shape=(1, 2*self.n_qubits), dtype=int)
        return None

def Commutes(VectorPauliWord_1, VectorPauliWord_2):
    """

    See https://arxiv.org/pdf/1907.09386.pdf for mathematical background of symplectic approach.

    Args:
        VectorPauliWord_1 (VectorPauliWord): vector object of PauliWord
        VectorPauliWord_2 (VectorPauliWord): vector object of other PauliWord

    Returns:
        bool: True if operators commute else returns False not

    """

    if VectorPauliWord_1.n_qubits!=VectorPauliWord_2.n_qubits:
        raise ValueError('qubit number mismatch')

    n_qubits = VectorPauliWord_1.n_qubits
    sparse_zeros = csr_matrix(np.zeros((n_qubits,n_qubits), dtype=int))
    J_symplectic = sparse_block([
                                [sparse_zeros , sparse_eye(n_qubits, dtype=int)],
                                [sparse_eye(n_qubits), sparse_zeros]
                            ])

    commutation = VectorPauliWord_1.Pvec.dot(J_symplectic.dot(VectorPauliWord_2.Pvec.T))

    if commutation.toarray()%2 == 0:
        return True
    else:
        return False

def QWC(VectorPauliWord_1, VectorPauliWord_2):
    """

    Cannot use symplectic approach. Iteratively goes through each vectorised Pauliword, checking if
     they all qubit-wise commute (QWC)

    Args:
        VectorPauliWord_1 (VectorPauliWord): vector object of PauliWord
        VectorPauliWord_2 (VectorPauliWord): vector object of other PauliWord

    Returns:
        bool: True if operators QWC else returns False not

    """
    if VectorPauliWord_1.n_qubits != VectorPauliWord_2.n_qubits:
        raise ValueError('qubit number mismatch')

    n_qubits = VectorPauliWord_1.n_qubits
    for qbit in range(n_qubits):

        qbit_vec1 = (VectorPauliWord_1.Pvec[0, qbit], VectorPauliWord_1.Pvec[0, qbit+n_qubits]) # (X_vec, Z_vec)

        if qbit_vec1 == (0,0):
            # identity on qubit of Vec1
            continue

        qbit_vec2 = (VectorPauliWord_2.Pvec[0, qbit],  VectorPauliWord_2.Pvec[0, qbit+n_qubits])  # (X_vec, Z_vec)

        if qbit_vec2 == (0,0):
            # identity on qubit of Vec2
            continue

        if qbit_vec1 != qbit_vec2:
            # different operator on qubit
            return False

    return True

def vectorised_full_commuting_check(n_qubits, binary_H_mat):

    sparse_zeros = csr_matrix(np.zeros((n_qubits,n_qubits), dtype=int))
    J_symplectic = sparse_block([
                                [sparse_zeros , sparse_eye(n_qubits, dtype=int)],
                                [sparse_eye(n_qubits), sparse_zeros]
                            ])

    vectorised_commuting_check = binary_H_mat.dot(J_symplectic.dot(np.transpose(binary_H_mat)))
    return vectorised_commuting_check

class Vector_QubitHamiltonian():
    """
    Build matrix form of QubitHamiltonian. Allows fast way to get Hamiltonian graph adjacency matrix

    Args:
        n_qubits (int): Number of qubits
        QubitHamiltonian (Openfermion.ops.QubitOperator): Openfermion QubitOperator of 1 or MORE operator

    ** Example **

    >> from openfermion.ops import QubitOperator
    >> n_qubits = 3
    >> H = QubitOperator('X0 X1 X2') + QubitOperator('X1 X2') + QubitOperator('Z0 Z2') + QubitOperator('X2')
    >> H_vec = Vector_QubitHamiltonian(H, n_qubits)
    >> adj_mat = H_vec.Get_adj_mat('C')

    >> print([P.QubitOp for P in H_vec.QubitHamiltonian_VectorPauliWord_list])
    >> print(adj_mat)

    output:
        [   1.0 [X0 X1 X2],
            1.0 [X1 X2],
            1.0 [Z0 Z2],
            1.0 [X2]
        ]

        [[0 1 1 1]
         [1 0 0 1]
         [1 0 0 0]
         [1 1 0 0]]

    """
    def __init__(self, QubitHamiltonian, n_qubits):
        self.QubitHamiltonian_list = list(QubitHamiltonian)
        self.n_qubits = n_qubits

        self.Qubit_H_vec_ind_to_Pvec = {}
        self._init_binary_matrix_form()

    def _init_binary_matrix_form(self):
        """
        Turn QubitHamiltonian of M terms into an (M * 2N_qubits) matrix. Each row represents a Pauli Operator.
        Useful as can find commutativity relationships in a vectorised way.
        """

        # binary_mat= np.zeros((len(self.QubitHamiltonian_list), 2 * self.n_qubits), dtype=int) # (observabales, Paulivec_len)
        # for ind, PauliOp in enumerate(self.QubitHamiltonian_list):
        #     VectorPWord = VectorPauliWord(self.n_qubits, PauliOp)
        #     binary_mat[ind,:] = VectorPWord.Pvec.toarray()
        #     self.QubitHamiltonian_VectorPauliWord_list.append(VectorPWord)

        # self.binary_mat = csr_matrix(binary_mat, dtype=int)

        # self.binary_mat= csr_matrix(np.zeros((len(self.QubitHamiltonian_list),2 * self.n_qubits)), shape=(len(self.QubitHamiltonian_list),2 * self.n_qubits), dtype=int)# (observabales, Paulivec_len)
        self.binary_mat= lil_matrix((len(self.QubitHamiltonian_list),2 * self.n_qubits), dtype=int)
        for ind, PauliOp in enumerate(self.QubitHamiltonian_list):
            VectorPWord = VectorPauliWord(self.n_qubits, PauliOp)
            self.binary_mat[ind,:] = VectorPWord.Pvec
            self.Qubit_H_vec_ind_to_Pvec[ind] = VectorPWord
        return None

    def Get_adj_mat(self, Pauli_grouping_type):
        """

        Build the adjacency matrix for an undirected graph of M nodes - each node reprsenting a term in the Hamiltonian.
        The adjacency matrix is a M by M symmetric binary matrix, where matrix elements of 1 denote an edge, and
        matrix elements of 0 denote no edge. To get term of Hamiltonian from index use the Qubit_H_vec_ind_to_Pvec dictionary

        Args:
            Pauli_grouping_type (str): Commuting relationship between node edges
                                       (C = commuting, AC = anticommuting, QWC = qubitwise commuting)

        Returns:
            adjacency_mat (scipy.sparse array): matrix repsenting which terms are connected by edge
                                                              representing commuting relationship met.
        """
        if (Pauli_grouping_type == 'QWC') or (Pauli_grouping_type=='qubit-wise commuting'):

            adjacency_mat = np.zeros((self.binary_mat.shape[0], self.binary_mat.shape[0]), dtype=int)

            for P_vec_row_ind in range(self.binary_mat.shape[0]):
                for j in range(P_vec_row_ind+1, self.binary_mat.shape[0]): # only loop over bottom triangle

                    does_qwc = int(QWC(self.Qubit_H_vec_ind_to_Pvec[P_vec_row_ind],
                                       self.Qubit_H_vec_ind_to_Pvec[j])
                                   )

                    adjacency_mat[(P_vec_row_ind, j)] =does_qwc #[i, j]
                    adjacency_mat[(j, P_vec_row_ind)] =does_qwc # [j,i]

        else:

            vectorised_commuting_check = vectorised_full_commuting_check(self.n_qubits, self.binary_mat)

            if (Pauli_grouping_type == 'AC') or (Pauli_grouping_type=='anitcommuting'):
                adjacency_mat = vectorised_commuting_check.toarray() % 2

                ## TODO: below approach may be faster... as this does NOT make full dense array
                # row, col, values = find(Hamilt_vector.binary_mat)
                # modulo_values = values%2
                # adjacency_mat= csr_matrix((modulo_values, (row, col)), shape=(Hamilt_vector.binary_mat.shape[0], Hamilt_vector.binary_mat.shape[0]))

            elif (Pauli_grouping_type == 'C') or (Pauli_grouping_type=='commuting'):
                adjacency_mat = (vectorised_commuting_check.toarray()+1) % 2
                np.fill_diagonal(adjacency_mat, 0)

            else:
                raise ValueError(f'Unknown Pauli_grouping_type {Pauli_grouping_type}')

        return adjacency_mat #csr_matrix(adjacency_mat)

def Clique_cover_Hamiltonian(QubitHamiltonian, n_qubits, clique_relation, colouring_strategy, colour_interchange=False):

    """
    Find Clique cover of a given Openfermion QubitOperator representing a Hamiltonian

    Note for colour interchange
    https://fileadmin.cs.lth.se/cs/Personal/Andrzej_Lingas/k-m.pdf

    Args:
        QubitHamiltonian (Openfermion.ops.QubitOperator): Openfermion QubitOperator of 1 or MORE operator
        n_qubits: Total number of qubits for system
        clique_relation: Commuting relationship between terms in Hamiltonian
        colouring_strategy: Graph Colouring strategy
        colour_interchange (bool): Flag to perform colour interchange alg

    Returns:
        Cliques (dict): Dictionary of cliques following specified clique_relation

    ** Example **

    >> from openfermion.ops import QubitOperator
    >> n_qubits = 3
    >> H = QubitOperator('X0 X1 X2') + QubitOperator('X1 X2') + QubitOperator('Z0 Z2') + QubitOperator('X2')
    >> CliqueCover = Clique_cover_Hamiltonian(H, n_qubits, 'AC', 'largest_first')
    >> print(CliqueCover)

    output:
        {
             0: [1.0 [X0 X1 X2]],
             1: [1.0 [X1 X2], 1.0 [Z0 Z2]],
             2: [1.0 [X2]]
        }
    """

    if isinstance(QubitHamiltonian, list):
        H = QubitOperator()
        for Q_op in QubitHamiltonian: H+=Q_op
        QubitHamiltonian=H

    Hamilt_vector = Vector_QubitHamiltonian(QubitHamiltonian, n_qubits)

    adj_matrix = Hamilt_vector.Get_adj_mat(clique_relation)
    node_conv_dict = {ind: Pvec.QubitOp for ind, Pvec in Hamilt_vector.Qubit_H_vec_ind_to_Pvec.items()}

    Graph = nx.from_numpy_matrix(adj_matrix)
    Complement_Graph = nx.complement(Graph)

    greedy_colouring_output_dic = nx.greedy_color(Complement_Graph, strategy=colouring_strategy, interchange=colour_interchange)
    unique_colours = set(greedy_colouring_output_dic.values())

    Cliques = {}
    for Clique_ind in unique_colours:
        Cliques[Clique_ind] = [node_conv_dict[k] for k in greedy_colouring_output_dic.keys()
                                        if greedy_colouring_output_dic[k] == Clique_ind]

    # colour_key_for_nodes = {}
    # for colour in unique_colours:
    #     colour_key_for_nodes[colour] = [k for k in greedy_colouring_output_dic.keys()
    #                                     if greedy_colouring_output_dic[k] == colour]

    # if plot_graph is True:
    #     import matplotlib.cm as cm
    #     colour_list = cm.rainbow(np.linspace(0, 1, len(Cliques)))
    #     pos = nx.circular_layout(Graph)
    #
    #     for colour_ind, clique_list in enumerate(Cliques.values()):
    #         nx.draw_networkx_nodes(Graph, pos,
    #                                nodelist=[node_ind for node_ind in range(len(clique_list))],
    #                                node_color=colour_list[colour_ind].reshape([1, 4]),
    #                                node_size=500,
    #                                alpha=0.8)
    #
    #     labels = {node: clique_P_ops for node, clique_P_ops in enumerate(clique_list)}
    #     seperator = ' '
    #     labels = {node: seperator.join([tup[1] + str(tup[0]) for tup in node[0]]) for node in list(Graph.nodes)}
    #     #
    #     # nx.draw_networkx_labels(Graph, pos, labels)  # , font_size=8)
    #
    #     nx.draw_networkx_edges(Graph, pos, width=1.0, alpha=0.5)
    #
    #     # plt.savefig('coloured_G', dpi=300, transparent=True, )  # edgecolor='black', facecolor='white')
    #
    #     plt.show()

    del Hamilt_vector
    del Graph
    del Complement_Graph
    return Cliques


"""
Unit tests for shp.
"""

import os,tempfile
from nose import SkipTest
from nose.tools import assert_equal

import networkx as nx

class TestShp(object):
    @classmethod
    def setupClass(cls):
        global ogr
        try:
            from osgeo import ogr
        except ImportError:
            raise SkipTest('ogr not available.')

    def setUp(self):
        drv = ogr.GetDriverByName("ESRI Shapefile")
        shppath = os.path.join(tempfile.gettempdir(),'tmpshp.shp')
        if os.path.exists(shppath):
            drv.DeleteDataSource(shppath)
        shp = drv.CreateDataSource(shppath)
        lyr = shp.CreateLayer( "edges", None, ogr.wkbLineString)

        self.paths = (  [(7.0, 1.0), (1.0, 1.0)],
                        [(2.0, 2.0), (3.0, 3.0)],
                        [(4.0, 1.0), (2.0, 2.0)]
                    )
        for path in self.paths:
            feat = ogr.Feature(lyr.GetLayerDefn())
            g = ogr.Geometry(ogr.wkbLineString)
            for xy in path:
                g.AddPoint_2D(*xy)
            feat.SetGeometry(g)
            lyr.CreateFeature(feat)
        self.shppath = shppath
        self.drv = drv

    def testload(self):
        expected = nx.DiGraph()
        for p in self.paths:
            expected.add_path(p)
        actual = nx.read_shp(self.shppath)
        assert_equal(sorted(expected.node), sorted(actual.node))
        assert_equal(sorted(expected.edges()), sorted(actual.edges()))

    def tearDown(self):
        self.drv.DeleteDataSource(self.shppath)


"""
================
Plot Volume Data
================

This plots example volume data onto an example subject, S1, onto a flatmap
using quickflat. In order for this to run, you have to have a flatmap for
this subject in the pycortex filestore.

The cortex.Volume object is instantiated with a numpy array of the same size
as the scan for this subject and transform. Instead of the random test data,
you can replace this with any numpy array of the correct dimensionality.

By changing the parameters vmin and vmax, you get thresholded data, as shown
in the colorbar for the figure.

If you have NaN values within your array, those voxels show up transparent
on the brain.
"""

import cortex
import numpy as np
np.random.seed(1234)
import matplotlib.pyplot as plt

subject = 'S1'
xfm = 'fullhead'

# Creating a random dataset that is the shape for this transform with one
# entry for each voxel
test_data = np.random.randn(31, 100, 100)

# This creates a Volume object for our test dataset for the given subject
# and transform
vol_data = cortex.Volume(test_data, subject, xfm)
cortex.quickshow(vol_data)
plt.show()

# Can also alter the minimum and maximum values shown on the colorbar
vol_data_thresh = cortex.Volume(test_data, subject, xfm, vmin=-1, vmax=1)
cortex.quickshow(vol_data_thresh)
plt.show()

# If you have NaN values, those voxels show up transparent on the brain
test_data[10:15, :, :] = np.nan
vol_data_nan = cortex.Volume(test_data, subject, xfm)
cortex.quickshow(vol_data_nan)
plt.show()


import base64
import os

import requests

GEOCODING_URL = "https://api.openweathermap.org/geo/1.0/direct"
WEATHER_URL = "https://api.openweathermap.org/data/2.5/weather"

def load_key_from_env(env_key: str = "WEATHER_API_KEY", key: str | None = None):
    if key is None:
        key = os.environ.get(env_key)
    return key

def request(url: str, key: str | None = None, **params) -> dict:
    key = load_key_from_env(key=key)
    params = {**params, "appid": key}
    req = requests.get(url, params=params)
    if req.status_code != 200:
        raise ValueError(req.json()["message"])
    return req.json()

def resolve_city_data(city: str, key: str | None = None, lang: bool = False) -> tuple[float, float] | list[str]:
    key = load_key_from_env(key=key)
    lang_lat = request(GEOCODING_URL, key, q=city)
    if lang:
        try:
            ind = list(lang_lat[0]["local_names"]).index("en")
            local_names_values = list(lang_lat[0]["local_names"].values())
            return local_names_values[ind:] + local_names_values[:ind]
        except KeyError:
            return [lang_lat[0]["name"]]
        except IndexError:
            raise ValueError("City not found.")
    # Last element is the most accurate (from what I found out)
    lat = lang_lat[len(lang_lat)-1]["lat"]
    lon = lang_lat[len(lang_lat)-1]["lon"]
    return lat, lon

def resolve_city_weather(city: str, key: str | None = None) -> dict:
    key = load_key_from_env(key=key)
    lat, lon = resolve_city_data(city, key)
    data = request(WEATHER_URL, key, lat=lat, lon=lon, units="metric")
    return data

def get_weather_icon(icon_id: str) -> bytes:
    url = f'https://openweathermap.org/img/wn/{icon_id}.png'
    response = requests.get(url, stream=True)
    return base64.encodebytes(response.raw.read())


import requests
from bs4 import BeautifulSoup
import re

from requests.models import Response

headers = {
    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36'
}

for x in range(0, 2):
    url = 'https://store.steampowered.com/category/racing/#p={x}&tab=NewReleases'
    r = requests.get(url, headers=headers)
    suop = BeautifulSoup(r.text, "html.parser")
    New_Releases = suop.find('div', attrs={'id':'NewReleasesRows'})
    NewReleasesContents = suop.findAll('a', attrs={'class':'tab_item'})

    for NewReleasesContent in NewReleasesContents:
        imageUrl = NewReleasesContent.find('img', attrs={'class':'tab_item_cap_img'})['src']
        titles = NewReleasesContent.find('div', attrs={'class':'tab_item_name'}).text
        response = requests.get(imageUrl, headers=headers, stream=True)
        fileName = imageUrl.split("/")[-1].split("?")[0]
        ext = fileName[-4:]
        if response.ok:
            with open('images/' + re.sub(r'(?u)[^-\w.]', '-', titles) + ext, 'wb') as a:
                a.write(response.content)


import requests
import csv
from bs4 import BeautifulSoup as bs

headers = {
    "accept": "*/*",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 OPR/66.0.3515.103"
}

base_url = "https://hh.ru/search/vacancy?L_is_autosearch=false&area=1&clusters=true&enable_snippets=true&text=python&page=0"

def hh_parse(base_url, headers):
    jobs = []
    urls = []
    urls.append(base_url)
    session = requests.Session()
    request = session.get(base_url, headers=headers)
    if request.status_code == 200:
        soup = bs(request.content, "lxml")

        try:
            num_of_pages = soup.find_all("a", attrs={"data-qa": "pager-page"})[-1].text
            num_of_pages = int(num_of_pages) + 1
            for i in range(num_of_pages):
                url = (
                    f"https://hh.ru/search/vacancy?L_is_autosearch=false&area=1&clusters=true&enable_snippets=true&text=python&page={i}")
                if url not in urls:
                    urls.append(url)
        except:
            pass

        for url in urls:
            request = session.get(url, headers=headers)
            soup = bs(request.content, "lxml")
            divs = soup.find_all("div", attrs={"data-qa": "vacancy-serp__vacancy"})
            for div in divs:
                try:
                    title = div.find("a", attrs={"data-qa": "vacancy-serp__vacancy-title"}).text
                    href = div.find("a", attrs={"data-qa": "vacancy-serp__vacancy-title"})['href']
                    company = div.find("a", attrs={"data-qa": "vacancy-serp__vacancy-employer"}).text
                    text_1 = div.find("div", attrs={"data-qa": "vacancy-serp__vacancy_snippet_responsibility"}).text
                    text_2 = div.find("div", attrs={"data-qa": "vacancy-serp__vacancy_snippet_requirement"}).text
                    descr = f"Ищем: {text_1}. Требования: {text_2}"
                    jobs.append({
                        'title': title,
                        'href': href,
                        'company': company,
                        'descr': descr
                    })
                    print('Спарсено количество вакансий: ', len(jobs))

                except:
                    pass

    else:
        print("Error!")
    print("Parsing finished!")
    return jobs

def write_jobs_to_file(jobs):
    with open('parsed jobs.csv', 'w', encoding='utf-8') as file:
        writer = csv.writer(file)
        writer.writerow(('Вакансия', 'Ссылка', 'Компания', 'Описание'))
        for job in jobs:
            writer.writerow((job['title'], job['href'], job['company'], job['descr']))
        print("Writing to csv is finished!")

jobs = hh_parse(base_url, headers)

write_jobs_to_file(jobs)


import awswrangler as wr
import re
from aws_solutions.core.helpers import get_service_resource, get_service_client
from boto3.dynamodb.conditions import Key
import os

#######################################################
# Use S3 Interface to interact with S3 objects
# For example to download/upload them
#######################################################
from datalake_library.commons import init_logger
from datalake_library.configuration.resource_configs import S3Configuration, KMSConfiguration
from datalake_library.interfaces.s3_interface import S3Interface

s3 = get_service_resource('s3')
dynamodb = get_service_resource("dynamodb")
ssm = get_service_client('ssm')

s3_interface = S3Interface()
# IMPORTANT: Stage bucket where transformed data must be uploaded

logger = init_logger()

class CustomTransform():
    def __init__(self):
        logger.info("S3 Blueprint Light Transform initiated")

    def transform_object(self, resource_prefix, bucket, key, team, dataset):

        stage_bucket = S3Configuration(resource_prefix).stage_bucket

        # show the object key received
        logger.info('SOURCE OBJECT KEY: ' + key)

        # retrieve the source file as an S3 object
        s3_object = s3.Object(bucket, key)

        # get the file size - originally we would send the file size to the email lambda to determine if it can be attached
        file_size = s3_object.content_length

        # get the file last modified date as a formatted string
        file_last_modified = s3_interface.get_last_modified(bucket, key)
        file_last_modified = file_last_modified.replace(' ', '-').replace(':', '-').split('+')[0]
        logger.info('file_last_modified: {}'.format(file_last_modified))

        # initialize our variables extracted from the key:
        key_team = key_dataset = workflow_name = schedule_frequency = file_name = file_year = file_month = file_day = file_hour = file_minute = file_second = file_millisecond = file_basename = file_extension = file_version = ''

        processed_keys = []

        # break the file key into it's name components
        key_parts = re.match("workflow=([^/]*)/schedule=([^/]*)/(.*)", key)  # USE WHEN INGESTION BUCKET OUTSIDE OF LAKE

        if key_parts is not None:
            workflow_name, schedule_frequency, file_name = key_parts.groups()  # use below 3 lines if ingesting direct from amc bucket
            key_team = team
            key_dataset = dataset

            # see if the workflow name matches the naming scheme for a AMC UI result:
            if re.match("analytics-[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}", workflow_name) is not None:
                logger.info(
                    "Workflow name {} appears to be a result from the AMC UI, skipping transformation, setting processed_keys to an empty array".format(
                        workflow_name))
                processed_keys = []
                return (processed_keys)

        #see if the key matches the file name pattern with time e.g. workflow=standard_geo_date_summary_V3/schedule=adhoc/file_last_modified=2020-02-03-12-25-38/2020-02-03T14:01:47Z-standard_geo_date_summary.csv
        file_name_with_time_search_results = re.match("(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})Z-([^\.]*)\.(.*)",file_name)

        if file_name_with_time_search_results is not None:
            file_year, file_month, file_day, file_hour, file_minute, file_second, file_millisecond, file_basename, file_extension = file_name_with_time_search_results.groups()
        else:
            # Check to see if the key matches the pattern with date only e.g. workflow=standard_geo_date_summary_V3/schedule=weekly/2020-02-04-standard_geo_date_summary_V3-ver2.csv
            file_name_date_only_search_results = re.match("(\d{4})-(\d{2})-(\d{2})-([^.]*)\.(.*)", file_name)
            if file_name_date_only_search_results is not None:
                file_year, file_month, file_day, file_basename, file_extension = file_name_date_only_search_results.groups()

        # check to see if we have a version appended to the end fo the file basename
        version_results = re.match(".*-(ver\d)", file_basename)
        if version_results is not None:
            file_version = version_results.groups()[0]

        customer_config = ssm.get_parameter(
            Name='/{}/DynamoDB/DataLake/CustomerConfig'.format(resource_prefix),
            WithDecryption=True
        ).get('Parameter').get('Value')
        config_table = dynamodb.Table(customer_config)
        response = config_table.query(
            IndexName='amc-index',
            Select='ALL_PROJECTED_ATTRIBUTES',
            KeyConditionExpression=Key('hash_key').eq(bucket)
        )
        prefix = response['Items'][0]['prefix'].lower()
        customer_hash_key = response['Items'][0]['customer_hash_key'].lower()
        logger.info('prefix: {}'.format(prefix))
        ##################################

        #read the source file into a pandas dataframe, replacing double quotes with single quotes before it is processed by pandas
        logger.info("fileExtension : " + file_extension)
        logger.info("workflowName : " + workflow_name)
        logger.info("scheduleFrequency : " + schedule_frequency)

        if file_extension.lower() == 'csv' and workflow_name != '' and schedule_frequency != '' :

            ### Validate small file ###
            if file_size < 1000:
                line_count = s3_object.get()['Body'].read().decode('utf-8').count('\n')
                if line_count <= 1:
                    logger.info("File empty: No data to process")
                    return processed_keys

            oob_reports = get_oob_reports(response)

            # Calculate the output path with partitioning based on the original file name
            output_path = ''

            # ### OUTPUT_PATH FOR OOB_REPORTS
            table_prefix = get_table_prefix(workflow_name, oob_reports, prefix)

            output_path = get_output_path(file_version,table_prefix,workflow_name,schedule_frequency,customer_hash_key,file_year,file_month,file_last_modified,file_basename,file_extension)

            logger.info("outputPath : " + output_path)

            output_path = os.path.splitext(output_path)[0].rsplit('/', 1)[0].split('/')
            output_path[0] = wr.catalog.sanitize_table_name(output_path[0])
            output_path = '/'.join(output_path)
            output_path = '{}/{}.{}'.format(output_path, file_basename, file_extension)

            # Uploading file to Stage bucket at appropriate path
            # IMPORTANT: Build the output s3_path without the s3://stage-bucket/
            s3_path = 'pre-stage/{}/{}/{}'.format(team,dataset, output_path)
            logger.info('S3 Path: {}'.format(s3_path))

            kms_key = KMSConfiguration(resource_prefix, "Stage").get_kms_arn

            content = s3_object.get()['Body'].read().decode("UTF8").replace('\\"', "'")

            file_meta_data = {
                'keyTeam': key_team,
                'keyDataset': key_dataset,
                'workflowName': workflow_name,
                'scheduleFrequency': schedule_frequency,
                'fileName': file_name,
                'fileYear': file_year,
                'fileMonth': file_month,
                'fileDay': file_day,
                'fileHour': file_hour,
                'fileMinute': file_minute,
                'fileSecond': file_second,
                'fileMillisecond': file_millisecond,
                'fileBasename': file_basename,
                'fileExtension': file_extension,
                'fileVersion': file_version,
                'partitionedPath': output_path.rsplit('/', 1)[0]
            }

            s3.Object(stage_bucket, s3_path).put(Body=content, ServerSideEncryption='aws:kms', SSEKMSKeyId=kms_key,
                                                 Metadata=file_meta_data
                                                 )

            # IMPORTANT S3 path(s) must be stored in a list
            processed_keys = [s3_path]

        #######################################################
        # IMPORTANT
        # This function must return a Python list
        # of transformed S3 paths. Example:
        # ['pre-stage/engineering/legislators/persons_parsed.json']
        #######################################################

        return processed_keys

def get_table_prefix(workflow_name, oob_reports, prefix):
    if workflow_name not in oob_reports:
        table_prefix=prefix
    else:
        table_prefix = 'amc'

    return table_prefix

def get_oob_reports(response):
    oob_reports = []
    if 'oob_reports' in response['Items'][0]:
                for wf in response['Items'][0]['oob_reports']:
                    oob_reports.append(wf)

    return oob_reports

def get_output_path(file_version, table_prefix,workflow_name,schedule_frequency,customer_hash_key,file_year,file_month,file_last_modified,file_basename,file_extension):
    if file_version != '':
        output_path = "{}_{}_{}_{}/customer_hash={}/export_year={}/export_month={}/file_last_modified={}/{}.{}".format(table_prefix,workflow_name,schedule_frequency,file_version,customer_hash_key,file_year,file_month,file_last_modified,file_basename,file_extension)
    else:
        output_path = "{}_{}_{}/customer_hash={}/export_year={}/export_month={}/file_last_modified={}/{}.{}".format(table_prefix,workflow_name,schedule_frequency,customer_hash_key,file_year,file_month,file_last_modified,file_basename,file_extension)

    return output_path


import json

devices = [{'platform': 'nexus', 'hostname': 'nycr01'}, {'platform': 'catalyst', 'hostname': 'nycsw02'}, {'platform': 'mx', 'hostname': 'nycr03'}, {'platform': 'srx', 'hostname': 'nycfw01'}, {'platform': 'asa', 'hostname': 'nycfw02'}]
print(json.dumps(devices, indent=4))

for item in devices:
    platform = item.get('platform')
    if platform == "nexus":
         print("Vendor is Cisco")
    elif platform == "catalyst":
         print("Vendor is Cisco")
    elif platform == "aci":
         print("Vendor is Cisco")
    elif platform == "srx" or platform == "mx":
         print("Vendor is Juniper")
    else:
         print("Unknown Vendor")

# Another option to verify

cisco_platforms = ['catalyst', 'nexus', 'aci']
juniper_platforms = ['mx', 'srx']

for item in devices:
    platform = item.get('platform')
    if platform in cisco_platforms:
        print("Vendor is Cisco")
    elif platform in juniper_platforms:
        print("Vendor is Juniper")
    else:
        print("Unknown Vendor")


"""1. Znajdź pierwszą cyfrę od lewej i od prawej zadanego napisu
2. Połącz tak znalezione 2 cyfry w liczbę 2-cyfrową."""
from more_itertools import first
from pathlib import Path

def get_2_digit_number(string: str) -> int:
    digits = []
    for char in string:
        if char.isdigit():
            digits.append(char)
    return int(digits[0] + digits[-1])

def get_2_digit_number_2(string: str) -> int:
    digit_1 = first(char for char in string if char.isdigit())
    digit_2 = first(char for char in reversed(string) if char.isdigit())
    return int(digit_1 + digit_2)

advent_txt = Path(__file__).parent / "advent_1.txt"
lines = open(advent_txt).readlines()
result = sum(map(get_2_digit_number, lines))
print(result)


# -*- coding: utf-8 -*-
from django.shortcuts import render
from django.shortcuts import redirect
from django.http import HttpResponse, JsonResponse
import json
#IMPORTAMOS LA CONEXION PARA QUERY PERSONALIZADO
from django.db import connection, transaction
#dictfetchall
from principal.views import dictfetchall, notificacion_director
#models
from personal.models import Personal
from auditoria.models import Auditorias
from sac.models import Sac, PlanAccion
from areas.models import Procesos
from documentos.models import Documento, Revision

#importamos el form documento
from documentos.forms import DocumentacionForm
from procesos.forms import ReemplazarForm
from documentos.models import Documento, Revision
from normas.models import Normas

# Create your views here.
@notificacion_director
def jefe(request):

    #validar si existe usuario logeado
    if 'nombreUsuario' not in request.session:

        return render(request, 'seguridad/login.html', {})

    else:

        cursor = connection.cursor()
        query = "SELECT auditoria_auditorias.numero_auditoria FROM  auditoria_auditorias GROUP BY auditoria_auditorias.numero_auditoria "
        cursor.execute(query)
        rows = dictfetchall(cursor)

        #CONVIERTE  QuerySet  a list object
        num_auditorias_list= list(rows)

        context={
            'personal':Personal.objects.values('id', 'nombre').filter(estado=1),
            'auditorias':num_auditorias_list,
            'jefes':Personal.objects.values('id', 'nombre').all().filter(estado=1),
            'sacs':Sac.objects.values('id').all().filter(estado_cabecera=1),
        }

        return render(request, 'jefeArea/jefe.html', context)

def updateSacJefe(request):

    #validar si existe usuario logeado
    if 'nombreUsuario' not in request.session:

        return render(request, 'seguridad/login.html', {})

    else:

        if request.method == 'POST' and request.is_ajax():

            id_sac = request.POST.get('id_sac')
            descripcion_correcion = request.POST.get('descripcion_correcion')
            analisis_causa= request.POST.get('analisis_causa')

            if descripcion_correcion:

                descripcion = descripcion_correcion
            else:

                descripcion = None

            Sac.objects.filter(pk=id_sac).update(
                descripcion_correcion=descripcion,
                analisis_causa=analisis_causa,
                estado_cabecera = 3
            )
            if 'notificacionJefe' in request.session:
                del request.session['notificacionJefe']

            data = {
                'resultado':'ok_update',
                'mensaje': 'Datos Actualizados Correctamente !!',
            }
            return HttpResponse(json.dumps(data), content_type="application/json")

def insertPlanAccion(request):

    if 'nombreUsuario' not in request.session:

        return render(request, 'seguridad/login.html', {})

    else:

        if request.method == 'POST' and request.is_ajax():

            planes = request.POST.get('data')
            id_sac = request.POST.get('id_sac')

            import json
            planes_json = json.loads(planes)

            for plan in planes_json['gridData']:
                with transaction.atomic():
                    plan_accion = PlanAccion(
                        detalle_plan_accion = plan['detalle'],
                        plazo_plan_accion = plan['plazo'],
                        responsable_plan_accion_id = plan['responsable'],
                        sac_id = plan['id_sac'],
                        recursoHumano = plan['recursoHumano'],
                        detalleRecursoHumano = plan['detalleRecursoHumano'],
                        recursoTecnico = plan['recursoTecnico'],
                        detalleRecursoTecnico = plan['detalleRecursoTecnico'],
                        recursoFinanciero = plan['recursoFinanciero'],
                        detalleRecursoFinanciero = plan['detalleRecursoFinanciero']
                    )
                    plan_accion.save()

            Sac.objects.filter(pk=id_sac).update(
                sac_jefe = None
            )
            if 'notificacionJefe' in request.session:
                del request.session['notificacionJefe']

            json = {
                'resultado':'ok_insert',
                'mensaje': 'Datos Grabados Correctamente !!',
            }
            return JsonResponse(json, safe=False)

def updatePlan(request):

    if 'nombreUsuario' not in request.session:

        return render(request, 'seguridad/login.html', {})

    else:
        if request.method == 'POST' and request.is_ajax():

            id_plan = request.POST.get('id_plan')
            plan = request.POST.get('plan')
            plazo = request.POST.get('plazo')
            responsable_id = request.POST.get('responsable_id')

            PlanAccion.objects.filter(pk=id_plan).update(
                detalle_plan_accion=plan,
                plazo_plan_accion=plazo,
                responsable_plan_accion_id = responsable_id
            )

            json = {
                'resultado':'ok_update',
                'mensaje': 'Datos Grabados Correctamente !!',
            }
            return JsonResponse(json, safe=False)

def deletePlanAccion(request):

    if 'nombreUsuario' not in request.session:

        return render(request, 'seguridad/login.html', {})

    else:
        if request.method == 'POST' and request.is_ajax():

            id_plan = request.POST.get('id_plan')

            #eliminar plan
            PlanAccion.objects.filter(pk=id_plan).delete()
            #resultado
            json = {
                'resultado':'ok_delete',
                'mensaje': 'Datos Eliminados Correctamente !!',
            }
            return JsonResponse(json, safe=False)

def selectPlanAccion(request):

    if 'nombreUsuario' not in request.session:

        return render(request, 'seguridad/login.html', {})

    else:

            id_sac = request.GET['id_sac']

            cursor = connection.cursor()
            query = " SELECT sac_planaccion.detalle_plan_accion, sac_planaccion.plazo_plan_accion,  sac_planaccion.responsable_plan_accion_id "
            query = query + " FROM sac_planaccion "
            query = query + " WHERE sac_planaccion.sac_id=%s"
            params=[id_sac]

            cursor.execute(query, params)

            rows = dictfetchall(cursor)

            from sac.views import json_serial
            return HttpResponse(json.dumps(rows,  default=json_serial), content_type="application/json")

def selectPlanColab(request):

    if 'nombreUsuario' not in request.session:

        return render(request, 'seguridad/login.html', {})

    else:

        if request.method == 'POST' and request.is_ajax():

            id_plan = request.POST.get('id_plan')

            cursor = connection.cursor()
            query = " SELECT * "
            query = query + " FROM sac_planaccion "
            query = query + " WHERE sac_planaccion.id=%s"
            params=[id_plan]

            cursor.execute(query, params)

            rows = dictfetchall(cursor)

            from sac.views import json_serial
            return HttpResponse(json.dumps(rows,  default=json_serial), content_type="application/json")

@notificacion_director
def adminJefeDoc(request):

    """
    LISTADO DE DIRECTORIO POR ID USUARIO
    Y PROCESO VINCULADO
    """
    if 'nombreUsuario' not in request.session:

        return render(request, 'seguridad/login.html', {})

    else:

        cursor = connection.cursor()
        query = " SELECT DISTINCT areas_areas.area AS proceso "
        query = query + " FROM areas_area_proceso "
        query = query + " LEFT JOIN areas_areas ON areas_area_proceso.area_id = areas_areas.id  "
        #query = query + " LEFT JOIN areas_personal ON areas_area_proceso.id = areas_personal.procedimiento_id "
        query = query + " WHERE areas_area_proceso.personal_id =%s "
        params=[request.session['idUsuario']]
        cursor.execute(query, params)

        rows_directorios = dictfetchall(cursor)

        alldirectorys = []
        import os
        for directorios in rows_directorios:
            for root, dirs, files in os.walk('media/gestionDocumental'):
                for directory in dirs:
                    if directory == directorios['proceso']:

                        element = {}
                        element['directorio'] =  directory
                        element['ruta_absoluta'] = root+'/'+directory
                        alldirectorys.append(element)

        context={
            'archivos_list': alldirectorys,
            'documentoForm':DocumentacionForm,
            'reemplazarForm':ReemplazarForm
        }
        return render(request, 'jefeArea/directorios.html', context)

@notificacion_director
def estadoDocJefe(request):

    #validar si existe usuario logeado
    if 'nombreUsuario' not in request.session:

        #return render(request, 'seguridad/login.html', {})
        return redirect('login')
    else:

        context={
        }
        return render(request, 'jefeArea/estadoDocumentacion.html', context)

def updateDocJefe(request):

    #validar si existe usuario logeado
    if 'nombreUsuario' not in request.session:

        #return render(request, 'seguridad/login.html', {})
        return redirect('login')
    else:

        id_revision = request.POST.get('id_revision')
        id_documento = request.POST.get('id_documento')
        #estado 2=observacion 3=aprobado
        estado = request.POST.get('estado_documento')

        import datetime
        fecha_actual = datetime.datetime.now().strftime ("%Y-%m-%d")

        #existe observacion
        if estado == '2':

            observacion = request.POST.get('observaciones_documento')
        else:
            observacion = None

        Documento.objects.filter(pk=id_documento).update(
            estado = estado
        )

        Revision.objects.filter(pk=id_revision).update(
            fec_rev_director = fecha_actual,
            estado_rev_director = estado,
            observacion_rev_director = observacion
        )
        #borrar alerta
        if  'notificacionDocPorRevisar' in request.session  :
            del request.session['notificacionDocPorRevisar']

        return redirect('estadoDocJefe')

def selectNotLider(request):
        # validar si existe usuario logeado
    if 'nombreUsuario' not in request.session:

        # return render(request, 'seguridad/login.html', {})
        return redirect('login')

    else:
        if request.method == "POST" and request.is_ajax():

            cursor = connection.cursor()
            query = " SELECT DISTINCT areas_procesos.proceso, areas_procesos.estado_colaborador, areas_procesos.estado_encargado, "
            query = query + " areas_procesos.observaciones_encargado, areas_procesos.estado_lider, areas_procesos.observaciones_lider, "
            query = query + " areas_procesos.estado_admin "
            query = query + " FROM areas_procesos "
            query = query + " LEFT JOIN areas_area_proceso ON areas_procesos.id = areas_area_proceso.proceso_id "
            query = query + " WHERE areas_procesos.estado_encargado = 4"
            query = query + " AND areas_area_proceso.personal_id =%s "
            params=[request.session['idUsuario']]

            cursor.execute(query, params)

            rows=dictfetchall(cursor)
            list_rows = list(rows)

            return JsonResponse(list_rows, safe=False)

def GridDocumentacionDirector(request):

    # validar si existe usuario logeado
    if 'nombreUsuario' not in request.session:

        # return render(request, 'seguridad/login.html', {})
        return redirect('login')

    else:

        cursor = connection.cursor()
        query = " SELECT DISTINCT documentos_documento.id AS id_documento, documentos_documento.path, documentos_documento.descripcion, "
        query = query + " documentos_documento.version, documentos_documento.estado, documentos_documento.fec_subido, "
        query = query + " documentos_documento.nombre, areas_areas.area AS proceso,  areas_procesos.proceso AS procedimiento, "
        query = query + " personal_personal.nombre AS nombre_responsable, documentos_documento.procedimiento_id "
        query = query + " FROM documentos_documento "
        query = query + " LEFT JOIN areas_areas ON documentos_documento.proceso_id = areas_areas.id "
        query = query + " LEFT JOIN areas_procesos ON documentos_documento.procedimiento_id = areas_procesos.id "
        query = query + " LEFT JOIN personal_personal ON documentos_documento.subido_por_id = personal_personal.id "
        query = query + " LEFT JOIN documentos_revision ON documentos_documento.id = documentos_revision.documento_id "
        query = query + " WHERE documentos_revision.director_id =%s "
        params = [request.session['idUsuario']]

        cursor.execute(query, params)

        rows = dictfetchall(cursor)

        if rows:
            #RETORNA
            return JsonResponse(rows, safe=False)
        else:
            #RETORNA MENSAJE DE NO EXISTEN
            json = {
            'resultado':'no_ok',
            'mensaje': 'no existen Documentos',
            }
            return JsonResponse(json, safe=False)