from __future__ import annotations
import itertools
import json
import math
import random
import uuid
from copy import deepcopy
from datetime import datetime
import time
from typing import Iterable, List
from dataclasses import dataclass

from tinyolap.config import Config
from tinyolap.dimension import Dimension
from tinyolap.member import Member, MemberList
# from tinyolap.cube import Cube
from tinyolap.utilities.hybrid_dict import HybridDict
from tinyolap.exceptions import TinyOlapViewError

class ViewCell:
    """Represents a single data cell in a view."""

    def __init__(self, value: float, formatted_value: str, format: str, comments=None):
        self.value: float = value
        self.formatted_value: str = formatted_value
        self.format: str = format
        self.comments = comments

@dataclass
class ViewStatistics:
    """Represents statics about the size and refresh of a view."""
    last_refresh: datetime = datetime.min
    refresh_duration: float = 0.0
    cells_count: int = 0
    rows: int = 0
    columns: int = 0
    row_dimensions: int = 0
    column_dimensions: int = 0
    executed_rules: int = 0
    executed_cell_requests: int = 0
    executed_cell_aggregations: int = 0

    def to_dict(self) -> dict:
        return {
            'lastRefresh': str(self.last_refresh.isoformat()),
            'refreshDuration': self.refresh_duration,
            'cellsCount': self.cells_count,
            'rowsCount': self.rows,
            'columnsCount': self.columns,
            'rowDimensionsCount': self.row_dimensions,
            'columnDimensionsCount': self.column_dimensions,
            'executedRules': self.executed_rules,
            'executedCellRequests': self.executed_cell_requests,
            'executedCellAggregations': self.executed_cell_aggregations
        }

class ViewWindow:
    """Defines a window to a view by column (left, right) and row (top, bottom) coordinates."""
    def __init__(self, top: int, left: int, bottom: int, right: int):
        self.top = top
        self.left = left
        self.bottom = bottom
        self.right = right

    def __len__(self):
        return (self.bottom - self.top + 1) * (self.right - self.left + 1)

    @property
    def columns(self) -> int:
        return self.right - self.left + 1

    @property
    def rows(self) -> int:
        return self.bottom - self.top + 1

    def contain(self, row, column) -> bool:
        """Check if a specific (row, column) coordinate falls into the view window."""
        return self.top <= row <= self.bottom and self.left <= column <= self.right

    def intersect(self, other: ViewWindow) -> ViewWindow | None:
        """Returns the intersection of the view window with another view window."""
        right = min(self.right, other.right)
        left = max(self.left, other.left)
        bottom = min(self.bottom, other.bottom)
        top = max(self.top, other.top)
        d_cols = right - left
        d_rows = bottom - top
        if (d_cols >= 0) and (d_rows >= 0):
            return ViewWindow(top, left,  bottom, right)
        return None

    def __str__(self):
        return f"[{self.top}, {self.left}] <-> [{self.bottom}, {self.right}]"

    def __repr__(self):
        return self.__str__()

class ViewAxisPositionMember:
    def __init__(self, view, axis, member: Member, indentation: int = 0):
        self._view = view
        self._axis = axis
        self.member = member
        self.indentation = indentation

class ViewAxisPosition:
    def __init__(self, view, axis, members: tuple[Member]):
        self._view = view
        self._axis = axis
        self._members = members
        self.all_zero: bool = False

    def __len__(self):
        return len(self._members)

    def __iter__(self):
        return self._members.__iter__()

    def __getitem__(self, item: int) -> Member:
        return self._members[item]

    def __str__(self):
        return f"ViewAxisPos['{[member for member in self._members]}']"

    def __repr__(self):
        return f"ViewAxisPos['{[member for member in self._members]}']"

    @property
    def dimensions_count(self):
        """Returns the number of position on the axis."""
        return len(self._members)

class ViewAxisDimension:
    def __init__(self, dimension: Dimension):
        self.dimension: Dimension = dimension
        self.top_level: int = 0

class ViewAxis(Iterable[ViewAxisPosition]):

    def __init__(self, view, idx, dimensions, member_lists):
        self._view = view
        self._positions: tuple[ViewAxisPosition] = tuple()

        if isinstance(dimensions, Iterable):
            self._dimensions: tuple[Dimension] = dimensions
            self._dim_members: tuple[MemberList] = member_lists
            self._dim_idx: tuple[int] = idx
        else:
            self._dimensions: tuple[Dimension] = (dimensions,)
            self._dim_members: tuple[MemberList] = (member_lists,)
            self._dim_idx: tuple[int] = (idx,)

        self._dim_count = len(self._dimensions)
        self._members_counts = tuple([len(members) for members in self._dim_members])

        # fill positions
        positions = []
        for position in itertools.product(*self._dim_members):
            positions.append(ViewAxisPosition(self._view, self, position))
        self._positions = tuple(positions)
        self._positions_count = len(self._positions)

        # member indentations (needed for row axis mainly)
        self.indentations = []

    def _add(self, idx, dimension, member):
        """Adds a dimension to the view axis."""
        self._dimensions = tuple(list(self._dimensions) + [dimension, ])
        self._dim_members = tuple(list(self._dim_members) + [MemberList(dimension, member), ])
        self._dim_idx = tuple(list(self._dim_idx) + [idx, ])
        self._dim_count = len(self._dimensions)
        self._members_counts = tuple([len(members) for members in self._dim_members])

        # fill positions
        positions = []
        for position in itertools.product(*self._dim_members):
            positions.append(ViewAxisPosition(self._view, self, position))
        self._positions = tuple(positions)
        self._positions_count = len(self._positions)

    def __iter__(self):
        return self._positions.__iter__()

    def __getitem__(self, index: int) -> ViewAxisPosition:
        return self._positions[index]

    def __len__(self):
        """returns the number of positions"""
        return len(self._positions)

    def __str__(self):
        return f"Axis[{', '.join([dim.name + f'({len(members)})' for dim, members in zip(self._dimensions, self._dim_members)])}]"

    def __repr__(self):
        return self.__str__()

    @property
    def positions_count(self):
        """Returns the number of position on the axis."""
        return len(self._positions)

    @property
    def dimensions_count(self):
        """Returns the number of dimensions on the axis."""
        return len(self._dimensions)

    @property
    def dimensions(self) -> tuple[Dimension]:
        """Returns the number of position on the axis."""
        return self._dimensions

    @property
    def positions(self) -> tuple[ViewAxisPosition]:
        """Returns the number of position on the axis."""
        return self._positions

    def to_dict(self) -> dict:
        """Converts the view axis into a serializable Python dictionary."""
        """FOR INTERNAL USE! Converts the contents of the attribute field to a dict."""
        return {"dimensions": [
            {"dimension": dim.name, "ordinal": ordinal, "members":
                [member.name for member in self._dim_members[ordinal]]}
            for ordinal, dim in enumerate(self._dimensions)
        ],
            "positions": [
                {"row": row, "members": [
                    {"name": member.name, "level": member.level,
                     "root": member.is_root} for member in position
                ]} for row, position in enumerate(self._positions)
            ],
        }

class View:
    """
    Represents a view to a cube. Used for reporting purposes. Views manage, optimize
    and provide the client side access to data from a TinyOlap cube.
    """

    # todo: drilldown path
    # todo: drilldowns ermöglichen
    # todo: json output fUr clients

    def __init__(self, cube, name=None, description=None,
                 definition=None,
                 zero_suppression_on_rows: bool = False,
                 zero_suppression_on_columns: bool = False,
                 uid=None,
                 random_view: bool = False,
                 use_first_root_members_for_filters: bool = False,
                 title: str = ""):
        """
        Initializes a new view for the given cube.
        :param cube: The Cube to create a view for.
        :param name: (optional) the name of the view. If no name is defined a random name will be created.
        :param description: (optional) a description for the view.
        :param definition: (optional) a valid view definition. If no view definition is provided, then
            a default view definition, based on the cubes structure, will be created. For details on how to
            create view definitions please see the TinyOlap documentation, or create a default view and
            the a look the dict object returned by the 'definition' property.
        :param zero_suppression_on_rows: Identifies is zero suppression should be applied to the rows of the view.
        :param zero_suppression_on_columns: Identifies is zero suppression should be applied to the columns of the view.
        :param uid: A uid of the view object. Useful for client/server interaction, persistence and state management.
        :param random_view: Flag to force the creation of a random view, instead of the default view, upon
            the given cube. Only valid for the case that no view definition is defined.
        :param use_first_root_members_for_filters: Flag to force to set the first root member for all filter dimensions.
            This applies to both random views and the default view.
        :param title: (optional) Title for the report, if not defined in view definition.
        """
        self._cube = cube
        self._definition = definition
        self._row_zero: bool = zero_suppression_on_rows
        self._col_zero: bool = zero_suppression_on_columns
        self._cells = []
        self._grid = dict()

        if not name:
            name = str(uuid.uuid4())[:8]
        self._name = name

        self._title = title
        self._description: str = ""
        self._default_number_format: str = "{:,.0f}"
        self._uid: str = str(uid) if uid else str(uuid.uuid4())
        self._random_view: bool = random_view
        self._use_first_root_members_for_filters = use_first_root_members_for_filters
        self._statistics: ViewStatistics = ViewStatistics()

        self._last_refresh = None
        self._filter_row_set = None
        self._filter_base_idx_address = None
        self._filter_level = 0
        self._filter_number_format = None

        # process the view definition, or create a default definition
        if self._definition is None:
            self._create_default_definition()
        else:
            filters, rows, columns = self._parse(self._definition)
            self._filter_axis: ViewAxis = filters
            self._row_axis: ViewAxis = rows
            self._col_axis: ViewAxis = columns

    @property
    def cube(self):
        """Returns the cube of the view."""
        return self._cube

    @property
    def definition(self) -> dict:
        """Returns the cube of the view."""
        return self._definition

    @definition.setter
    def definition(self, value: dict):
        """Sets the title of the view."""
        if self._parse(value):
            self._definition = value
        raise TinyOlapViewError(f"An unknown error occurred while pasring view '{self.name}'")

    @property
    def uid(self) -> str:
        """Returns the uid of the view. Useful for client/server interaction, persistence and state management."""
        return self._uid

    @property
    def title(self) -> str:
        """Returns the title of the view."""
        return self._title

    @property
    def name(self) -> str:
        """Returns the name of the view."""
        return self._name

    @property
    def last_refresh(self) -> datetime:
        """Returns the datetime when the view was last refreshed."""
        if not self._last_refresh:
            return datetime.min
        return self._last_refresh

    def reset(self):
        """Resets the cell data currently cached by the view.
        The next access to the view will force a refresh of the requested cell data."""
        self._grid = dict()
        self._filter_number_format = None
        self._filter_level = None
        self._filter_row_set = None
        self._filter_base_idx_address = None

    @title.setter
    def title(self, value: str):
        """Sets the title of the view."""
        self._title = value

    @property
    def description(self) -> str:
        """Returns the description of the view."""
        return self._description

    @description.setter
    def description(self, value: str):
        """Sets the description of the view."""
        self._description = value

    @property
    def default_number_format(self) -> str:
        """Returns the default number format for the view in Python format.
        The default number format is: "{:,.0f}"."""
        return self._default_number_format

    @default_number_format.setter
    def default_number_format(self, value: str):
        """Sets the default number format for the view in Python format.
        Number formats defined for one of the dimension members
        used in the view will override the default format.
        Default format is: "{:,.0f}"."""
        self._default_number_format = value

    @property
    def statistics(self) -> ViewStatistics:
        """Returns the statistic information about the view."""
        return self._statistics

    @property
    def zero_suppression_on_rows(self) -> bool:
        """Returns the zero-suppression setting for the rows of the view."""
        return self._row_zero

    @zero_suppression_on_rows.setter
    def zero_suppression_on_rows(self, value: bool):
        """Sets the zero-suppression setting for the rows of the view."""
        # todo: refresh the view when changing zero suppression
        self._row_zero = value

    @property
    def zero_suppression_on_columns(self) -> bool:
        """Returns the zero-suppression setting for the columns of the view."""
        return self._col_zero

    @zero_suppression_on_columns.setter
    def zero_suppression_on_columns(self, value: bool):
        """Sets the zero-suppression setting for the columns of the view."""
        # todo: refresh the view when changing zero suppression
        self._col_zero = value

    @property
    def use_first_root_members_for_filters(self) -> bool:
        """Returns if the first root member will be set for all filter dimensions in random or default views."""
        return self._use_first_root_members_for_filters

    @use_first_root_members_for_filters.setter
    def use_first_root_members_for_filters(self, value: bool):
        """Sets if the first root member should be set for all filter dimensions in random or default views."""
        self._use_first_root_members_for_filters = value

    @property
    def filter_axis(self):
        """Returns the filter axis of the view, containing the filter settings of the view."""
        return self._filter_axis

    @property
    def row_axis(self):
        """Returns the row axis, containing the member sets defined for the rows of the view."""
        return self._row_axis

    @property
    def column_axis(self):
        """Returns the column axis, containing the member sets defined for the columns of the view."""
        return self._col_axis

    def __getitem__(self, coordinates) -> ViewCell:
        """
        Retrieves a single view cell from the view using its coordinates.

        .. code:: python

            view = View(cube)
            # return the cell at the ordinal position 0
            cell = view[0]
            # return the cell at the row/column position (1, 1)
            cell = view[1, 1]
            # return the cell at the row position ("North", "Cars") and
            # the column position "Jan".
            cell = view["North", "Cars", "Jan"]

        Coordinates are a value or an array of values that uniquely specify
        a data cell with the rows and columns of the view.

        Coordinates can be one of the following:

            * An array of position numbers
            * An array of member names
            * The ordinal position

        :param coordinates: The position to be returned
        :return: A view cell object
        """

        if type(coordinates) is int:
            # ordinal position
            col = coordinates % self._col_axis._positions_count
            row = int(math.floor(coordinates / self._col_axis._positions_count))
        elif len(coordinates) == 2 and type(coordinates[0]) is int:
            col = coordinates[0]
            row = coordinates[1]
        else:
            col = 0
            row = 0
            raise NotImplementedError()

        # collect address by index (not by name, by index is much faster)
        idx_address = [0] * self._cube.dimensions_count
        axis = self._filter_axis
        super_level = 0
        number_format = 0
        for i in range(axis._dim_count):
            idx_address[axis._dim_idx[i]] = axis.positions[0][i].index
            super_level += axis.positions[0][i].level
            if axis.positions[0][i].format:
                number_format = axis.positions[0][i].format
        axis = self._row_axis
        for i in range(axis._dim_count):
            idx_address[axis._dim_idx[i]] = axis.positions[row][i].index
            super_level += axis.positions[row][i].level
            if axis.positions[0][i].format:
                number_format = axis.positions[0][i].format
        axis = self._col_axis
        for i in range(axis._dim_count):
            idx_address[axis._dim_idx[i]] = axis.positions[col][i].index
            super_level += axis.positions[col][i].level
            if axis.positions[0][i].format:
                number_format = axis.positions[0][i].format

        value = self.cube._get((super_level, tuple(idx_address),))
        if number_format:
            return ViewCell(value, number_format.format(value), number_format)
        else:
            return ViewCell(value, self._default_number_format.format(value), number_format)

    def __setitem__(self, coordinates, value):
        """
        Sets the value for a single view cell from the view using its coordinates.
        This method should be used by clients to write data entered by a user into the cube.

        Coordinates are a value or an array of values that uniquely specify
        a data cell with the rows and columns of the view.

        Coordinates can be one of the following:

            * An array of position numbers
            * An array of member names
            * The ordinal position

        :param coordinates: The position to be accessed.
        :param value: The value to be written.
        :return: A view cell object
        """
        return ViewCell()

    def __repr__(self):
        return self._name

    def __str__(self):
        return self._name

    def __len__(self):
        return self._col_axis.positions_count * self._row_axis.positions_count

    def cell(self, coordinates) -> ViewCell:
        """
        Retrieves a single view cell from the view using its coordinates.
        It is recommended to use the build-in __getitem__ method.

        .. code:: python

            view = View(cube)
            # return the cell at the ordinal position 0
            cell = view[0]
            # return the cell at the row/column position (1, 1)
            cell = view[1, 1]
            # return the cell at the row position ("North", "Cars") and
            # the column position "Jan".
            cell = view["North", "Cars", "Jan"]

        Coordinates are a value or an array of values that uniquely specify
        a data cell with the rows and columns of the view.

        Coordinates can be one of the following:

            * An array of position numbers
            * An array of member names
            * The ordinal position

        :param coordinates: The position to be returned
        :return: A view cell object
        """
        return self.__getitem__(coordinates)

    def validate(self, definition: dict) -> (bool, str):
        """Validates if a view definition is valid. Does not raises an error"""
        try:
            filters, rows, columns = self._parse(definition)
        except TinyOlapViewError as err:
            return False, str(err)
        except Exception as err:
            return False, f"Internal Fatal Error. {str(err)}"
        return True, None

    def _parse(self, definition: dict):
        """Parses a view definition. If successful the view will be prepared for execution."""

        # read all (relevant) metadata
        if "name" in definition:
            self._name = definition["name"]
        if "title" in definition:
            self._title = definition["title"]
        if "description" in definition:
            self._description = definition["description"]
        if "cube" in definition:
            if self._cube.name.lower() != str(definition["cube"]).lower():
                raise TinyOlapViewError(f"Failed to parse view definition. Attribute 'cube' = "
                                        f"'{definition['name']}' does not match the name of the "
                                        f"cube the view has been created for: '{self._cube.name}'.")

        # get all defined axis
        filter_axis = None
        row_axis = None
        column_axis = None
        if "filters" in definition:
            filter_axis = self._parse_axis(definition["filters"], "filters", False)
        else:
            filter_axis = ViewAxis(self, [], [], [])
        if "rows" in definition:
            row_axis = self._parse_axis(definition["rows"], "rows")
        else:
            row_axis = ViewAxis(self, [], [], [])
        if "columns" in definition:
            column_axis = self._parse_axis(definition["columns"], "columns")
        else:
            column_axis = ViewAxis(self, [], [], [])

        # are dimensions missing?
        missing_dims = self._get_missing_dims((filter_axis, row_axis, column_axis))
        if missing_dims:
            # add members for all missing dimensions to the filter axis
            for dim in missing_dims:
                ordinal = self._cube.get_dimension_ordinal(dim)
                filter_axis._add(idx=ordinal, dimension=dim, member=dim.default_member)

        return filter_axis, row_axis, column_axis

    def _get_missing_dims(self, axes):
        cube = self._cube
        dimensions = list(cube.dimensions)
        for axis in axes:
            if axis:
                for dim in axis.dimensions:
                    if dim in dimensions:
                        dimensions.remove(dim)
        return dimensions

    def _parse_axis(self, axis_definition, axis_name: str,
                    return_all_members_if_no_members_defined: bool = True) -> ViewAxis:
        axis = axis_definition
        dimensions = []
        member_lists = []
        dim_idx = []
        if "dimensions" in axis:
            dims = axis["dimensions"]
            if not isinstance(dims, Iterable) or type(dims) is str:
                # Not a list of dimensions!!! Then try to evaluate for a
                # simple dimension name e.g. "years", or a list of such e.g. "years, months, products"
                dim_names = str(dims)
                # we allow ',' and ';' as separators
                if "," in dim_names:
                    dim_names = [dim.strip() for dim in dim_names.split(",")]
                elif ";" in dim_names:
                    dim_names = [dim.strip() for dim in dim_names.split(";")]
                else:
                    dim_names = (dim_names,)

                # validate the dimension names
                for dim_name in dim_names:
                    ordinal = self._cube.get_dimension_ordinal(dim_name)
                    if ordinal == -1:
                        raise TinyOlapViewError(f"Failed to parse view definition. Attribute 'dimensions' = "
                                                f"'{dims}' on {axis_name}-axis definition is invalid."
                                                f"At least '{dim_name}' is not a dimension of cube '{self._cube.name}'.")

                    # add the dimension with default member or all members
                    dimension = self._cube.dimensions[dim_name]
                    dim_idx.append(ordinal)
                    dimensions.append(dimension)
                    if return_all_members_if_no_members_defined:
                        members = list(dimension.members)
                    else:
                        members = [dimension.default_member, ]
                    member_lists.append(MemberList(dimension, members))

            else:
                for ordinal, dim in enumerate(dims):
                    # get attributes from definition
                    if "dimension" not in dim:
                        # Not a list of dimension definitions!!! Then try to evaluate for a
                        # simple dimension names e.g. "years"
                        dim_name = str(dim)
                        ordinal = self._cube.get_dimension_ordinal(dim_name)
                        if ordinal == -1:
                            raise TinyOlapViewError(f"Failed to parse view definition. Attribute 'dimensions' = "
                                                    f"'{dim_name}' on {axis_name}-axis definition is invalid."
                                                    f"'{dim_name}' is not a dimension of cube '{self._cube.name}'.")

                        # add the dimension with default member or all members
                        dimension = self._cube.dimensions[dim_name]
                        dim_idx.append(ordinal)
                        if return_all_members_if_no_members_defined:
                            members = list(dimension.members)
                        else:
                            members = [dimension.default_member, ]
                    else:
                        dim_name = dim["dimension"]
                        # get dimension and members
                        if dim_name not in self._cube.dimensions:
                            raise TinyOlapViewError(f"Invalid dimension in view definition. '{dim_name}', "
                                                    f"at 'dimension' at position {ordinal} "
                                                    f"in {axis_name}-axis definition, is not a dimension of "
                                                    f"cube '{self._cube.name}'.")
                        dimension = self._cube.dimensions[dim_name]

                        if "members" in dim:
                            member_names = dim["members"]
                            if isinstance(member_names, str):
                                member_names = (member_names,)
                            elif not isinstance(member_names, Iterable):
                                member_names = (str(member_names),)

                            # FOR FUTURE USE - use of ordinal in axis
                            if "ordinal" in dim:
                                ordinal = dim["ordinal"]
                                if not (type(ordinal) is int or float):
                                    if str(ordinal).isnumeric():
                                        ordinal = int(ordinal)
                                    else:
                                        ordinal = 0
                                else:
                                    ordinal = int(ordinal)  # float -> int, if required

                            dim_idx.append(self._cube.get_dimension_ordinal(dimension))
                            members = []
                            for pos, member_name in enumerate(member_names):
                                if not dimension.member_exists(member_name):
                                    raise TinyOlapViewError(f"Invalid member in view definition. '{member_name}' "
                                                            f"at position {pos} for the 'dimension' at position {ordinal} "
                                                            f"in {axis_name}-axis definition if not a member of dimension "
                                                            f"'{dimension.name}'.")
                                member = dimension.members[member_name]
                                members.append(member)
                        else:
                            # if no members are defined, return the default or all members.
                            if return_all_members_if_no_members_defined:
                                members = list(dimension.members)
                            else:
                                members = [dimension.default_member, ]

                    dimensions.append(dimension)
                    member_lists.append(MemberList(dimension, members))

            return ViewAxis(self, idx=tuple(dim_idx), dimensions=tuple(dimensions), member_lists=tuple(member_lists))
        else:
            # no filter in axis defined, that's ok (maybe)
            return ViewAxis(self, [], [], [])

    def _refresh_missing(self, window: ViewWindow):
        """Refreshes missing data for a requested view window"""
        requires_refresh = False
        grid = self._grid
        missing = ViewWindow(-1,-1,-1,-1)

        # evaluate the rows and cols that require refresh
        for row in range(window.top, window.bottom + 1):
            if (missing.top == -1) and (row not in grid):
                missing.top = row
            if row not in grid:
                missing.bottom = row
            elif missing.left == -1:
                for col in range(window.left, window.right + 1):
                    if (missing.left == -1) and (col not in grid[row]):
                        missing.left = col
                    if col not in grid[row]:
                        missing.right = col

        missing = window.intersect(missing)
        if missing:
            self.refresh(window=missing)

    def refresh(self, read_comments: bool = True, window: ViewWindow = None) -> View:
        """Refreshes the view from the database."""

        # prepare statistics
        stat = self._statistics
        stat.refresh_duration = time.time()
        stat.executed_cell_requests = self._cube.counter_cell_requests
        stat.executed_cell_aggregations = self._cube.counter_aggregations
        stat.executed_rules = self._cube.counter_rule_requests

        rows = self._row_axis
        cols = self._col_axis
        indent = dict()
        indent_max = [0] * rows._dim_count
        # cells = []
        if not window:
            window = ViewWindow(0, 0, rows.positions_count - 1, cols.positions_count - 1)
        else:
            # ...to ensure that the requested window is not larger than the entire view
            window = window.intersect(ViewWindow(0, 0, rows.positions_count - 1, cols.positions_count - 1))

        # refresh filter axis first to create a FactTableRowSet (for faster querying)
        number_format = ""
        filter_level = 0
        idx_address = [0] * self._cube.dimensions_count

        # if we not already have cached the filter setup and the row_set
        if not self._filter_base_idx_address:
            axis = self._filter_axis
            for d in range(axis._dim_count):
                member = axis.positions[0][d]
                if isinstance(member, Iterable):
                    # todo: support for multi member selection
                    member = member[0]
                idx_address[axis._dim_idx[d]] = member.index
                filter_level += member.level
                if member.format:
                    number_format = member.format
            if axis._dim_count:
                # This can be a very expensive operations (even seconds for very large databases!!!)
                row_set = self._cube._facts.create_row_set(idx_address)
            else:
                row_set = set()
            self._filter_number_format = number_format
            self._filter_level = filter_level
            self._filter_row_set = row_set
            self._filter_base_idx_address = deepcopy(idx_address)
        else:
            number_format = self._filter_number_format
            filter_level = self._filter_level
            row_set = self._filter_row_set
            idx_address = self._filter_base_idx_address

        # refresh rows and columns
        # for row in range(rows.positions_count):
        for row in range(window.top, window.bottom + 1):
            super_level = filter_level
            for d in range(rows._dim_count):
                idx_address[rows._dim_idx[d]] = rows.positions[row][d].index
                member = rows.positions[row][d]
                super_level += member.level
                # prepare indentation
                if member.level > indent_max[d]:
                    indent_max[d] = member.level
                if row not in indent:
                    indent[row] = dict()
                indent[row][d] = member.level
                # if d == 0:
                #     indent.append([member.level])
                # else:
                #     indent[row].append(member.level)

                if member.format:
                    number_format = member.format

            all_zero = True
            # for col in range(cols.positions_count):
            for col in range(window.left, window.right + 1):
                for d in range(cols._dim_count):
                    member = cols.positions[col][d]
                    idx_address[cols._dim_idx[d]] = member.index
                    super_level += member.level
                    if member.format:
                        number_format = member.format

                value = self._cube._get((super_level, tuple(idx_address),), row_set=row_set)

                if type(value) is not float:
                    if value is None:
                        formatted_value = ""
                    else:
                        all_zero = False
                        formatted_value = str(value)
                else:
                    if value != 0.0:
                        all_zero = False

                    if not number_format:
                        formatted_value = self._default_number_format.format(value)
                    else:
                        formatted_value = number_format.format(value)
                if read_comments:
                    view_cell = ViewCell(value, formatted_value, number_format, self._cube.comments[tuple(idx_address)])
                else:
                    view_cell = ViewCell(value, formatted_value, number_format)

                if col == window.left:
                    if row not in self._grid:
                        self._grid[row] = dict()
                self._grid[row][col] = view_cell

                # if col == 0:
                #     cells.append([view_cell, ])
                # else:
                #     cells[row].append(view_cell)

            rows.positions[row].all_zero = all_zero

        # update indentations
        for row, indents in indent.items():
            for d in range(rows.dimensions_count):
                indent[row][d] = indent_max[d] - indent[row][d]

        # for p in range(len(indent)):
        #     for d in range(rows.dimensions_count):
        #         indent[p][d] = indent_max[d] - indent[p][d]
        rows.indentations = indent

        # update statistics
        stat.last_refresh = datetime.now()
        stat.refresh_duration = round(time.time() - stat.refresh_duration, 6)
        stat.executed_cell_requests = self._cube.counter_cell_requests - stat.executed_cell_requests
        stat.executed_cell_aggregations = self._cube.counter_aggregations - stat.executed_cell_aggregations
        stat.executed_rules = self._cube.counter_rule_requests - stat.executed_rules
        stat.rows = rows.positions_count
        stat.columns = cols.positions_count
        stat.cells_count = stat.rows * stat.columns
        stat.row_dimensions = rows._dim_count
        stat.column_dimensions = cols._dim_count

        self._last_refresh = datetime.now()

        return self

    def _create_default_definition(self):
        """Creates a default view definition for the cube.

        The last dimension of the cube will be placed in the column axis of the view,
        the previous last (if such exists) in the row axis and all remaining dimensions
        will be placed in the filter axis. If a randomized view is requested, then dimensions
        will be shuffled and for all filter dimensions a random member will be selected."""

        dimensions = self._cube.dimensions
        ordinal = list(range(len(dimensions)))
        if self._random_view:
            random.shuffle(ordinal)
        remaining = len(dimensions)

        # set up column axis
        if remaining > 0:
            idx = remaining - 1
            self._col_axis = ViewAxis(self, idx=ordinal[idx], dimensions=dimensions[ordinal[idx]],
                                      member_lists=dimensions[ordinal[idx]].members)
            remaining -= 1

        # set up row axis
        if remaining > 0:
            idx = remaining - 1
            self._row_axis = ViewAxis(self, idx=ordinal[idx], dimensions=dimensions[ordinal[idx]],
                                      member_lists=dimensions[ordinal[idx]].members)
            remaining -= 1

        # if required, flip axis to show less members in the columns and more i the rows
        if self._row_axis.positions_count < self._col_axis.positions_count:
            temp = self._row_axis
            self._row_axis = self._col_axis
            self._col_axis = temp

        # set up filter axis
        if remaining > 0:
            if not self._use_first_root_members_for_filters or self._random_view:
                self._filter_axis = ViewAxis(self,
                                             idx=tuple([ordinal[idx] for idx in range(remaining)]),
                                             dimensions=tuple([dimensions[ordinal[idx]] for idx in range(remaining)]),
                                             member_lists=tuple(
                                                 [MemberList(dimension=dimensions[ordinal[idx]],
                                                             members=random.choice(dimensions[ordinal[idx]].members))
                                                  for idx in range(remaining)]))
            else:
                self._filter_axis = ViewAxis(self,
                                             idx=tuple([ordinal[idx] for idx in range(remaining)]),
                                             dimensions=tuple([dimensions[ordinal[idx]] for idx in range(remaining)]),
                                             member_lists=tuple(
                                                 [MemberList(dimension=dimensions[ordinal[idx]],
                                                             members=dimensions[ordinal[idx]].members[0])
                                                  for idx in range(remaining)]))

        # create report definition
        definition = {
            "contentType": Config.ContentTypes.VIEW_DEFINITION,
            "version": Config.VERSION,
            "database": str(self.cube.database.name),
            "cube": self._cube.name,
            "name": self._name,
            "title": self.title,
            "description": self.description,
            "zeroSuppressionOnRows": self.zero_suppression_on_rows,
            "zeroSuppressionOnColumns": self.zero_suppression_on_columns,
            "filters": self._filter_axis.to_dict(),
            "rows": self._row_axis.to_dict(),
            "columns": self._col_axis.to_dict(),
        }
        self._definition = definition

    def to_console_output(self, hide_zeros: bool = True, window: ViewWindow = None) -> str:
        """Renders the view suitable for console output. The output contains
        control characters and color definitions and is therefore not suitable
        for other use cases."""
        start = time.time()

        rows = self._row_axis
        cols = self._col_axis
        grid = self._grid

        # ensure that the requested data is avaialble
        if not window:
            window = ViewWindow(0, 0, rows.positions_count - 1, cols.positions_count - 1)
        else:
            # ...to ensure that the requested window is not larger than  the entire view
            window = window.intersect(ViewWindow(0, 0, rows.positions_count - 1, cols.positions_count - 1))
        if not self._last_refresh:
            self.refresh(window=deepcopy(window))
        else:
            self._refresh_missing(window=deepcopy(window))

        text = "\n"
        cell_width = 14
        row_header_width = 16

        row_dims = rows.dimensions_count
        col_dims = cols.dimensions_count

        # title
        title = str(self)
        text += ("-" * 80) + "\n"
        text += f"{title}\n"
        text += ("-" * 80) + "\n"

        # header dimensions
        for member in self._filter_axis.positions[0]:
            text += f"{member.dimension.name} := {member.name}\n"

        # col headers
        for c in range(cols.dimensions_count):
            for r in range(self._row_axis.dimensions_count):
                text += " ".ljust(row_header_width)
            #  for position in self._col_axis.positions:
            for col in range(window.left, window.right + 1):
                position = self._col_axis.positions[col]
                caption = position[c].name
                if len(caption) > cell_width:
                    caption = caption[:cell_width - 3].strip() + "..."
                text += caption.center(cell_width)
            text += "\n"

        # row headers & cells
        previous = {}
        for r in range(window.top, window.bottom + 1):
            for c in range(window.left, window.right + 1):
                value = grid[r][c].value
                if type(value) is float:
                    if hide_zeros and value == 0.0:
                        value = f"-".rjust(cell_width)
                    else:
                        value = f"{value:,.0f}".rjust(cell_width)
                elif value is None:
                    value = f"-".rjust(cell_width)
                else:
                    value = f"{str(value)}".rjust(cell_width)

                comments = grid[r][c].comments
                if comments:
                    value = '\x1b[1;30;47m' + '@' + value[1:] + '\x1b[0m'

                if c == 0:
                    if r > 0:
                        text += "\n"
                    for pos, member in enumerate(self._row_axis.positions[r]):
                        caption = member.name
                        caption = (" " * (member.dimension.get_top_level() - member.level)) + caption
                        if len(caption) > row_header_width:
                            caption = caption[:row_header_width - 3].strip() + "..."
                        if pos in previous:
                            if previous[pos] != member:
                                text += caption.ljust(row_header_width)
                            else:
                                text += " ".ljust(row_header_width)
                        else:
                            text += caption.ljust(row_header_width)
                        previous[pos] = member

                text += value

        duration = time.time() - start
        stat = self._statistics

        statistics = f'\nView ' \
                     f'refreshed in {stat.refresh_duration:.5} sec, ' \
                     f'total time {duration:.5} sec\n' \
                     f'{stat.executed_cell_requests:,} cells, ' \
                     f'{stat.executed_cell_aggregations:,} aggregations, ' \
                     f'{stat.executed_rules:,} rules, ' \
                     f'caching is ' \
                     f'is {"ON" if self.cube.database.caching else "OFF"}, ' \
                     f'zero-suppression ' \
                     f'is {"ON" if self.zero_suppression_on_rows else "OFF"}'

        return text + statistics

    def to_json(self, indent=None) -> str:
        """Converts the current state of the view into a json string. Useful for serialization.
        :param indent: Indentation for json formatting.
        """
        return json.dumps(self.to_dict(), indent=indent)

    def to_dict(self, window: ViewWindow = None) -> dict:
        """Converts the current state of the view into a serializable Python dictionary."""
        """FOR INTERNAL USE! Converts the contents of the attribute field to a dict."""

        rows = self._row_axis
        cols = self._col_axis

        # ensure that the requested data is avaialble
        if not window:
            window = ViewWindow(0, 0, rows.positions_count - 1, cols.positions_count - 1)
        else:
            # ...to ensure that the requested window is not larger than  the entire view
            window = window.intersect(ViewWindow(0, 0, rows.positions_count - 1, cols.positions_count - 1))
        if not self._last_refresh:
            self.refresh(window=deepcopy(window))
        else:
            self._refresh_missing(window=deepcopy(window))

        cells = list()
        for row in range(window.top, window.bottom + 1):
            row_data = {"row": row}
            cell_list = list()
            for col in range(window.left, window.right + 1):
                cell = self._grid[row][col]
                if cell.comments:
                    cell_data = {"row": row, "col": col,
                                 "value": cell.value,
                                 "caption": cell.formatted_value,
                                 "comments": str(cell.comments)}
                else:
                    cell_data = {"row": row, "col": col,
                                 "value": cell.value,
                                 "caption": cell.formatted_value, }
                cell_list.append(cell_data)
            row_data["cells"] = cell_list
            cells.append(row_data)

        return {"contentType": Config.ContentTypes.VIEW,
                "version": Config.VERSION,
                "uid": self.uid,
                "database": str(self.cube.database.name),
                "cube": self._cube.name,
                "name": self._name,
                "title": self.title,
                "description": self.description,
                "zeroSuppressionOnRows": self.zero_suppression_on_rows,
                "zeroSuppressionOnColumns": self.zero_suppression_on_columns,
                "statistics": self._statistics.to_dict(),
                "axes": {
                    "filters": self._filter_axis.to_dict(),
                    "rows": self._row_axis.to_dict(),
                    "columns": self._col_axis.to_dict(),
                },
                "cells": cells
                }

    def to_html(self, endless_loop: bool = False, window: ViewWindow = None) -> str:
        """Converts the current state of the view into a simple static HTML representation."""

        start = time.time()

        rows = self._row_axis
        cols = self._col_axis
        grid = self._grid

        # ensure that the requested data is avaialble
        if not window:
            window = ViewWindow(0, 0, rows.positions_count - 1, cols.positions_count - 1)
        else:
            # ...to ensure that the requested window is not larger than  the entire view
            window = window.intersect(ViewWindow(0, 0, rows.positions_count - 1, cols.positions_count - 1))
        some_content_to_show = bool(window)
        if some_content_to_show:
            if not self._last_refresh:
                self.refresh(window=deepcopy(window))
            else:
                self._refresh_missing(window=deepcopy(window))

        tro = "<tr>"
        trc = "</tr>\n"
        table = ""

        # header dimensions
        table += '<table class="table w-auto"><tbody>\n'
        for position in self._filter_axis.positions:
            for member in position:
                table += f'<tr><td>{member.dimension.name}</td><th scope="row">{member.name}</th></tr>\n'
        table += '</tbody></table>'

        table += '<div style= width: 100%">'
        table += '<div class"table-responsive">' \
                 '<table class="table table-hover table-striped table-bordered"' \
                 '>\n'
        table += '<thead">\n'

        zero_rows = 0
        if some_content_to_show:

            row_dims = self._row_axis.dimensions_count
            col_dims = self._col_axis.dimensions_count

            # column headers
            dim_names_inserted = False
            table += tro
            for c in range(col_dims):
                for r in range(row_dims):
                    if dim_names_inserted:
                        table += f'<th scope="col" class="th-lg" style="width: 80px"></th>\n'
                    else:
                        dim_names = ", ".join(
                            "→" + dimension.name for dimension in self._col_axis.dimensions)  # .definition["columns"])
                        dim_names = dim_names + "</br>" + ", ".join(
                            "↓" + dimension.name for dimension in self._row_axis.dimensions)  # self.definition["rows"])
                        table += f'<td scope="col" class="td-lg" style="width: 80px">{dim_names}</td>\n'
                        dim_names_inserted = True
                for d in range(self._col_axis.dimensions_count):
                    for col in range(window.left, window.right + 1):
                        position = self._col_axis.positions[col]
                        table += f'<th scope="col" class="text-center" style="width: 80px">' \
                                 f'{position[d].name}' \
                                 f'</th>\n'

            table += trc
            table += '</thead">\n'

            # row headers and cells
            previous = {}
            table += tro
            rows = self._row_axis
            cols = self._col_axis

            # for row in range(rows.positions_count):
            for row in range(window.top, window.bottom + 1):
                hide_this_row = self.zero_suppression_on_rows and rows.positions[row].all_zero
                if hide_this_row:
                    zero_rows += 1
                else:
                    # for col in range(cols.positions_count):
                    for col in range(window.left, window.right + 1):
                        cell = self._grid[row][col]
                        # cell = self._cells[row][col]

                        # row headers
                        if col == window.left:
                            if row > window.top:
                                table += trc
                                table += tro

                            for pos, member in enumerate(rows.positions[row]):
                                if pos in previous:
                                    if previous[pos] != member.name:
                                        indentation = rows.indentations[row][pos]
                                        indent = "   " * indentation  # member.level
                                        table += f'<th class="text-nowrap" scope="row">{indent + member.name}</th>\n'
                                    else:
                                        table += f'<th class="text-nowrap" scope="row"></th>\n'
                                else:
                                    indentation = rows.indentations[row][pos]
                                    indent = "   " * indentation  # member.level
                                    table += f'<th class="text-nowrap" scope="row">{indent + member.name}</th>\n'
                                previous[pos] = member.name

                        value = cell.value
                        formatted_value = cell.formatted_value
                        negative = False
                        if type(value) is float:
                            negative = (value < 0.0)
                        if negative:
                            color = "DarkRed"
                        else:
                            color = "Black"

                        if cell.comments:
                            back_color = "Cornsilk"  # "#FFF8DC"
                            table += f'<td class="text-nowrap" style="text-align: right; ' \
                                     f'color:{color}; background-color:{back_color};" ' \
                                     f'data-toggle="tooltip" data-placement="top" title="{str(cell.comments)}">' \
                                     f'{formatted_value}</td>\n'
                        else:
                            back_color = "White"
                            table += f'<td class="text-nowrap" style="text-align: right; ' \
                                     f'color:{color}; background-color;{back_color};">{formatted_value}</td>\n'

            table += trc
        table += "</table></div>"

        # title
        title = ""
        if self.title:
            title = f"<h2>{self.title} on cube '{self.cube.database.name}:{self.cube.name}'</h2>\n"
            if self.description:
                table += f"<h4>{self.description}</h4>\n"

        stat = self.statistics
        duration = time.time() - start
        statistics = f'<div class="font-italic font-weight-light">View ' \
                     f'refreshed in {stat.refresh_duration:.5} sec, ' \
                     f'age of view {str(datetime.now() - self.statistics.last_refresh)}, ' \
                     f'total time {duration:.5} sec</div>' \
                     f'<div class="font-italic font-weight-light">' \
                     f'{stat.executed_cell_requests:,} cells, ' \
                     f'{stat.executed_cell_aggregations:,} aggregations, ' \
                     f'{stat.executed_rules:,} rules, ' \
                     f'caching is ' \
                     f'is {"ON" if self.cube.database.caching else "OFF"}, ' \
                     f'zero-suppression ' \
                     f'is {"ON" if self.zero_suppression_on_rows else "OFF"}, ' \
                     f'{zero_rows:,} rows suppressed.</div>'

        duration = f'<div class="font-italic font-weight-light">HTML rendered in {duration:.5} sec, ' \
                   f'total time {duration + stat.refresh_duration:.5} sec.' \
                   f'</div>'

        loop = ""
        if endless_loop:
            loop = """<script>window.location.reload();</script>"""

        html = f'<!doctype html>' \
               f'<html lang="en">' \
               f'<head><!-- Required meta tags -->' \
               f'<meta charset="utf-8">' \
               f'<meta name="viewport" content="width=device-width, initial-scale=1">' \
               f'<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"/> ' \
               f'<title>TinyOlap API (sample random view)</title>' \
               f'<style></style>' \
               f'</head>' \
               f'<body>' \
               f'<div class="p-3">' \
               f'{title}' \
               f'{statistics}' \
               f'{table}' \
               f'</div>' \
               f'{duration}' \
               f'{loop}' \
               f'</body></html>'
        return html

class ViewList:
    """
    Represents a list of views.
    """

    def __init__(self, cube):
        self._cube = cube
        self._views = HybridDict[View]()

    def __len__(self):
        return len(self._views)

    def __iter__(self):
        for view in self._views:
            yield view

    def __getitem__(self, item):
        return self._views[item]

    def __contains__(self, item):
        return item in self._views

    @property
    def cube(self):
        return self._cube

    def create(self, name: str, definition= None, random_view_layout: bool = False) -> View:
        """
        Creates a new (default) view. A view can afterwards be modified and saved.
        :param definition: The definition that defines the layout of the view.
        :param name: Name of the view to be created.
        :param random_view_layout: (optional) flag that identifies to create random layout for the view.
            Valuable for testing or demo purposes mainly.
        :return: The newly created view.
        """
        if name in self._views:
            raise KeyError(f"Failed to create view. A view named '{name}' already exists.")
        view = View(cube=self._cube, name=name, definition=definition, random_view=random_view_layout)
        self._views.append(view)
        return view

    def add(self, view: View) -> View:
        if view.name in self._views:
            raise KeyError(f"Failed to add view. A view named '{view.name}' already exists.")
        self._views.append(view)
        return view


from datetime import timedelta
import airflow
from airflow import DAG
from airflow.operators.bash_operator import BashOperator
from airflow.contrib.operators.bigquery_operator import BigQueryOperator
# from airflow.contrib.operators import bigquery_operator

default_args={
    'owner': 'airflow',
    'start_date': airflow.utils.dates.days_ago(2),
    'depends_on_past': False,
    'email': ['airflow@example.com'],
    'email_on_failure': False,
    'email_on_retry': False,
    'retries': 1,
    'retry_delay':timedelta(seconds=20)
}

dag = DAG('dm_dag_1_v1_1_1',
        default_args=default_args,
        description='Datamart Template DAG',
        schedule_interval=timedelta(minutes=5)
        )

display_msg = BashOperator(task_id='print_msg',
                    bash_command='echo "Connecting Bigquery ..............."' ,
                    dag=dag)

delay_wait = BashOperator(task_id='Sleeping',
                    bash_command='sleep 5m' ,
                    dag=dag)

# delay_wait_2 = BashOperator(task_id='Sleeping_2',
#                     bash_command='sleep 20s' ,
#                     dag=dag)

run_bigquery = BigQueryOperator(
    task_id='get_sample_data',
    sql='bql/bigquery_sample.sql',
    use_legacy_sql=False,
    dag=dag
)
display_msg >> delay_wait  >> run_bigquery


import numpy as np
import torch.nn as nn
from mmcv.cnn import normal_init

from mmpose.core.evaluation.top_down_eval import keypoints_from_heatmaps
from mmpose.core.evaluation import pose_pck_accuracy
from mmpose.core.post_processing import flip_back
from mmpose.models.builder import HEADS, build_loss

from .utils.tokenbase import TokenPose_TB_base

BN_MOMENTUM = 0.1

@HEADS.register_module()
class TokenPoseHead(nn.Module):
    """
    "TokenPose: Learning Keypoint Tokens for Human Pose Estimation".

    Args:
        in_channels (int): Number of input channels
        num_joints (int): Number of joints
        loss_keypoint (dict): Config for keypoint loss. Default: None.
        tokenpose_cfg (dict): Config for tokenpose.
    """

    def __init__(self,
                 in_channels,
                 num_joints,
                 loss_keypoint=None,
                 tokenpose_cfg=None,
                 train_cfg=None,
                 test_cfg=None):
        super().__init__()

        self.in_channels = in_channels
        self.num_joints = num_joints

        self.loss = build_loss(loss_keypoint)

        self.train_cfg = {} if train_cfg is None else train_cfg
        self.test_cfg = {} if test_cfg is None else test_cfg
        self.target_type = self.test_cfg.get('target_type', 'GaussianHeatmap')

        self.tokenpose_cfg = {} if tokenpose_cfg is None else tokenpose_cfg

        self.tokenpose = TokenPose_TB_base(feature_size=tokenpose_cfg.feature_size,
                                           patch_size=tokenpose_cfg.patch_size,
                                           num_keypoints=self.num_joints,
                                           dim=tokenpose_cfg.dim,
                                           depth=tokenpose_cfg.depth,
                                           heads=tokenpose_cfg.heads,
                                           mlp_ratio=tokenpose_cfg.mlp_ratio,
                                           heatmap_size=tokenpose_cfg.heatmap_size,
                                           channels=in_channels,
                                           pos_embedding_type=tokenpose_cfg.pos_embedding_type,
                                           apply_init=tokenpose_cfg.apply_init)

    def forward(self, x):
        """Forward function."""
        if isinstance(x, list):
            x = x[0]
        x = self.tokenpose(x)
        return x

    def get_loss(self, output, target, target_weight):
        """Calculate top-down keypoint loss.

        Note:
            - batch_size: N
            - num_keypoints: K
            - heatmaps height: H
            - heatmaps weight: W

        Args:
            output (torch.Tensor[N,K,H,W]): Output heatmaps.
            target (torch.Tensor[N,K,H,W]): Target heatmaps.
            target_weight (torch.Tensor[N,K,1]):
                Weights across different joint types.
        """
        losses = dict()

        assert not isinstance(self.loss, nn.Sequential)
        assert target.dim() == 4 and target_weight.dim() == 3

        losses['heatmap_loss'] = self.loss(output.pred, target, target_weight)

        return losses

    def get_accuracy(self, output, target, target_weight):
        """Calculate accuracy for top-down keypoint loss.

        Note:
            - batch_size: N
            - num_keypoints: K
            - heatmaps height: H
            - heatmaps weight: W

        Args:
            output (torch.Tensor[N,K,H,W]): Output heatmaps.
            target (torch.Tensor[N,K,H,W]): Target heatmaps.
            target_weight (torch.Tensor[N,K,1]):
                Weights across different joint types.
        """

        accuracy = dict()

        if self.target_type == 'GaussianHeatmap':
            _, avg_acc, _ = pose_pck_accuracy(
                output.pred.detach().cpu().numpy(),
                target.detach().cpu().numpy(),
                target_weight.detach().cpu().numpy().squeeze(-1) > 0)
            accuracy['acc_pose'] = float(avg_acc)

        return accuracy

    def inference_model(self, x, flip_pairs=None):
        """Inference function.

        Returns:
            output_heatmap (np.ndarray): Output heatmaps.

        Args:
            x (torch.Tensor[N,K,H,W]): Input features.
            flip_pairs (None | list[tuple]):
                Pairs of keypoints which are mirrored.
        """
        output = self.forward(x)
        output = output.pred

        if flip_pairs is not None:
            output_heatmap = flip_back(
                output.detach().cpu().numpy(),
                flip_pairs,
                target_type=self.target_type)
            # feature is not aligned, shift flipped heatmap for higher accuracy
            if self.test_cfg.get('shift_heatmap', False):
                output_heatmap[:, :, :, 1:] = output_heatmap[:, :, :, :-1]
        else:
            output_heatmap = output.detach().cpu().numpy()
        return output_heatmap

    def decode(self, img_metas, output, **kwargs):
        """Decode keypoints from heatmaps.

        Args:
            img_metas (list(dict)): Information about data augmentation
                By default this includes:

                - "image_file: path to the image file
                - "center": center of the bbox
                - "scale": scale of the bbox
                - "rotation": rotation of the bbox
                - "bbox_score": score of bbox
            output (np.ndarray[N, K, H, W]): model predicted heatmaps.
        """
        batch_size = len(img_metas)

        if 'bbox_id' in img_metas[0]:
            bbox_ids = []
        else:
            bbox_ids = None

        c = np.zeros((batch_size, 2), dtype=np.float32)
        s = np.zeros((batch_size, 2), dtype=np.float32)
        image_paths = []
        score = np.ones(batch_size)
        for i in range(batch_size):
            c[i, :] = img_metas[i]['center']
            s[i, :] = img_metas[i]['scale']
            image_paths.append(img_metas[i]['image_file'])

            if 'bbox_score' in img_metas[i]:
                score[i] = np.array(img_metas[i]['bbox_score']).reshape(-1)
            if bbox_ids is not None:
                bbox_ids.append(img_metas[i]['bbox_id'])

        preds, maxvals = keypoints_from_heatmaps(
            output,
            c,
            s,
            unbiased=self.test_cfg.get('unbiased_decoding', False),
            post_process=self.test_cfg.get('post_process', 'default'),
            kernel=self.test_cfg.get('modulate_kernel', 11),
            valid_radius_factor=self.test_cfg.get('valid_radius_factor',
                                                  0.0546875),
            use_udp=self.test_cfg.get('use_udp', False),
            target_type=self.test_cfg.get('target_type', 'GaussianHeatmap'))

        all_preds = np.zeros((batch_size, preds.shape[1], 3), dtype=np.float32)
        all_boxes = np.zeros((batch_size, 6), dtype=np.float32)
        all_preds[:, :, 0:2] = preds[:, :, 0:2]
        all_preds[:, :, 2:3] = maxvals
        all_boxes[:, 0:2] = c[:, 0:2]
        all_boxes[:, 2:4] = s[:, 0:2]
        all_boxes[:, 4] = np.prod(s * 200.0, axis=1)
        all_boxes[:, 5] = score

        result = {}

        result['preds'] = all_preds
        result['boxes'] = all_boxes
        result['image_paths'] = image_paths
        result['bbox_ids'] = bbox_ids

        return result

    def init_weights(self):
        # normal_init(self.fc, mean=0, std=0.01, bias=0)
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                # nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                nn.init.normal_(m.weight, std=0.001)
                # nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.ConvTranspose2d):
                nn.init.normal_(m.weight, std=0.001)
                if self.deconv_with_bias:
                    nn.init.constant_(m.bias, 0)


import argparse
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
from telegram import Bot, Update, ReplyKeyboardMarkup
import os
from news_collector import start_news_collector
from telegram.error import (TelegramError, Unauthorized, BadRequest,
                            TimedOut, ChatMigrated, NetworkError)
import logging

logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

collector = start_news_collector()

def parse_args():
    parser = argparse.ArgumentParser(
        description="PyNewsBot v.0.0.1\nThis bot's job is to "
                    "provide you with fresh news about Python"
                    "and he loves it!")
    parser.add_argument('-v', '--verbose', action="store_true", help='Verbose mode')
    parser.add_argument('-s', '--silent', action="store_true", help='Silent mode')
    return parser.parse_args()

def get_random_fresh_post():
    return collector.get_random_fresh_post()

def start(bot: Bot, update: Update):
    keyboard = [
        ['/get_news'],
    ]
    bot.send_message(chat_id=update.message.chat_id,
                     text='I am the PyNewsBot and I am here'
                          'to provide fresh news about Python.',
                     reply_markup=ReplyKeyboardMarkup(keyboard=keyboard))

def unknown(bot: Bot, update: Update):
    bot.send_message(chat_id=update.message.chat_id,
                     text="Sorry, but I can't help you with that -_-")

def echo(bot: Bot, update: Update):
    bot.send_message(chat_id=update.message.chat_id,
                     reply_to_message_id=update.message.message_id,
                     text=update.message.text)

def get_news(bot: Bot, update: Update):
    post = get_random_fresh_post()
    message = '{}{}\n{}'.format(post.description[:250].replace('<br>', '\n'), '...',
                                post.url)
    bot.send_message(chat_id=update.message.chat_id, text=message)

def error_callback(bot, update, error):
    logger = logging.getLogger()
    try:
        raise error
    except Unauthorized as e:
        logger.error('Chat_id:{}\nUser message:{}\nError message:\n{}\n'.format(
            update.message.chat_id,
            update.message.text,
            e.message)
        )
    except BadRequest as e:
        logger.error('Chat_id:{}\nUser message:{}\nError message:\n{}\n'.format(
            update.message.chat_id,
            update.message.text,
            e.message)
        )
    except TimedOut as e:
        logger.error('Chat_id:{}\nUser message:{}\nError message:\n{}\n'.format(
            update.message.chat_id,
            update.message.text,
            e.message)
        )
    except NetworkError as e:
        logger.error('Chat_id:{}\nUser message:{}\nError message:\n{}\n'.format(
            update.message.chat_id,
            update.message.text,
            e.message)
        )
    except ChatMigrated as e:
        logger.error('Chat_id:{}\nUser message:{}\nError message:\n{}\n'.format(
            update.message.chat_id,
            update.message.text,
            e.message)
        )
    except TelegramError as e:
        logger.error('Chat_id:{}\nUser message:{}\nError message:\n{}\n'.format(
            update.message.chat_id,
            update.message.text,
            e.message)
        )

if __name__ == '__main__':
    args = parse_args()
    logger = logging.getLogger()
    if args.silent:
        logger.setLevel(logging.FATAL)
    else:
        if args.verbose:
            logger.setLevel(logging.DEBUG)
    token = os.environ.get('TELEGRAM_BOT_API_TOKEN', None)
    if not token:
        raise EnvironmentError(
            'Need to export your telegram bot token as TELEGRAM_BOT_API_TOKEN enviroment variable.')
    updater = Updater(token=token)
    dispatcher = updater.dispatcher
    start_handler = CommandHandler('start', start)
    get_news_handler = CommandHandler('get_news', get_news)
    echo_handler = MessageHandler(Filters.text, echo)
    unknown_handler = MessageHandler(Filters.command, unknown)
    dispatcher.add_handler(start_handler)
    dispatcher.add_handler(echo_handler)
    dispatcher.add_handler(get_news_handler)
    dispatcher.add_error_handler(error_callback)
    dispatcher.add_handler(unknown_handler)
    updater.start_polling(bootstrap_retries=5)
    logger.info('Started the bot')


"""
Flask application main module.

Run in development mode:
set FLASK_ENV=development
flask run
"""

from flask import Flask, request, render_template, make_response
from io import BytesIO
from plotting import initialize_matplotlib, generate_plot, close_fig
from solar import compute_orbit

initialize_matplotlib()
app = Flask(__name__) # starts the flask application

@app.route('/')
def main():
    """
    Returns:
        Rendered index page template
    """
    return render_template('index.html')

@app.route('/ss.png')
def image():
    """
    Serves ss.png?date=<date>&n=<n>

    Get parameters:
        date: in the `yyyy-mm-dd` format
        n: limit on the number of planets to render (e.g.,., 4 to go up to Mars)

    Returns:
        Rendered image of the locations and orbits of the
        <n> planets on the given date

    Raises:
        Will raise exceptions if GET parameters are missing or incorrect.
        Warning: no error checks are implemented.
    """
    date = request.args.get('date', type=str)
    n = request.args.get('n', default = 4, type=int)

    fig = generate_plot(date, n)
    fmt = "png"

    # here the generated matplotlib plot is saved into a binary stream
    bio = BytesIO()
    fig.savefig(bio, format=fmt)
    close_fig(fig)

    #... which is then returned along with the HTTP response
    r = make_response(bio.getvalue())
    r.headers['Content-Type'] = 'image/{}'.format(fmt)
    # Note: browser caching was disabled to facilitate testing
    r.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0'
    r.headers['Pragma'] = 'no-cache'
    r.headers['Expires'] = '-1'
    bio.close()
    return r


from typing import Text, Dict, Any, List

from rasa_sdk import Action, Tracker
from rasa_sdk.executor import CollectingDispatcher

from my_fb_api.button import UrlButton
from my_fb_api.button_template import ButtonTemplate
from my_fb_api.horizontal_template import HorizontalTemplate, HorizontalTemplateElement

class ActionTestAnything(Action):
    def name(self) -> Text:
        return 'act_test_anything'

    def run(self, dispatcher: CollectingDispatcher,
            tracker: Tracker,
            domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:
        buttons = []
        elements = []
        buttons.extend([
            UrlButton('webview compact', 'https://9088ec6b.ngrok.io/account/contact_info/1234567/', UrlButton.COMPACT),
            UrlButton('webview tall', 'https://9088ec6b.ngrok.io/account/contact_info/1234567/', UrlButton.TALL),
            UrlButton('webview full', 'https://9088ec6b.ngrok.io/account/contact_info/1234567/', UrlButton.FULL),
        ])
        elements.extend([
            HorizontalTemplateElement(
                image_url='https://www.w3schools.com/w3css/img_lights.jpg',
                title='Generic template', subtitle='subtitle',
                default_action=HorizontalTemplateElement.DefaultAction(
                    'https://9088ec6b.ngrok.io/account/contact_info/1234567/'),
                list_buttons=buttons
            )
        ])
        template = HorizontalTemplate(elements)
        a = {"messages": [{"attachment": {"type": "template",
                                          "payload": {"template_type": "generic", "image_aspect_ratio": "square",
                                                      "elements": [
                                                          {"title": "Welcome!", "subtitle": "Choose your preferences",
                                                           "buttons": [{"type": "web_url",
                                                                        "url": "https://discreet-attic.glitch.me/show-webview",
                                                                        "title": "Webview (compact)",
                                                                        # "messenger_extensions": True,
                                                                        "webview_height_ratio": "compact"},
                                                                       {"type": "web_url",
                                                                        "url": "https://discreet-attic.glitch.me/show-webview",
                                                                        "title": "Webview (tall)",
                                                                        # "messenger_extensions": True,
                                                                        "webview_height_ratio": "tall"},
                                                                       {"type": "web_url",
                                                                        "url": "https://discreet-attic.glitch.me/show-webview",
                                                                        "title": "Webview (full)",
                                                                        # "messenger_extensions": True,
                                                                        "webview_height_ratio": "full"}]}]}}}]}
        dispatcher.utter_message(json_message=template.to_json_message())

        # print(template.to_json_message())
        print(a)
        return []


#PyTorch lib
import torch
import torch.nn as nn
from torch.autograd import Variable
import torch.utils.data as Data
import torch.nn.functional as F
import torchvision
#Tools lib
import numpy as np
import cv2
import random
import time
import os
import argparse
from skimage.metrics import peak_signal_noise_ratio, structural_similarity

def calc_psnr(im1, im2):
    im1_y = cv2.cvtColor(im1, cv2.COLOR_BGR2YCR_CB)[:, :, 0]
    im2_y = cv2.cvtColor(im2, cv2.COLOR_BGR2YCR_CB)[:, :, 0]
    return peak_signal_noise_ratio(im1_y, im2_y)

def calc_ssim(im1, im2):
    im1_y = cv2.cvtColor(im1, cv2.COLOR_BGR2YCR_CB)[:, :, 0]
    im2_y = cv2.cvtColor(im2, cv2.COLOR_BGR2YCR_CB)[:, :, 0]
    return structural_similarity(im1_y, im2_y)

def get_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("--input_dir", default='./test_results/deraindrop', type=str)
    parser.add_argument("--gt_dir", default='./demo_samples/deraindrop', type=str)
    args = parser.parse_args()
    return args

def align_to_four(img):
    a_row = int(img.shape[0]/4)*4
    a_col = int(img.shape[1]/4)*4
    img = img[0:a_row, 0:a_col]
    return img

if __name__ == '__main__':
    args = get_args()

    input_list = sorted(os.listdir(args.input_dir))
    gt_list = sorted(os.listdir(args.gt_dir))
    num = len(input_list)
    cumulative_psnr = 0
    cumulative_ssim = 0
    for i in range(num):
        print('Processing image: %s'%(input_list[i]))
        img = cv2.imread(os.path.join(args.input_dir, input_list[i]))
        gt = cv2.imread(os.path.join(args.gt_dir, gt_list[i]))
        img = align_to_four(img)
        gt = align_to_four(gt)
        result = img
        # result = np.array(result, dtype = 'uint8')
        cur_psnr = calc_psnr(result, gt)
        cur_ssim = calc_ssim(result, gt)
        print('PSNR is %.4f and SSIM is %.4f'%(cur_psnr, cur_ssim))
        cumulative_psnr += cur_psnr
        cumulative_ssim += cur_ssim
    print('In testing dataset, PSNR is %.4f and SSIM is %.4f'%(cumulative_psnr/num, cumulative_ssim/num))


from itertools import permutations

nums = sorted(list(permutations([1,2,3,4,5,6,7,8,9],3)))
n = int(input())
strings = [input() for _ in range(n)]
ans_cnt = 0
for i in nums:
    num = ""
    for j in i:
        num+=str(j)
    flag = 0
    for j in strings:
        wnum,ws,wb = map(int, j.split())
        wnum = str(wnum)
        s=0
        b=0
        for k in range(3):
            if num[k]==wnum[k]:
                s+=1
            elif num[k] in wnum:
                b+=1
        if ws != s or wb != b:
            flag = 1
    if flag==0:
        ans_cnt+=1
print(ans_cnt)


from .master.autonomous_manager import AutonomousManager
from .template.machine import Machine

from .utils.logger import Logger

class MasterManager(Machine):

    def __init__(self):
        Machine.__init__(self)

        self.autonomous = AutonomousManager()
        self.transitions = []
        self.logger = Logger(console_log=False, ros_log=True)

        # Initialization
        self.initial_state = self.autonomous
        self.current_state = self.initial_state

    def run(self, vehicle):
        sc = self.get_state_path()
        Machine.run(self, vehicle)
        sn = self.get_state_path()
        self.logger.log(sc, sn, vehicle)

    def get_state_path(self):
        state = self.current_state
        state_path = [state.string()]

        while isinstance(state, Machine):
            state = state.current_state
            state_path.append(state.string())

        return state_path


import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)

from sklearn import preprocessing
from sklearn.preprocessing import MaxAbsScaler
from sklearn.linear_model import ElasticNetCV
from sklearn.base import BaseEstimator, ClassifierMixin

class Model4(BaseEstimator, ClassifierMixin):

    def __init__(self, disable=False):
        self.name = 'ElasticNetCV Model'
        self.disable = disable

    def transform(self, data):
        # features = ['X314', 'X0_6', 'X261']
        # data = data[features]
        if not hasattr(self, 'scaler'):
            self.scaler = MaxAbsScaler().fit(data)
        data = pd.DataFrame(self.scaler.transform(data))
        # features = [ 20, 146, 147, 183, 193, 265, 306, 398, 411]
        # features = [0, 2, 3, 5, 15, 16, 21, 27, 28, 29, 33, 38, 40, 41, 43, 45, 47,
        #             56, 61, 62, 63, 67, 71, 77, 82, 88, 99, 104, 107, 113, 115, 117, 119, 121, 124, 139,
        #             143, 153, 168, 172, 178, 179, 181, 187, 189, 194, 206, 213,
        #             225, 227, 228, 236, 238, 250, 255, 262, 264, 266, 267, 270, 274, 276, 281, 288,
        #             289, 300, 302, 303, 308, 330, 343, 387, 390, 392,
        #             399, 409, 415, 416, 419, 420, 427, 432,
        #             435, 439, 442, 444, 453, 454, 458, 459]
        # data = data.drop(features, axis=1)
        # features = [
        #     20, 133, 146, 147, 183,
        #     193, 199, 265, 306, 398,
        #     410, 411
        # ]
        # features = [
        #     20, 133, 146, 147, 183,
        #     193, 199, 265, 306, 398,
        #     410, 411
        # ]
        features = [18, 20, 133, 146, 147, 159, 183, 192, 193, 199, 202, 232, 249, 265, 269, 306, 398, 410, 411, 413, 445]
        data = data[features]
        return data

    def fit(self, train, y_train):
        if self.disable:
            print(self.name, 'disabled')
            return
        print('Training', self.name)
        # self.outlier = ModelTest()
        # self.outlier.fit(train, y_train)
        train = self.transform(train.copy())
        y_train = y_train.copy()

        # train['outlier'] = m_test.predict()

        # self.model = ElasticNetCV(l1_ratio=1.0, tol=0.0001)
        # self.model.fit(train, y_train)

        # self.drop_list = []
        # print(self.model.coef_)
        # print(len(self.model.coef_))
        # features = train.columns.values
        # for i in range(len(features)):
        #     if self.model.coef_[i] < 0.00001:
        #         # print(train.columns[i])
        #         self.drop_list.append(features[i])
        # train = train.drop(self.drop_list, axis=1)
        # print(train.shape)

        self.model = ElasticNetCV(l1_ratio=1.0, tol=0.0001)
        self.model.fit(train, y_train)

        # print(self.model.coef_)
        # print(len(self.model.coef_))
        # for i in range(len(train.columns)):
        #     if self.model.coef_[i] > 0.4:
        #         print("    ", train.columns.values[i], self.model.coef_[i])

    def predict(self, test):
        if self.disable or not self.model:
            return np.zeros((test.shape[0],))
        test = self.transform(test.copy())
        return self.model.predict(test)


from collections import defaultdict

def ceildiv(a, b):
    return (a + b - 1) // b

INF = 10**9

def main():
    N, H = map(int, input().split())
    ds = defaultdict(lambda: -INF)  # t -> max d_t
    for n in range(N):
        t, d = map(int, input().split())
        ds[t] = max(ds[t], d)
    # Remove t and d such that t * d does not increase
    mx = -INF
    ts = []
    es = dict()
    for t in sorted(ds.keys()):
        td = t * ds[t]
        if td > mx:
            mx = td
            ts.append(t)
            es[t] = ds[t]
    # e_t --> f_t = max_t'>=t e_t'
    mx = -INF
    us = []
    for t in sorted(es.keys(), reverse=True):
        if es[t] > mx:
            us.append(t)
            mx = es[t]
    us.reverse()

    M = 0
    t = 0
    for u in us:
        f = es[u]
        s = M // f

        # sim <= s
        D = (s - t) * M
        if H <= D:
            ans = ceildiv(H, M) + t
            print(ans)
            return
        H -= D
        t = s

        # sim <= u
        def damages(x):
            return (x + t + 1) * (x - t) // 2 * f
        D = damages(u)
        if H <= D:
            left = t  # NG
            right = u  # OK
            while right - left > 1:
                mid = (left + right) // 2
                if H - damages(mid) <= 0:
                    right = mid
                else:
                    left = mid
            print(right)
            return
        H -= D
        t = u
        M = u * f
    # same as sim <= s
    ans = ceildiv(H, M) + t
    print(ans)
    return

if __name__ == "__main__":
    main()


# -*- coding:utf-8 -*-
__author__ = 'zhengjiali'
__date__ = '2018/4/16 下午5:08'
from django.conf.urls import url

from .views import *
from api.test_views import CaseTestView

urlpatterns = [
    url(r'^query$', PlanQueryView.as_view(),name="query"),#plan列表搜索
    url(r'^(?P<plan_id>\w+)$', get_plan),#获取plan信息接口
    url(r'^add/$',new_plan),#新建计划step1
    url(r'^(?P<plan_id>\w+)/addCase$',plan_addCase), #新建计划step2
    url(r'^delete/$',plan_delete),#删除计划
    url(r'^(?P<plan_id>\w+)/exec$',ExecTask.as_view(),name='execTask'),#异步执行
    url(r'^(?P<plan_id>\w+)/task$',TaskView.as_view()),#同步调用
    url(r'^check/(?P<plan_id>\w+)/$',plan_check),#plan查看url
    url(r'^$',plan_list),
    url(r'^addinfo/$',plan_addinfo),
    url(r'^(?P<plan_id>\w+)/addcase/$', plan_addcase),
    url(r'^(?P<plan_id>\w+)/history/$',get_tasks),
]


import argparse
import calendar
import datetime
import math
from typing import List, Optional

import svgwrite

from .exceptions import PosterError
from .poster import Poster
from .track import Track
from .tracks_drawer import TracksDrawer
from .utils import compute_grid
from .value_range import ValueRange
from .xy import XY

class CircularDrawer(TracksDrawer):
    """Draw a circular Poster for each of the Poster's tracks.

    Attributes:
        _rings: True if distance rings should be drawn, else False.
        _ring_color: Color of distance rings.

    Methods:
        create_args: Set up an argparser for circular poster options.
        fetch_args: Get args from argparser.
        draw: Draw each year on the Poster.
    """

    def __init__(self, the_poster: Poster):
        """Init the CircularDrawer with default values for _rings and _ring_color

        Note that these can be overridden via arguments when calling."""
        super().__init__(the_poster)
        self._rings = False
        self._ring_color = "darkgrey"

    def create_args(self, args_parser: argparse.ArgumentParser):
        """Add arguments to the parser"""
        group = args_parser.add_argument_group("Circular Type Options")
        group.add_argument(
            "--circular-rings",
            dest="circular_rings",
            action="store_true",
            help="Draw distance rings.",
        )
        group.add_argument(
            "--circular-ring-color",
            dest="circular_ring_color",
            metavar="COLOR",
            type=str,
            default="darkgrey",
            help="Color of distance rings.",
        )

    def fetch_args(self, args):
        """Get arguments from the parser"""
        self._rings = args.circular_rings
        self._ring_color = args.circular_ring_color

    def draw(self, dr: svgwrite.Drawing, size: XY, offset: XY):
        """Draw the circular Poster using distances broken down by time"""
        if self.poster.tracks is None:
            raise PosterError("No tracks to draw.")
        if self.poster.length_range_by_date is None:
            return

        years = self.poster.years.count()
        _, counts = compute_grid(years, size)
        if counts is None:
            raise PosterError("Unable to compute grid.")
        count_x, count_y = counts[0], counts[1]
        x, y = 0, 0
        cell_size = size * XY(1 / count_x, 1 / count_y)
        margin = XY(4, 4)
        if count_x <= 1:
            margin.x = 0
        if count_y <= 1:
            margin.y = 0
        sub_size = cell_size - 2 * margin
        for year in range(self.poster.years.from_year, self.poster.years.to_year + 1):
            self._draw_year(dr, sub_size, offset + margin + cell_size * XY(x, y), year)
            x += 1
            if x >= count_x:
                x = 0
                y += 1

    def _draw_year(self, dr: svgwrite.Drawing, size: XY, offset: XY, year: int):
        min_size = min(size.x, size.y)
        outer_radius = 0.5 * min_size - 6
        radius_range = ValueRange.from_pair(outer_radius / 4, outer_radius)
        center = offset + 0.5 * size

        if self._rings:
            self._draw_rings(dr, center, radius_range)

        year_style = f"dominant-baseline: central; font-size:{min_size * 4.0 / 80.0}px; font-family:Arial;"
        month_style = f"font-size:{min_size * 3.0 / 80.0}px; font-family:Arial;"

        dr.add(
            dr.text(
                f"{year}",
                insert=center.tuple(),
                fill=self.poster.colors["text"],
                text_anchor="middle",
                alignment_baseline="middle",
                style=year_style,
            )
        )
        df = 360.0 / (366 if calendar.isleap(year) else 365)
        day = 0
        date = datetime.date(year, 1, 1)
        while date.year == year:
            text_date = date.strftime("%Y-%m-%d")
            a1 = math.radians(day * df)
            a2 = math.radians((day + 1) * df)
            if date.day == 1:
                (_, last_day) = calendar.monthrange(date.year, date.month)
                a3 = math.radians((day + last_day - 1) * df)
                sin_a1, cos_a1 = math.sin(a1), math.cos(a1)
                sin_a3, cos_a3 = math.sin(a3), math.cos(a3)
                r1 = outer_radius + 1
                r2 = outer_radius + 6
                r3 = outer_radius + 2
                dr.add(
                    dr.line(
                        start=(center + r1 * XY(sin_a1, -cos_a1)).tuple(),
                        end=(center + r2 * XY(sin_a1, -cos_a1)).tuple(),
                        stroke=self.poster.colors["text"],
                        stroke_width=0.3,
                    )
                )
                path = dr.path(
                    d=("M", center.x + r3 * sin_a1, center.y - r3 * cos_a1),
                    fill="none",
                    stroke="none",
                )
                path.push(
                    f"a{r3},{r3} 0 0,1 {r3 * (sin_a3 - sin_a1)},{r3 * (cos_a1 - cos_a3)}"
                )
                dr.add(path)
                tpath = svgwrite.text.TextPath(
                    path, date.strftime("%B"), startOffset=(0.5 * r3 * (a3 - a1))
                )
                text = dr.text(
                    "",
                    fill=self.poster.colors["text"],
                    text_anchor="middle",
                    style=month_style,
                )
                text.add(tpath)
                dr.add(text)
            if text_date in self.poster.tracks_by_date:
                self._draw_circle_segment(
                    dr,
                    self.poster.tracks_by_date[text_date],
                    a1,
                    a2,
                    radius_range,
                    center,
                )

            day += 1
            date += datetime.timedelta(1)

    def _determine_ring_distance(self) -> Optional[float]:
        length_range = self.poster.length_range_by_date
        ring_distance = None
        for distance in [1.0, 5.0, 10.0, 50.0]:
            if self.poster.units != "metric":
                # convert from miles to meters
                distance *= 1609.344
            else:
                # convert from km to meters
                distance *= 1000.0
            if length_range.upper() < distance:
                continue
            ring_distance = distance
            if (length_range.upper() / distance) <= 5:
                break
        return ring_distance

    def _draw_rings(self, dr: svgwrite.Drawing, center: XY, radius_range: ValueRange):
        length_range = self.poster.length_range_by_date
        ring_distance = self._determine_ring_distance()
        if ring_distance is None:
            return
        distance = ring_distance
        while distance < length_range.upper():
            radius = (
                radius_range.lower()
                + radius_range.diameter() * distance / length_range.upper()
            )
            dr.add(
                dr.circle(
                    center=center.tuple(),
                    r=radius,
                    stroke=self._ring_color,
                    stroke_opacity="0.2",
                    fill="none",
                    stroke_width=0.3,
                )
            )
            distance += ring_distance

    def _draw_circle_segment(
        self,
        dr: svgwrite.Drawing,
        tracks: List[Track],
        a1: float,
        a2: float,
        rr: ValueRange,
        center: XY,
    ):
        length = sum([t.length for t in tracks])
        has_special = len([t for t in tracks if t.special]) > 0
        color = self.color(self.poster.length_range_by_date, length, has_special)
        r1 = rr.lower()
        r2 = (
            rr.lower()
            + rr.diameter() * length / self.poster.length_range_by_date.upper()
        )
        sin_a1, cos_a1 = math.sin(a1), math.cos(a1)
        sin_a2, cos_a2 = math.sin(a2), math.cos(a2)
        path = dr.path(
            d=("M", center.x + r1 * sin_a1, center.y - r1 * cos_a1),
            fill=color,
            stroke="none",
        )
        path.push("l", (r2 - r1) * sin_a1, (r1 - r2) * cos_a1)
        path.push(f"a{r2},{r2} 0 0,0 {r2 * (sin_a2 - sin_a1)},{r2 * (cos_a1 - cos_a2)}")
        path.push("l", (r1 - r2) * sin_a2, (r2 - r1) * cos_a2)
        dr.add(path)


from django.urls import path
from .views import ListLocationView, DetailLocationView, DetailRegionView, DetailLeisureView, ListPostView, \
    CreatePostView, Profile
from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
    path('posts/', ListPostView.as_view(), name='posts'),
    path('create/', CreatePostView.as_view(template_name='location/add_location.html'), name='create_location'),
    path('', ListLocationView.as_view(), name='locations'),
    path('locations/<int:pk>', DetailLocationView.as_view(), name='location'),
    path('regions/<str:slug>', DetailRegionView.as_view(), name='region'),
    path('leisure/<str:slug>', DetailLeisureView.as_view(), name='leisure'),
    path('profile/', Profile.as_view(), name='profile'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)


from datetime import date
from fastapi import FastAPI, Header
from starlette.responses import JSONResponse
from starlette.responses import Response
from starlette.requests import Request

import base64
import time
import json
import uvicorn
import requests
import logging

import os

COVID_API_KEY = os.environ['COVID_API_KEY']

app = FastAPI()

@app.get("/info")
async def info():
    return JSONResponse({
        "serviceName" : "Covid-19 API",
        "language" : "Python",
        "framework" : "FastAPI"
    } , status_code = 200)

@app.get("/covid-19")
async def get_all():
    url = "https://covid-19-data.p.rapidapi.com/totals"
    headers = {
    'x-rapidapi-key': COVID_API_KEY,
    'x-rapidapi-host': "covid-19-data.p.rapidapi.com"
    }
    response = requests.get(url, headers=headers)
    return response.json()

@app.get("/covid-19/th")
async def get_th():
    current_date = date.today()
    today = current_date.strftime("%Y-%m-%d")
    url = f"https://covid-19-data.p.rapidapi.com/report/country/name?date={today}&name=Thailand"

    headers = {
        'x-rapidapi-key': COVID_API_KEY,
        'x-rapidapi-host': "covid-19-data.p.rapidapi.com"
        }

    response = requests.get(url, headers=headers)
    return JSONResponse(response.json(),status_code = 200)

if __name__ == "__main__":
    uvicorn.run("main:app",host='0.0.0.0',port=8080)