import numpy as np
import cv2

def read_image(img_path: str) -> np.array:
    """
    Читает картинку в np.array
    :param img_path: путь к картинке
    :return:
    """
    # cv2 читает в BGR
    image = cv2.imread(str(img_path))
    return image

def resize_image(img: np.array, max_dim_size: int) -> np.array:
    """
    Сжимает пропорционально картинку, чтобы большая сторона стала равна max_dim_size
    :param img: картинка в np.array
    :param max_dim_size: int
    :return:
    """
    image = np.array(img.copy())
    height, width, colour_layers = image.shape[:3]

    if width > height:
        scale_percent = max_dim_size / width
        new_width = max_dim_size
        new_height = int(height * scale_percent)
    if width <= height:
        scale_percent = max_dim_size / height
        new_width = int(width * scale_percent)
        new_height = max_dim_size

    return cv2.resize(image, (new_width, new_height))

def pad_image(img: np.array, background_color: tuple[int]) -> np.array:
    """
    Относительно центра картинки добавляет поля
    в высоту или ширину делая кадр квадратным
    """
    image = np.array(img.copy())
    height, width, colour_layers = image.shape[:3]

    max_dimm = max(height, width)
    new_height = int(max_dimm)
    new_width = int(max_dimm)

    # пустой кадр, заполненный фоном, куда потом вставим изображение
    empty_frame = np.full((new_height,
                           new_width,
                           colour_layers),
                          background_color,
                          dtype='uint8')

    pad_height = int((new_height - height) / 2)
    pad_width = int((new_width - width) / 2)

    # ищем координаты для вставки
    # в numpy нумерация с верхнего левого угла...
    # потому если мы добавляем сверху, координаты не меняются.
    if pad_height >= 0:
        y0 = 0 + pad_height
        y1 = y0 + height
    if pad_height < 0:
        y0 = 0
        y1 = y0 + height

    if pad_width >= 0:
        x0 = 0 + pad_width
        x1 = x0 + width
    if pad_width < 0:
        x0 = 0
        x1 = x0 + width

    # вставили картинку
    empty_frame[y0:y1, x0:x1] = image

    result = empty_frame

    return result

def process_image(img_path: str) -> np.array:
    """
    Пайплайн обработки зиображения
    :param img_path: путь к картинке
    :return: np.array
    """
    img = read_image(img_path)  # прочитали
    img = resize_image(img, 640)  # сжали/растянули
    img = pad_image(img, (114, 114, 114))  # сделали падинг фоном до квадрата
    img = img.astype(np.float32)  # привели к float32
    # img = cv2.normalize(img, None, 0, 1, cv2.NORM_MINMAX)  # предположение, цвета при обучении были нормализованы
    img = np.transpose(img, (2, 0, 1))  # приводим к виду [idx, colour, h, w]
    img = np.expand_dims(img, axis=0)
    return img


import logging
from typing import Dict
from typing import List
from typing import Set
from typing import Tuple

from django.db import transaction
from django.db.models import Q

from sleuthpr.models import CheckStatus
from sleuthpr.models import ExternalUser
from sleuthpr.models import Installation
from sleuthpr.models import PullRequest
from sleuthpr.models import PullRequestReviewer
from sleuthpr.models import PullRequestStatus
from sleuthpr.models import Repository
from sleuthpr.models import RepositoryCommit
from sleuthpr.models import RepositoryCommitParent
from sleuthpr.models import ReviewState
from sleuthpr.services import external_users
from sleuthpr.services import rules
from sleuthpr.services.scm import Commit
from sleuthpr.triggers import BASE_BRANCH_UPDATED
from sleuthpr.triggers import PR_CREATED
from sleuthpr.triggers import PR_UPDATED
from sleuthpr.triggers import REVIEW_UPDATED
from sleuthpr.triggers import STATUS_UPDATED
from sleuthpr.util import dirty_set_all

logger = logging.getLogger(__name__)

def on_updated(installation: Installation, repository: Repository, pull_request: PullRequest):
    logger.info(f"Updated pull request: {pull_request.remote_id} on {repository.full_name}")

    # refresh files and commits
    refresh_commits(installation, repository, pull_request)

    rules.evaluate(repository, PR_UPDATED, {"pull_request": pull_request})

def on_created(installation: Installation, repository: Repository, pull_request: PullRequest):
    logger.info(f"Created pull request: {pull_request.remote_id} on {repository.full_name}")
    # refresh files and commits

    refresh_commits(installation, repository, pull_request)
    rules.evaluate(repository, PR_CREATED, {"pull_request": pull_request})

def delete(repository: Repository, pull_request: PullRequest):
    pull_request.delete()
    logger.info(f"Deleted pull request: {pull_request.remote_id} on {repository.full_name}")

def update_status(installation: Installation, repository: Repository, context: str, state: CheckStatus, sha: str):
    for pr in repository.pull_requests.filter(source_sha=sha).all():  # type: PullRequest
        status = pr.statuses.filter(context=context).first()
        if status:
            dirty = dirty_set_all(status, dict(state=str(state)))
        else:
            status = PullRequestStatus.objects.create(pull_request=pr, context=context, state=state)
            dirty = True
        if dirty:
            logger.info(
                f"Updated check {status.context} to {status.state} for pull request: {pr.remote_id} "
                f"on {repository.full_name}"
            )
            rules.evaluate(repository, STATUS_UPDATED, {"pull_request": pr, "status": status})

def update_review(
    installation: Installation,
    repository: Repository,
    pull_request: PullRequest,
    reviewer: ExternalUser,
    state: ReviewState,
):
    review: PullRequestReviewer = pull_request.reviewers.filter(user=reviewer).first()
    if review:
        dirty = dirty_set_all(review, dict(state=state))
        if dirty:
            logger.info(
                f"Updated review {reviewer.remote_id} to {state} for pull request: {pull_request.remote_id} "
                f"on {repository.full_name}"
            )
            rules.evaluate(repository, REVIEW_UPDATED, {"pull_request": pull_request, "reviewer": review})
    else:
        logger.error(f"Missing existing pr reviewer for user {reviewer.id} on pr {pull_request.id}")

def on_source_change(installation: Installation, repository: Repository, name: str, sha: str):
    for pull_request in (
        repository.pull_requests.filter(base_branch_name=name).filter(~Q(base_sha=sha)).all()
    ):  # type: PullRequest
        logger.info(
            f"PR's base branch updated, updating pull request: {pull_request.remote_id} " f"on {repository.full_name}"
        )
        rules.evaluate(repository, BASE_BRANCH_UPDATED, {"pull_request": pull_request})

def refresh(installation: Installation, repository: Repository):
    for pull_request in installation.client.get_pull_requests(repository):
        for context, state in installation.client.get_statuses(repository.identifier, pull_request.source_sha):
            status = PullRequestStatus.objects.create(pull_request=pull_request, context=context, state=state)
            rules.evaluate(repository, STATUS_UPDATED, {"pull_request": pull_request, "status": status})

        refresh_commits(installation, repository, pull_request)

        rules.evaluate(repository, PR_CREATED, {"pull_request": pull_request})

def refresh_commits(installation: Installation, repository: Repository, pull_request: PullRequest):
    all_commits: List[Commit] = []
    for commit in installation.client.get_pull_request_commits(repository.identifier, int(pull_request.remote_id)):
        all_commits.append(commit)

    RepositoryCommit.objects.filter(pull_request=pull_request).update(pull_request=None)
    chunk_size = 100
    chunks: List[List[Commit]] = [all_commits[i : i + chunk_size] for i in range(0, len(all_commits), chunk_size)]
    for chunk in chunks:
        changed_shas = add_commits(repository, chunk)
        RepositoryCommit.objects.filter(sha__in=[sha for sha in changed_shas]).update(pull_request=pull_request)

@transaction.atomic
def add_commits(repository: Repository, commits: List[Commit]) -> Set[str]:
    changed_shas = set()

    links: List[Tuple] = []
    all_shas: List[str] = []
    for commit in commits:
        for parent in commit.parents:
            links.append((commit.sha, parent))
            all_shas.append(commit.sha)
            all_shas.append(parent)

    logger.info(f"Passed links: {links}")

    commits_by_child: Dict[str, Commit] = {c.sha: c for c in commits}

    # Find existing RepositoryCommits for both children and parents
    repo_commits = {c.sha: c for c in repository.commits.filter(sha__in=all_shas).all()}

    # Add any missing RepositoryCommits
    for sha in {s for s in all_shas if s not in repo_commits}:
        commit = commits_by_child.get(sha, None)
        if commit:
            author = external_users.get_or_create(
                installation=repository.installation, name=commit.author_name, email=commit.author_email
            )
            committer = external_users.get_or_create(
                installation=repository.installation, name=commit.author_name, email=commit.author_email
            )
            message = commit.message
        else:
            message = author = committer = None

        repo_commit = RepositoryCommit.objects.create(
            repository=repository, sha=sha, message=message, author=author, committer=committer
        )
        repo_commits[repo_commit.sha] = repo_commit
        changed_shas.add(sha)

    # Find existing RepositoryCommitParents
    saved_link_shas = {
        f"{c.child.sha}:{c.parent.sha}": c
        for c in repository.commit_tree.select_related("child", "parent")
        .filter(child__sha__in=[child for child, _ in links])
        .all()
    }

    logger.info(f"existing child:parent -- {[saved_link_shas.keys()]}")

    # Add any missing RepositoryCommitParents
    for child_sha, parent_sha in [link for link in links if not saved_link_shas.get(":".join(link))]:
        RepositoryCommitParent.objects.create(
            repository=repository, parent=repo_commits[parent_sha], child=repo_commits[child_sha]
        )
        logger.info(f"Linking {child_sha} to parent {parent_sha}")
        changed_shas.add(child_sha)

    logger.info(f"Updated {len(changed_shas)} changed shas in the db")
    return changed_shas


from .base import *
DEBUG=True
MEDIA_URL='/media/'
MEDIA_ROOT='media'

STATIC_URL = '/static/'
STATIC_ROOT='staticfiles'

EMAIL_BACKEND = 'sendgrid_backend.SendgridBackend'
SENDGRID_API_KEY = os.environ["SENDGRID_API_KEY"]


from django.shortcuts import render
from .models import Translate

# Create your views here.

def translate_view(request):
    soz = request.GET.get('q','')
    if soz and soz != '':
        natija = Translate.objects.filter(inglizcha__contains=soz).all()[:3]
    else:
        natija = None

    return render(request,'main/translate.html',{'q': soz,'natija': natija})


'''
import random

#Palavra oculta
animais = ['maritaca','cachorro', 'gato', 'papagaio', 'coelho', 'hamster']

animais = random.choice(animais)

palavra_oculta = ''

for letra in animais:
    palavra_oculta += '*'

palavra_oculta = str (input('Advinhe o animal da forca: '))

if animais == palavra_oculta:
    print('voce acertou o animal e: ', animais)
else:
    print('vc errou')
    '''

import random

# lista de palavras
animais = ['maritaca', 'cachorro', 'gato', 'papagaio', 'coelho', 'hamster']

# escolhe uma palavra aleatoriamente
palavra_original = random.choice(animais)

# converte a palavra original em uma palavra oculta
palavra_oculta = '*' * len(palavra_original)

# número máximo de tentativas
max_tentativas = 6

# lista de letras já usadas
letras_usadas = []

# loop principal do jogo
while max_tentativas > 0 and palavra_oculta != palavra_original:
    # exibe a palavra oculta e as letras já usadas
    print('Palavra:', palavra_oculta)
    print('Letras usadas:', letras_usadas)

    # pede ao usuário para adivinhar uma letra
    letra = input('Digite uma letra: ')

    # verifica se a letra já foi usada
    if letra in letras_usadas:
        print('Você já usou essa letra. Tente novamente.')
    else:
        # adiciona a letra à lista de letras usadas
        letras_usadas.append(letra)

        # verifica se a letra está na palavra original
        if letra in palavra_original:
            # atualiza a palavra oculta com a letra adivinhada
            nova_palavra_oculta = ''
            for i in range(len(palavra_original)):
                if palavra_original[i] == letra:
                    nova_palavra_oculta += letra
                else:
                    nova_palavra_oculta += palavra_oculta[i]
            palavra_oculta = nova_palavra_oculta
            print('Boa! A letra', letra, 'está na palavra.')
        else:
            print('Ops! A letra', letra, 'não está na palavra.')
            max_tentativas -= 1

# verifica se o jogador ganhou ou perdeu o jogo
if palavra_oculta == palavra_original:
    print('Parabéns! Você acertou a palavra ', palavra_original)
else:
    print('Que pena! A palavra era ', palavra_original)


import csv
import requests
import re
import os

url = 'https://padax.github.io/taipei-day-trip-resources/taipei-attractions-assignment.json'
response = requests.get(url).json()
posts = response["result"]["results"]

valid_posts = []

for post in posts:
    if int(post["xpostDate"][:4]) >= 2015:
        urls = re.findall('https?://[\w/.-]+\.jpg', post["file"], re.IGNORECASE)
        valid_posts.append([post["stitle"], post["address"][5:8], post["longitude"], post["latitude"], urls[0]])

with open("data.csv", 'w') as file:
    writer = csv.writer(file)

    for post in valid_posts:
        writer.writerow(post)


#!/usr/bin/python3
# -*- coding: utf-8 -*-

import os
import sys

from PyQt5.QtWidgets import QToolBar, QAction, QFileDialog, QMessageBox
from PyQt5.QtGui import QIcon

from localelanguage import tr
from globalvalue import *
from multiencodefile import mcOpen, McFile, openWithEncoding

class ToolBar(QToolBar):
    def __init__(self, parent = None):
        super().__init__(parent)
        self.initUI()

    def initUI(self):
        self.setMovable(False)
        self.setFloatable(False)

def initToolBar(mainWidget):
    openFile = mainWidget.toolBar.addAction(tr(Str_Open))
    openFile.setShortcut(Shortcut_OpenFile)
    openFile.setIcon(QIcon(PathForOpenIcon))
    openFile.triggered.connect(openFileDialog)

    saveFile = mainWidget.toolBar.addAction(tr(Str_Save))
    saveFile.setShortcut(Shortcut_SaveFile)
    saveFile.setIcon(QIcon(PathForSaveIcon))
    saveFile.triggered.connect(saveCurrentFileWithoutAsk)

    togglePW = mainWidget.toolBar.addAction(tr(Str_TogglePreviewWindow))
    togglePW.setShortcut(Shortcut_TogglePW)
    togglePW.setIcon(QIcon(PathForToggleIcon))
    togglePW.triggered.connect(mainWidget.togglePreviewWindow)

    height = mainWidget.toolBar.iconSize().height()
    mainWidget.toolBar.setMaximumHeight(height)

def saveCurrentFile():
    mainWidget = getMainWidget()
    #Check the flag of text changed
    if mainWidget.isTextSaved():
        return
    mainWidget.resetTextSavedFlag()

    #If current file is valid, save it
    curFileInfo = getCurrentFileInfo()
    if curFileInfo != None:
        reply = QMessageBox.question(mainWidget, tr(Str_MessageBoxTitle),
                tr(Str_QuestionForIfSaveFile), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
        if reply == QMessageBox.No:
            return

        with openWithEncoding(curFileInfo.fileName,'w', curFileInfo.encoding) as file:
            data = mainWidget.getPlainText()
            file.write(data)

        return

    #Otherwise check the contents in textedit
    contents = mainWidget.getPlainText()
    if len(contents) < 1:
        return

    reply = QMessageBox.question(mainWidget, tr(Str_MessageBoxTitle),
                tr(Str_QuestionForIfSaveFile), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
    if reply == QMessageBox.No:
        return

    #Save file dialog
    fileName = QFileDialog.getSaveFileName(mainWidget, tr(Str_SaveFileDialogTitle))
    if fileName[0]:
        with openWithEncoding(fileName[0], 'w', DefaultFileEncoding) as file:
            data = mainWidget.getPlainText()
            file.write(data)

def saveCurrentFileWithoutAsk():
    mainWidget = getMainWidget()
    #Check the flag of text changed
    if mainWidget.isTextSaved():
        return

    #If current file is valid, save it
    curFileInfo = getCurrentFileInfo()
    if curFileInfo != None:
        with openWithEncoding(curFileInfo.fileName,'w', curFileInfo.encoding) as file:
            data = mainWidget.getPlainText()
            file.write(data)
        #Reset the flag of text changed
        mainWidget.resetTextSavedFlag()
        return

    #Save file dialog
    fileName = QFileDialog.getSaveFileName(mainWidget, tr(Str_SaveFileDialogTitle))
    if fileName[0]:
        with openWithEncoding(fileName[0], 'w', DefaultFileEncoding) as file:
            data = mainWidget.getPlainText()
            file.write(data)
        mainWidget.resetTextSavedFlag()

def openFileDialog():
    # save the current file
    saveCurrentFile()

    #homePath = 'C:/Users/UserName/'
    mainWidget = getMainWidget()
    fileName = QFileDialog.getOpenFileName(mainWidget, tr(Str_OpenDialogTitle))

    if fileName[0]:
        with mcOpen(fileName[0], 'r') as mcFile:
            # backup the file infomation
            setCurrentFileInfoWithDetail(fileName[0], mcFile.encoding)

            #update the text and the title of the main window
            data = mcFile.file.read()
            mainWidget.setPlainText(data)
            mainWidget.updateWindowTitle()
            mainWidget.resetTextSavedFlag()


from urllib.request import urlopen
import json
mode = input('Driving or walking?')
bestemming = input('Adres:')
bestemming = bestemming.split(',')
print(bestemming)

#url = 'https://maps.googleapis.com/maps/api/distancematrix/json?origins=Oostervenne+239+Purmerend&destinations=rijksdorp+wassenaar&mode=driving&key=-Ls'
url = 'https://maps.googleapis.com/maps/api/distancematrix/json?origins={}+{}+{}&destinations=maastricht+Nederland&mode={}&key=-Ls'.format(bestemming[0],bestemming[1],bestemming[2],mode)
response = urlopen(url)
print(url)
data = json.loads(response.read().decode('utf-8'))
#print(data)
km =  data['rows'][0]['elements'][0]['distance']['text']
km = km[:-2]
print(km)


__all__ = (
    "APIQueryDefinition",
    "SnubaQueryBuilder",
    "SnubaResultConverter",
    "get_date_range",
    "get_intervals",
    "parse_field",
    "parse_query",
    "resolve_tags",
)

import math
from datetime import datetime, timedelta
from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple, Union

from snuba_sdk import Column, Condition, Entity, Function, Granularity, Limit, Offset, Op, Query
from snuba_sdk.conditions import BooleanCondition
from snuba_sdk.orderby import Direction, OrderBy

from sentry.api.utils import InvalidParams, get_date_range_from_params
from sentry.exceptions import InvalidSearchQuery
from sentry.models import Project
from sentry.search.events.builder import UnresolvedQuery
from sentry.sentry_metrics.utils import (
    resolve_tag_key,
    resolve_weak,
    reverse_resolve,
    reverse_resolve_weak,
)
from sentry.snuba.dataset import Dataset
from sentry.snuba.metrics.fields import DerivedMetricExpression, metric_object_factory
from sentry.snuba.metrics.fields.base import (
    generate_bottom_up_dependency_tree_for_metrics,
    get_derived_metrics,
    org_id_from_projects,
)
from sentry.snuba.metrics.naming_layer.mapping import get_mri, get_public_name_from_mri
from sentry.snuba.metrics.query import MetricField
from sentry.snuba.metrics.query import OrderBy as MetricsOrderBy
from sentry.snuba.metrics.query import QueryDefinition, Tag
from sentry.snuba.metrics.utils import (
    ALLOWED_GROUPBY_COLUMNS,
    FIELD_REGEX,
    MAX_POINTS,
    OPERATIONS,
    OPERATIONS_PERCENTILES,
    TS_COL_GROUP,
    TS_COL_QUERY,
    UNALLOWED_TAGS,
    DerivedMetricParseException,
    MetricDoesNotExistException,
)
from sentry.snuba.sessions_v2 import ONE_DAY  # TODO: unite metrics and sessions_v2
from sentry.snuba.sessions_v2 import AllowedResolution, InvalidField, finite_or_none
from sentry.utils.dates import parse_stats_period, to_datetime, to_timestamp
from sentry.utils.snuba import parse_snuba_datetime

def parse_field(field: str, query_params) -> MetricField:
    derived_metrics_mri = get_derived_metrics(exclude_private=True)
    matches = FIELD_REGEX.match(field)
    try:
        if matches is None:
            raise TypeError
        operation = matches[1]
        metric_name = matches[2]
        metric_mri = get_mri(metric_name)
        if metric_mri in derived_metrics_mri and isinstance(
            derived_metrics_mri[metric_mri], DerivedMetricExpression
        ):
            raise DerivedMetricParseException(
                f"Failed to parse {field}. No operations can be applied on this field as it is "
                f"already a derived metric with an aggregation applied to it."
            )
    except (IndexError, TypeError):
        metric_name = field
        metric_mri = get_mri(metric_name)
        if metric_mri in derived_metrics_mri and isinstance(
            derived_metrics_mri[metric_mri], DerivedMetricExpression
        ):
            # The isinstance check is there to foreshadow adding raw metric aliases
            return MetricField(op=None, metric_name=metric_name)
        raise InvalidField(
            f"Failed to parse '{field}'. Must be something like 'sum(my_metric)', or a supported "
            f"aggregate derived metric like `session.crash_free_rate"
        )

    if operation not in OPERATIONS:
        raise InvalidField(
            f"Invalid operation '{operation}'. Must be one of {', '.join(OPERATIONS)}"
        )

    return MetricField(operation, metric_name)

def resolve_tags(org_id: int, input_: Any) -> Any:
    """Translate tags in snuba condition

    This assumes that all strings are either tag names or tag values, so do not
    pass Column("metric_id") or Column("project_id") into this function.

    """
    if isinstance(input_, list):
        return [resolve_tags(org_id, item) for item in input_]
    if isinstance(input_, Function):
        if input_.function == "ifNull":
            # This was wrapped automatically by QueryBuilder, remove wrapper
            return resolve_tags(org_id, input_.parameters[0])
        return Function(
            function=input_.function,
            parameters=input_.parameters
            and [resolve_tags(org_id, item) for item in input_.parameters],
        )
    if isinstance(input_, Condition):
        return Condition(
            lhs=resolve_tags(org_id, input_.lhs), op=input_.op, rhs=resolve_tags(org_id, input_.rhs)
        )
    if isinstance(input_, BooleanCondition):
        return input_.__class__(
            conditions=[resolve_tags(org_id, item) for item in input_.conditions]
        )
    if isinstance(input_, Column):
        # HACK: Some tags already take the form "tags[...]" in discover, take that into account:
        if input_.subscriptable == "tags":
            name = input_.key
        else:
            name = input_.name
        return Column(name=resolve_tag_key(org_id, name))
    if isinstance(input_, str):
        return resolve_weak(org_id, input_)

    return input_

def parse_query(query_string: str) -> Sequence[Condition]:
    """Parse given filter query into a list of snuba conditions"""
    # HACK: Parse a sessions query, validate / transform afterwards.
    # We will want to write our own grammar + interpreter for this later.
    for unallowed_tag in UNALLOWED_TAGS:
        if f"{unallowed_tag}:" in query_string:
            raise InvalidParams(f"Tag name {unallowed_tag} is not a valid query filter")
    try:
        query_builder = UnresolvedQuery(
            Dataset.Sessions,
            params={
                "project_id": 0,
            },
        )
        where, _ = query_builder.resolve_conditions(query_string, use_aggregate_conditions=True)
    except InvalidSearchQuery as e:
        raise InvalidParams(f"Failed to parse query: {e}")

    return where

class APIQueryDefinition:
    """
    This is the definition of the query the user wants to execute.
    This is constructed out of the request params, and also contains a list of
    `fields` and `groupby` definitions as [`ColumnDefinition`] objects.

    Adapted from [`sentry.snuba.sessions_v2`].

    """

    def __init__(self, projects, query_params, paginator_kwargs: Optional[Dict] = None):
        self._projects = projects
        paginator_kwargs = paginator_kwargs or {}

        self.query = query_params.get("query", "")
        self.parsed_query = parse_query(self.query) if self.query else None
        raw_fields = query_params.getlist("field", [])
        self.groupby = query_params.getlist("groupBy", [])

        if len(raw_fields) == 0:
            raise InvalidField('Request is missing a "field"')

        self.fields = []
        for key in raw_fields:
            self.fields.append(parse_field(key, query_params))

        self.orderby = self._parse_orderby(query_params)
        self.limit: Optional[Limit] = self._parse_limit(query_params, paginator_kwargs)
        self.offset = self._parse_offset(query_params, paginator_kwargs)

        start, end, rollup = get_date_range(query_params)
        self.rollup = rollup
        self.start = start
        self.end = end
        self.histogram_buckets = int(query_params.get("histogramBuckets", 100))
        if self.histogram_buckets > 250:
            raise InvalidField(
                "We don't have more than 250 buckets stored for any given metric bucket."
            )
        histogram_from = query_params.get("histogramFrom", None)
        histogram_to = query_params.get("histogramTo", None)
        self.histogram_from = float(histogram_from) if histogram_from is not None else None
        self.histogram_to = float(histogram_to) if histogram_to is not None else None
        self.include_series = query_params.get("includeSeries", "1") == "1"
        self.include_totals = query_params.get("includeTotals", "1") == "1"

        if not (self.include_series or self.include_totals):
            raise InvalidParams("Cannot omit both series and totals")

        # Validates that time series limit will not exceed the snuba limit of 10,000
        self._validate_series_limit(query_params)

    def to_query_definition(self) -> QueryDefinition:
        return QueryDefinition(
            org_id=org_id_from_projects(self._projects),
            project_ids=[project.id for project in self._projects],
            include_totals=self.include_totals,
            include_series=self.include_series,
            select=self.fields,
            start=self.start,
            end=self.end,
            where=self.parsed_query,
            groupby=self.groupby,
            orderby=self.orderby,
            limit=self.limit,
            offset=self.offset,
            granularity=Granularity(self.rollup),
            histogram_buckets=self.histogram_buckets,
            histogram_from=self.histogram_from,
            histogram_to=self.histogram_to,
        )

    def _parse_orderby(self, query_params):
        orderby = query_params.getlist("orderBy", [])
        if not orderby:
            return None
        elif len(orderby) > 1:
            raise InvalidParams("Only one 'orderBy' is supported")

        orderby = orderby[0]
        direction = Direction.ASC
        if orderby[0] == "-":
            orderby = orderby[1:]
            direction = Direction.DESC

        try:
            field = parse_field(orderby, query_params)
        except KeyError:
            # orderBy one of the group by fields may be supported in the future
            raise InvalidParams("'orderBy' must be one of the provided 'fields'")

        return MetricsOrderBy(field, direction)

    def _parse_limit(self, query_params, paginator_kwargs):
        if self.orderby:
            return Limit(paginator_kwargs.get("limit"))
        else:
            per_page = query_params.get("per_page")
            if per_page is not None:
                # If order by is not None, it means we will have a `series` query which cannot be
                # paginated, and passing a `per_page` url param to paginate the results is not
                # possible
                raise InvalidParams("'per_page' is only supported in combination with 'orderBy'")
            return None

    def _parse_offset(self, query_params, paginator_kwargs):
        if self.orderby:
            return paginator_kwargs.get("offset")
        else:
            cursor = query_params.get("cursor")
            if cursor is not None:
                # If order by is not None, it means we will have a `series` query which cannot be
                # paginated, and passing a `per_page` url param to paginate the results is not
                # possible
                raise InvalidParams("'cursor' is only supported in combination with 'orderBy'")
            return None

    def _validate_series_limit(self, query_params):
        if self.limit:
            if (
                self.end - self.start
            ).total_seconds() / self.rollup * self.limit.limit > MAX_POINTS:
                raise InvalidParams(
                    f"Requested interval of {query_params.get('interval', '1h')} with statsPeriod of "
                    f"{query_params.get('statsPeriod')} is too granular for a per_page of "
                    f"{self.limit.limit} elements. Increase your interval, decrease your statsPeriod, "
                    f"or decrease your per_page parameter."
                )

def get_intervals(start: datetime, end: datetime, granularity: int):
    assert granularity > 0
    delta = timedelta(seconds=granularity)
    while start < end:
        yield start
        start += delta

def get_date_range(params: Mapping) -> Tuple[datetime, datetime, int]:
    """Get start, end, rollup for the given parameters.

    Apply a similar logic as `sessions_v2.get_constrained_date_range`,
    but with fewer constraints. More constraints may be added in the future.

    Note that this function returns a right-exclusive date range [start, end),
    contrary to the one used in sessions_v2.

    """
    interval = parse_stats_period(params.get("interval", "1h"))
    interval = int(3600 if interval is None else interval.total_seconds())

    # hard code min. allowed resolution to 10 seconds
    allowed_resolution = AllowedResolution.ten_seconds

    smallest_interval, interval_str = allowed_resolution.value
    if interval % smallest_interval != 0 or interval < smallest_interval:
        raise InvalidParams(
            f"The interval has to be a multiple of the minimum interval of {interval_str}."
        )

    if ONE_DAY % interval != 0:
        raise InvalidParams("The interval should divide one day without a remainder.")

    start, end = get_date_range_from_params(params)

    date_range = end - start

    date_range = timedelta(seconds=int(interval * math.ceil(date_range.total_seconds() / interval)))

    if date_range.total_seconds() / interval > MAX_POINTS:
        raise InvalidParams(
            "Your interval and date range would create too many results. "
            "Use a larger interval, or a smaller date range."
        )

    end_ts = int(interval * math.ceil(to_timestamp(end) / interval))
    end = to_datetime(end_ts)
    start = end - date_range

    # NOTE: The sessions_v2 implementation cuts the `end` time to now + 1 minute
    # if `end` is in the future. This allows for better real time results when
    # caching is enabled on the snuba queries. Removed here for simplicity,
    # but we might want to reconsider once caching becomes an issue for metrics.

    return start, end, interval

class SnubaQueryBuilder:

    #: Datasets actually implemented in snuba:
    _implemented_datasets = {
        "metrics_counters",
        "metrics_distributions",
        "metrics_sets",
    }

    def __init__(self, projects: Sequence[Project], query_definition: QueryDefinition):
        self._projects = projects
        self._query_definition = query_definition
        self._org_id = query_definition.org_id

    def _build_where(self) -> List[Union[BooleanCondition, Condition]]:
        where: List[Union[BooleanCondition, Condition]] = [
            Condition(Column("org_id"), Op.EQ, self._org_id),
            Condition(Column("project_id"), Op.IN, self._query_definition.project_ids),
            Condition(Column(TS_COL_QUERY), Op.GTE, self._query_definition.start),
            Condition(Column(TS_COL_QUERY), Op.LT, self._query_definition.end),
        ]
        filter_ = resolve_tags(self._org_id, self._query_definition.where)
        if filter_:
            where.extend(filter_)

        return where

    def _build_groupby(self) -> List[Column]:
        groupby_cols = []
        for field in self._query_definition.groupby or []:
            if field in UNALLOWED_TAGS:
                raise InvalidParams(f"Tag name {field} cannot be used to groupBy query")
            if field in ALLOWED_GROUPBY_COLUMNS:
                groupby_cols.append(Column(field))
            else:
                assert isinstance(field, Tag)
                groupby_cols.append(Column(resolve_tag_key(self._org_id, field)))
        return groupby_cols

    def _build_orderby(self) -> Optional[List[OrderBy]]:
        if self._query_definition.orderby is None:
            return None
        orderby = self._query_definition.orderby
        op = orderby.field.op
        metric_mri = get_mri(orderby.field.metric_name)
        metric_field_obj = metric_object_factory(op, metric_mri)
        return metric_field_obj.generate_orderby_clause(
            projects=self._projects,
            direction=orderby.direction,
            query_definition=self._query_definition,
        )

    def __build_totals_and_series_queries(
        self, entity, select, where, groupby, orderby, limit, offset, rollup, intervals_len
    ):
        rv = {}
        totals_query = Query(
            dataset=Dataset.Metrics.value,
            match=Entity(entity),
            groupby=groupby,
            select=select,
            where=where,
            limit=limit or Limit(MAX_POINTS),
            offset=Offset(offset or 0),
            granularity=rollup,
            orderby=orderby,
        )

        if self._query_definition.include_totals:
            rv["totals"] = totals_query

        if self._query_definition.include_series:
            series_query = totals_query.set_groupby(
                (totals_query.groupby or []) + [Column(TS_COL_GROUP)]
            )

            # In a series query, we also need to factor in the len of the intervals array
            series_limit = MAX_POINTS
            if limit:
                series_limit = limit.limit * intervals_len
            rv["series"] = series_query.set_limit(series_limit)

        return rv

    def __update_query_dicts_with_component_entities(
        self, component_entities, metric_mri_to_obj_dict, fields_in_entities
    ):
        # At this point in time, we are only supporting raw metrics in the metrics attribute of
        # any instance of DerivedMetric, and so in this case the op will always be None
        # ToDo(ahmed): In future PR, we might want to allow for dependency metrics to also have an
        #  an aggregate and in this case, we would need to parse the op here
        op = None
        for entity, metric_mris in component_entities.items():
            for metric_mri in metric_mris:
                metric_key = (op, metric_mri)
                if metric_key not in metric_mri_to_obj_dict:
                    metric_mri_to_obj_dict[metric_key] = metric_object_factory(op, metric_mri)
                    fields_in_entities.setdefault(entity, []).append(metric_key)
        return metric_mri_to_obj_dict

    def get_snuba_queries(self):
        metric_mri_to_obj_dict = {}
        fields_in_entities = {}

        for field in self._query_definition.select:
            metric_mri = get_mri(field.metric_name)
            metric_field_obj = metric_object_factory(field.op, metric_mri)
            # `get_entity` is called the first, to fetch the entities of constituent metrics,
            # and validate especially in the case of SingularEntityDerivedMetric that it is
            # actually composed of metrics that belong to the same entity
            try:
                #  When we get to an instance of a MetricFieldBase where the entity is an
                #  instance of dict, we know it is from a composite entity derived metric, and
                #  we need to traverse down the constituent metrics dependency tree until we get
                #  to instances of SingleEntityDerivedMetric, and add those to our queries so
                #  that we are able to generate the original CompositeEntityDerivedMetric later
                #  on as a result of a post query operation on the results of the constituent
                #  SingleEntityDerivedMetric instances
                component_entities = metric_field_obj.get_entity(projects=self._projects)
                if isinstance(component_entities, dict):
                    # In this case, component_entities is a dictionary with entity keys and
                    # lists of metric_mris as values representing all the entities and
                    # metric_mris combination that this metric_object is composed of, or rather
                    # the instances of SingleEntityDerivedMetric that it is composed of
                    metric_mri_to_obj_dict = self.__update_query_dicts_with_component_entities(
                        component_entities=component_entities,
                        metric_mri_to_obj_dict=metric_mri_to_obj_dict,
                        fields_in_entities=fields_in_entities,
                    )
                    continue
                elif isinstance(component_entities, str):
                    entity = component_entities
                else:
                    raise DerivedMetricParseException("Entity parsed is in incorrect format")
            except MetricDoesNotExistException:
                # If we get here, it means that one or more of the constituent metrics for a
                # derived metric does not exist, and so no further attempts to query that derived
                # metric will be made, and the field value will be set to the default value in
                # the response
                continue

            if entity not in self._implemented_datasets:
                raise NotImplementedError(f"Dataset not yet implemented: {entity}")

            metric_mri_to_obj_dict[(field.op, metric_mri)] = metric_field_obj
            fields_in_entities.setdefault(entity, []).append((field.op, metric_mri))

        where = self._build_where()
        groupby = self._build_groupby()

        queries_dict = {}
        for entity, fields in fields_in_entities.items():
            select = []
            metric_ids_set = set()
            for field in fields:
                metric_field_obj = metric_mri_to_obj_dict[field]
                select += metric_field_obj.generate_select_statements(
                    projects=self._projects, query_definition=self._query_definition
                )
                metric_ids_set |= metric_field_obj.generate_metric_ids(self._projects)

            where_for_entity = [
                Condition(
                    Column("metric_id"),
                    Op.IN,
                    list(metric_ids_set),
                ),
            ]
            orderby = self._build_orderby()

            queries_dict[entity] = self.__build_totals_and_series_queries(
                entity=entity,
                select=select,
                where=where + where_for_entity,
                groupby=groupby,
                orderby=orderby,
                limit=self._query_definition.limit,
                offset=self._query_definition.offset,
                rollup=self._query_definition.granularity,
                intervals_len=len(
                    list(
                        get_intervals(
                            self._query_definition.start,
                            self._query_definition.end,
                            self._query_definition.granularity.granularity,
                        )
                    )
                ),
            )

        return queries_dict, fields_in_entities

class SnubaResultConverter:
    """Interpret a Snuba result and convert it to API format"""

    def __init__(
        self,
        organization_id: int,
        query_definition: QueryDefinition,
        fields_in_entities: dict,
        intervals: List[datetime],
        results,
    ):
        self._organization_id = organization_id
        self._intervals = intervals
        self._results = results
        self._query_definition = query_definition

        # This is a set of all the `(op, metric_mri)` combinations passed in the query_definition
        self._query_definition_fields_set = {
            (field.op, get_mri(field.metric_name)) for field in query_definition.select
        }
        # This is a set of all queryable `(op, metric_mri)` combinations. Queryable can mean it
        # includes one of the following: AggregatedRawMetric (op, metric_mri), instance of
        # SingularEntityDerivedMetric or the instances of SingularEntityDerivedMetric that are
        # the constituents necessary to calculate instances of CompositeEntityDerivedMetric but
        # are not necessarily requested in the query definition
        self._fields_in_entities_set = {
            elem for fields_in_entity in fields_in_entities.values() for elem in fields_in_entity
        }
        self._set_of_constituent_queries = self._fields_in_entities_set.union(
            self._query_definition_fields_set
        )

        # This basically generate a dependency tree for all instances of `MetricFieldBase` so
        # that in the case of a CompositeEntityDerivedMetric, we are able to calculate it but
        # only after calculating its dependencies
        self._bottom_up_dependency_tree = generate_bottom_up_dependency_tree_for_metrics(
            self._query_definition_fields_set
        )

        self._timestamp_index = {timestamp: index for index, timestamp in enumerate(intervals)}

    def _parse_tag(self, tag_string: str) -> str:
        tag_key = int(tag_string.replace("tags[", "").replace("]", ""))
        return reverse_resolve(tag_key)

    def _extract_data(self, data, groups):
        tags = tuple(
            (key, data[key])
            for key in sorted(data.keys())
            if (key.startswith("tags[") or key in ALLOWED_GROUPBY_COLUMNS)
        )

        tag_data = groups.setdefault(tags, {})
        if self._query_definition.include_series:
            tag_data.setdefault("series", {})
        if self._query_definition.include_totals:
            tag_data.setdefault("totals", {})

        bucketed_time = data.pop(TS_COL_GROUP, None)
        if bucketed_time is not None:
            bucketed_time = parse_snuba_datetime(bucketed_time)

        # We query the union of the query_definition fields, and the fields_in_entities from the
        # QueryBuilder necessary as it contains the constituent instances of
        # SingularEntityDerivedMetric for instances of CompositeEntityDerivedMetric
        for op, metric_mri in self._set_of_constituent_queries:
            key = f"{op}({metric_mri})" if op is not None else metric_mri
            default_null_value = metric_object_factory(
                op, metric_mri
            ).generate_default_null_values()

            try:
                value = data[key]
            except KeyError:
                # This could occur when we have derived metrics that are generated from post
                # query operations, and so don't have a direct mapping to the query results
                # or also from raw_metrics that don't exist in clickhouse yet
                cleaned_value = default_null_value
            else:
                if op in OPERATIONS_PERCENTILES:
                    value = value[0]
                cleaned_value = finite_or_none(value)

            if bucketed_time is None:
                # Only update the value, when either key does not exist or its a default
                if key not in tag_data["totals"] or tag_data["totals"][key] == default_null_value:
                    tag_data["totals"][key] = cleaned_value

            if self._query_definition.include_series:
                if bucketed_time is not None or tag_data["totals"][key] == default_null_value:
                    empty_values = len(self._intervals) * [default_null_value]
                    series = tag_data["series"].setdefault(key, empty_values)

                    if bucketed_time is not None:
                        series_index = self._timestamp_index[bucketed_time]
                        if series[series_index] == default_null_value:
                            series[series_index] = cleaned_value

    def translate_results(self):
        groups = {}
        for entity, subresults in self._results.items():
            for k in "totals", "series":
                if k in subresults:
                    for data in subresults[k]["data"]:
                        self._extract_data(data, groups)

        groups = [
            dict(
                by=dict(
                    (self._parse_tag(key), reverse_resolve_weak(value))
                    if key not in ALLOWED_GROUPBY_COLUMNS
                    else (key, value)
                    for key, value in tags
                ),
                **data,
            )
            for tags, data in groups.items()
        ]

        # Applying post query operations for totals and series
        for group in groups:
            totals = group.get("totals")
            series = group.get("series")

            for op, metric_mri in self._bottom_up_dependency_tree:
                metric_obj = metric_object_factory(op=op, metric_mri=metric_mri)
                grp_key = f"{op}({metric_mri})" if op is not None else metric_mri

                if totals is not None:
                    totals[grp_key] = metric_obj.run_post_query_function(
                        totals, query_definition=self._query_definition
                    )

                if series is not None:
                    # Series
                    for idx in range(0, len(self._intervals)):
                        series.setdefault(
                            grp_key,
                            [metric_obj.generate_default_null_values()] * len(self._intervals),
                        )
                        series[grp_key][idx] = metric_obj.run_post_query_function(
                            series, query_definition=self._query_definition, idx=idx
                        )

        # Remove the extra fields added due to the constituent metrics that were added
        # from the generated dependency tree. These metrics that are to be removed were added to
        # be able to generate fields that require further processing post query, but are not
        # required nor expected in the response
        for group in groups:
            totals = group.get("totals")
            series = group.get("series")

            for key in set(totals or ()) | set(series or ()):
                matches = FIELD_REGEX.match(key)
                if matches:
                    operation = matches[1]
                    metric_mri = matches[2]
                else:
                    operation = None
                    metric_mri = key

                if (operation, metric_mri) not in self._query_definition_fields_set:
                    if totals is not None:
                        del totals[key]
                    if series is not None:
                        del series[key]
                else:
                    public_metric_key = (
                        f"{get_public_name_from_mri(metric_mri)}"
                        if operation is None
                        else f"{operation}({get_public_name_from_mri(metric_mri)})"
                    )
                    if totals is not None:
                        totals[public_metric_key] = totals.pop(key)
                    if series is not None:
                        series[public_metric_key] = series.pop(key)

        return groups


import matplotlib
matplotlib.use('TkAgg')

from matplotlib import pyplot

# 1. Prepare data
labels = ['Android', 'iOS', 'Web']
values = [300, 400, 300]
colors = ['red', 'gold', 'green']
explode = [0, 0.1, 0]

# 2. Plot
pyplot.pie(values, labels = labels, colors = colors, explode = explode, shadow = True)
pyplot.axis('equal')

# 3. Show
pyplot.show()