import argparse
import collections

import numpy as np

import torch
import torch.optim as optim
from torchvision import transforms

from retinanet import model
from retinanet.dataloader import CocoDataset, CSVDataset, collater, Resizer, AspectRatioBasedSampler, Augmenter, \
    Normalizer
from torch.utils.data import DataLoader

from retinanet import coco_eval
from retinanet import csv_eval

assert torch.__version__.split('.')[0] == '1'

print('CUDA available: {}'.format(torch.cuda.is_available()))

def main(args=None):
    parser = argparse.ArgumentParser(description='Simple training script for training a RetinaNet network.')

    parser.add_argument('--dataset', help='Dataset type, must be one of csv or coco.')
    parser.add_argument('--coco_path', help='Path to COCO directory')
    parser.add_argument('--csv_train', help='Path to file containing training annotations (see readme)')
    parser.add_argument('--csv_classes', help='Path to file containing class list (see readme)')
    parser.add_argument('--csv_val', help='Path to file containing validation annotations (optional, see readme)')

    parser.add_argument('--depth', help='Resnet depth, must be one of 18, 34, 50, 101, 152', type=int, default=50)
    parser.add_argument('--epochs', help='Number of epochs', type=int, default=100)

    parser = parser.parse_args(args)

    # Create the data loaders
    if parser.dataset == 'coco':

        if parser.coco_path is None:
            raise ValueError('Must provide --coco_path when training on COCO,')

        dataset_train = CocoDataset(parser.coco_path, set_name='train2017',
                                    transform=transforms.Compose([Normalizer(), Augmenter(), Resizer()]))
        dataset_val = CocoDataset(parser.coco_path, set_name='val2017',
                                  transform=transforms.Compose([Normalizer(), Resizer()]))

    elif parser.dataset == 'csv':

        if parser.csv_train is None:
            raise ValueError('Must provide --csv_train when training on COCO,')

        if parser.csv_classes is None:
            raise ValueError('Must provide --csv_classes when training on COCO,')

        dataset_train = CSVDataset(train_file=parser.csv_train, class_list=parser.csv_classes,
                                   transform=transforms.Compose([Normalizer(), Augmenter(), Resizer()]))

        if parser.csv_val is None:
            dataset_val = None
            print('No validation annotations provided.')
        else:
            dataset_val = CSVDataset(train_file=parser.csv_val, class_list=parser.csv_classes,
                                     transform=transforms.Compose([Normalizer(), Resizer()]))

    else:
        raise ValueError('Dataset type not understood (must be csv or coco), exiting.')

    sampler = AspectRatioBasedSampler(dataset_train, batch_size=2, drop_last=False)
    dataloader_train = DataLoader(dataset_train, num_workers=3, collate_fn=collater, batch_sampler=sampler)

    if dataset_val is not None:
        sampler_val = AspectRatioBasedSampler(dataset_val, batch_size=1, drop_last=False)
        dataloader_val = DataLoader(dataset_val, num_workers=3, collate_fn=collater, batch_sampler=sampler_val)

    # Create the model
    if parser.depth == 18:
        retinanet = model.resnet18(num_classes=dataset_train.num_classes(), pretrained=True)
    elif parser.depth == 34:
        retinanet = model.resnet34(num_classes=dataset_train.num_classes(), pretrained=True)
    elif parser.depth == 50:
        retinanet = model.resnet50(num_classes=dataset_train.num_classes(), pretrained=True)
    elif parser.depth == 101:
        retinanet = model.resnet101(num_classes=dataset_train.num_classes(), pretrained=True)
    elif parser.depth == 152:
        retinanet = model.resnet152(num_classes=dataset_train.num_classes(), pretrained=True)
    else:
        raise ValueError('Unsupported model depth, must be one of 18, 34, 50, 101, 152')

    use_gpu = True

    if use_gpu:
        if torch.cuda.is_available():
            retinanet = retinanet.cuda()

    if torch.cuda.is_available():
        retinanet = torch.nn.DataParallel(retinanet).cuda()
    else:
        retinanet = torch.nn.DataParallel(retinanet)

    retinanet.training = True

    optimizer = optim.Adam(retinanet.parameters(), lr=1e-5)

    scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=3, verbose=True)

    loss_hist = collections.deque(maxlen=500)

    retinanet.train()
    retinanet.module.freeze_bn()

    print('Num training images: {}'.format(len(dataset_train)))

    for epoch_num in range(parser.epochs):

        retinanet.train()
        retinanet.module.freeze_bn()

        epoch_loss = []

        for iter_num, data in enumerate(dataloader_train):
            try:
                optimizer.zero_grad()

                if torch.cuda.is_available():
                    classification_loss, regression_loss = retinanet([data['img'].cuda().float(), data['annot']])
                else:
                    classification_loss, regression_loss = retinanet([data['img'].float(), data['annot']])

                classification_loss = classification_loss.mean()
                regression_loss = regression_loss.mean()

                loss = classification_loss + regression_loss

                if bool(loss == 0):
                    continue

                loss.backward()

                torch.nn.utils.clip_grad_norm_(retinanet.parameters(), 0.1)

                optimizer.step()

                loss_hist.append(float(loss))

                epoch_loss.append(float(loss))

                print(
                    'Epoch: {} | Iteration: {} | Classification loss: {:1.5f} | Regression loss: {:1.5f} | Running loss: {:1.5f}'.format(
                        epoch_num, iter_num, float(classification_loss), float(regression_loss), np.mean(loss_hist)))

                del classification_loss
                del regression_loss
            except Exception as e:
                print(e)
                continue

        if parser.dataset == 'coco':

            print('Evaluating dataset')

            coco_eval.evaluate_coco(dataset_val, retinanet)

        elif parser.dataset == 'csv' and parser.csv_val is not None:

            print('Evaluating dataset')

            mAP = csv_eval.evaluate(dataset_val, retinanet)

        scheduler.step(np.mean(epoch_loss))

        torch.save(retinanet.module, '{}_retinanet_{}.pt'.format(parser.dataset, epoch_num))

    retinanet.eval()

    torch.save(retinanet, 'model_final.pt')

if __name__ == '__main__':
    main()


#!/usr/bin/env python
from __future__ import unicode_literals, print_function
import ffmpeg
import sys
import praw

def submissionHandling(submission):
	url = submission.url

	if (url.endswith('mp4') or url.endswith('.webm')):
		print('Direct Link Provided')
	elif "imgur" in url:
	  url = (url.rstrip("gifv") + "mp4")
	else:
		print('Not a gif')
		return

	try:
		probe = ffmpeg.probe(url)
	except ffmpeg.Error as e:
		print(e.stderr, file=sys.stderr)
		sys.exit(1)

	video_stream = next((stream for stream in probe['streams'] if stream['codec_type'] == 'video'), None)
	if video_stream is None:
		print('No video stream found', file=sys.stderr)
		sys.exit(1)

	if ("mp4" in url):
		flair = (video_stream['duration']).strip('0')         # FOR MP4
	elif ('webm' in url):
		flair = (video_stream['tags']['DURATION']).strip('0:') # FOR WEBM

	flair = (str(round(float(flair),2)) + ' seconds')
	print(flair)
	submission.mod.flair(flair)
	return


import json
from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple, Union
from tqdm import tqdm
from spacy.lang.en import English # updated
import string
import re
import transformers
from transformers import PreTrainedTokenizer, PreTrainedTokenizerFast, AutoTokenizer

reg_whitespace = '[{0}]'.format(string.whitespace)
reg_weird = '[^0-9a-zA-Z!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~\n\r ]'
reg_CONTINUOUS_newlines = '\\n\s*\\n'
html_tags = '<(?!tag).*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});'

#load sentencizer at global level
sentencizer = English()
sentencizer.add_pipe(sentencizer.create_pipe('sentencizer')) # updated

@dataclass
class ModelMeta:
    max_seq_length:int = 512
    sep_token_extra:bool = False
    model_type:str = None
    pad_token_label_id:int =0
    sequence_a_segment_id:int =0
    mask_padding_with_zero:bool =True

@dataclass
class Annotation:

    start_index: int
    end_index: int
    value: str
    name: str

@dataclass
class SentenceMeta:
    words: List[str]
    labels: Optional[List[str]]

@dataclass
class InputExample:
    num_sentences: int
    num_tokens: int
    sentence_meta_list : List[SentenceMeta]

@dataclass
class InputFeatures:

    input_ids: List[int]
    attention_mask: List[int]
    token_type_ids: Optional[List[int]] = None
    label_ids: Optional[List[int]] = None

def recurive_replacement(value, replacement, text):
    while value in text:
        text= text.replace(value, replacement)
    return text

def parse_text(text):
    text = ''.join( [ch if ord(ch)<128 else ' ' for ch in text])
    #special case
    text = text.replace("\\r\\n","\n")
    text = text.replace("\\n","\n")
    text = text.replace("\\t","\t")
    text = text.replace("\\r","\r")
    text = text.replace("\\x0b"," ")
    text = text.replace("\\x0c"," ")
    text = text.replace("\r\n","\n")
    text = text.replace("\r","\n")
    text = re.sub(reg_CONTINUOUS_newlines,'\n', text)
    text = re.sub(reg_weird,' ', text)
    text = re.sub(html_tags,'', text)
    text = recurive_replacement('  ',' ', text)
    return text

def sent_tokenize(text):
    doc = sentencizer(text)
    return [sent.string for sent in doc.sents]

def tokenize(text:str)->List[str]:
    tokens = []
    word = ''
    for ch in text:
        if re.match('^[a-zA-Z0-9]',ch):
            word += ch
        else:
            if len(word)>0:
                tokens.append(word)
                word = ''
            tokens.append(ch)
    if len(word)>0:#check for leftover word if any
        tokens.append(word)
    return tokens

def correct_annotation_indice(label, label_si, label_ei, text):
    if label not in text:
        raise Exception('Cannot recover label indice from text')
    new_si = text.index(label)
    while new_si>=0:
        new_ei = new_si + len(label)
        # check for intersection
        recovered_label = text[new_si:new_ei]
        if new_si <= label_ei and label_si <= new_ei and recovered_label == label:
            return (new_si, new_ei)
        new_si = text.index(label, new_si+1)
    raise Exception('Could not recover correct label indice!')

def convert_platform_data_to_ner(examples:List[str], annotations_list:List[Annotation], class_list:List[str], use_iob2_format:bool=False)->List[InputExample]:
    assert(len(examples)== len(annotations_list))
    converted_examples = []
    for example,annotations in tqdm(zip(examples, annotations_list), total=len(examples),desc='converting samples'):
        sentences = sent_tokenize(example)
        tokens = []
        chr_to_tok_map = {}
        tok_to_sent_map = []
        num_tokens = 0
        token_idx = 0
        ch_index = 0
        for sent_index, sentence in enumerate(sentences):
            sent_tokens = tokenize(sentence)
            for token  in sent_tokens:
                for ch in token:
                    chr_to_tok_map[ch_index] = token_idx
                    ch_index += 1
                token_idx += 1
            tok_to_sent_map.append(( num_tokens, num_tokens+len(sent_tokens)))
            num_tokens += len(sent_tokens)
            tokens.extend(sent_tokens)
        labels = [class_list[0],]* len(tokens)# by default all the tokens would be 'O'
        for annotation in annotations:
            token_si = chr_to_tok_map[annotation.start_index]
            # if annotation.end_index not in chr_to_tok_map and annotation.end_index==len(example):
            #     token_ei = chr_to_tok_map[annotation.end_index-1]
            # else:
            token_ei = chr_to_tok_map[annotation.end_index-1]
            #start: sanity check
            original_label = ''.join(tokens[token_si: token_ei+1])
            assert(original_label == annotation.value or annotation.value in original_label)
            # if original_label != annotation.value:
            #     print(annotation.value)
            #     print('here')
            #end: sanity check
            # assert(len(set(labels[token_si:token_ei+1]))==1)#Entity over Entity not allowed
            if len(set(labels[token_si:token_ei+1]))>1:
                continue
            if use_iob2_format:
                iob_start = 'B-'+annotation.name.upper()
                iob_end = 'I-'+annotation.name.upper()
                assert(iob_start in class_list and iob_end in class_list)
                labels[token_si:token_ei+1] = [iob_end]*(token_ei+1-token_si)
                labels[token_si]=iob_start
            else:
                assert(annotation.name in class_list)
                labels[token_si:token_ei+1] = [annotation.name]*(token_ei+1-token_si)
        #create sentence level tokens
        sentence_meta_list = []
        for sent_si, sent_ei in tok_to_sent_map:
            sent_tokens = tokens[sent_si:sent_ei]
            sent_labels = labels[sent_si:sent_ei]
            sentence_meta_list.append( SentenceMeta(sent_tokens, sent_labels))
        converted_examples.append(InputExample(len(sentences), len(tokens), sentence_meta_list))
    return converted_examples

def convert_tokens_to_features(
    tokenizer:object,
    model_meta:ModelMeta,
    feature_tokens:List[str],
    feature_labels: List[str]=[],
    )->InputFeatures:

    inference_mode = len(feature_labels)==0
    cls_token_at_end = bool(model_meta.model_type in ["xlnet"])
    cls_token_segment_id= 2 if model_meta.model_type in ["xlnet"] else 0
    pad_token = tokenizer.pad_token_id
    pad_token_segment_id =tokenizer.pad_token_type_id
    pad_on_left =bool(tokenizer.padding_side == "left")
    sep_token = tokenizer.sep_token
    cls_token = tokenizer.cls_token
    special_tokens_count = tokenizer.num_special_tokens_to_add()
    max_allowed_tokens = model_meta.max_seq_length - special_tokens_count - int(model_meta.sep_token_extra)

    if len(feature_tokens) > max_allowed_tokens:
        feature_tokens = feature_tokens[: max_allowed_tokens]
        feature_labels = feature_labels[: max_allowed_tokens]
    feature_tokens += [sep_token]
    feature_labels += [model_meta.pad_token_label_id]
    if model_meta.sep_token_extra:
        # roberta uses an extra separator b/w pairs of sentences
        feature_tokens += [sep_token]
        feature_labels += [model_meta.pad_token_label_id]
    segment_ids = [model_meta.sequence_a_segment_id] * len(feature_tokens)

    if cls_token_at_end:
        feature_tokens += [cls_token]
        feature_labels += [model_meta.pad_token_label_id]
        segment_ids += [cls_token_segment_id]
    else:
        feature_tokens = [cls_token] + feature_tokens
        feature_labels = [model_meta.pad_token_label_id] + feature_labels
        segment_ids = [cls_token_segment_id] + segment_ids

    input_ids = tokenizer.convert_tokens_to_ids(feature_tokens)
    input_mask = [1 if model_meta.mask_padding_with_zero else 0] * len(input_ids)
    # Zero-pad up to the sequence length.
    padding_length = model_meta.max_seq_length - len(input_ids)
    if pad_on_left:
        input_ids = ([pad_token] * padding_length) + input_ids
        input_mask = ([0 if model_meta.mask_padding_with_zero else 1] * padding_length) + input_mask
        segment_ids = ([pad_token_segment_id] * padding_length) + segment_ids
        feature_labels = ([model_meta.pad_token_label_id] * padding_length) + feature_labels
    else:
        input_ids += [pad_token] * padding_length
        input_mask += [0 if model_meta.mask_padding_with_zero else 1] * padding_length
        segment_ids += [pad_token_segment_id] * padding_length
        feature_labels += [model_meta.pad_token_label_id] * padding_length

    assert len(input_ids) == model_meta.max_seq_length
    assert len(input_mask) == model_meta.max_seq_length
    assert len(segment_ids) == model_meta.max_seq_length
    if not inference_mode:
        assert len(feature_labels) == model_meta.max_seq_length
    return InputFeatures(
            input_ids=input_ids, attention_mask=input_mask, token_type_ids=segment_ids, label_ids=feature_labels
        )

def convert_examples_to_features(
    model_meta: ModelMeta,
    input_examples:List[InputExample],
    class_list: List[str],
    tokenizer: PreTrainedTokenizerFast,
    use_iob2_format:bool=False) -> List[InputFeatures]:
    max_allowed_tokens = model_meta.max_seq_length - tokenizer.num_special_tokens_to_add() + int(model_meta.sep_token_extra)
    class_map = {label: i for i, label in enumerate(class_list)}
    features = []
    for ex_index, example in tqdm(enumerate(input_examples), total=len(input_examples), desc='generating features'):
        #loop over sentences in an example
        context_subsets = []
        current_subset = []
        last_count = 0
        for sentence_meta in example.sentence_meta_list:
            tokens = []
            label_ids = []
            for word, label in zip(sentence_meta.words, sentence_meta.labels):
                word_tokens = tokenizer.tokenize(word)
                if len(word_tokens) > 0:
                    tokens.extend(word_tokens)
                    # Use the real label id for the first token of the word, and padding ids for the remaining tokens
                    temp_pad_id = model_meta.pad_token_label_id
                    label_id = class_map[label]
                    if label!='O' :
                        if use_iob2_format:
                            temp_pad_id = class_map['I-'+ label[2:]]
                        else:
                            temp_pad_id = class_map[label]
                    label_ids.extend([label_id] + [temp_pad_id] * (len(word_tokens) - 1))
            assert(len(tokens)== len(label_ids))
            num_tokens = len(tokens)
            sent_feature = SentenceMeta(tokens, label_ids)
            if last_count + num_tokens >= max_allowed_tokens:
                if len(current_subset)==0:
                    #meaning a single sentence is exceeding the max_seq_len of the model
                    context_subsets.append([sent_feature])
                else:
                    context_subsets.append(current_subset)
                    current_subset = [sent_feature]
                    last_count = num_tokens
            else:
                last_count += num_tokens
                current_subset.append(sent_feature)
        if len(current_subset)>0:
            context_subsets.append(current_subset)
        #finally create concatenated features based on sent features
        for context_subset in context_subsets:
            feature_tokens = []
            feature_labels = []
            for sent_feature in context_subset:
                feature_tokens.extend( sent_feature.words)
                feature_labels.extend( sent_feature.labels)
            input_features = convert_tokens_to_features(
                tokenizer = tokenizer,
                model_meta = model_meta,
                feature_tokens = feature_tokens,
                feature_labels = feature_labels
            )
            features.append(input_features)
    return features

def read_annotation_file(files:List[str], use_iob2_format=True):
    examples = []
    all_labels = []
    tag_master = []
    labels_skipped = 0
    lines = []
    for file in files:
        with open(file, 'r') as f:
            lines.extend(f.readlines())
    for line in lines:
        data = json.loads(line)
        text = data['content']
        annotations = []
        for annotation in data['annotation']:
            point = annotation['points'][0]
            labels = annotation['label']
            if not isinstance(labels, list):
                labels = [labels]
            for label in labels:
                #dataturks indices are both inclusive [start, end] but spacy is not [start, end)
                # entities.append((point['start'], point['end'] + 1 ,label))
                if label in ['Skills', 'UNKNOWN', 'Graduation Year']:
                    continue
                if label not in tag_master:
                    tag_master.append(label)
                label_text = point['text']
                label_si = point['start']
                label_ei = point['end']+1
                use_annotation = True
                if text[label_si:label_ei]!=label_text or label_ei>=len(text)+1:
                    use_annotation = False
                    if label_text in text:
                        label_si = text.index(label_text)
                        label_ei = label_si + len(label_text)
                        use_annotation = True
                if use_annotation:
                    annotations.append(Annotation(label_si, label_ei, label_text, label))
                else:
                    labels_skipped += 1
                    print('annotation skipped due to inconsitent indices..')
        if len(annotations) >0 and len(text.strip())>0:
            examples.append(text)
            all_labels.append(annotations)
        class_list = ['O']
    for tag_name in tag_master:
        if use_iob2_format:
            class_list.append('B-'+tag_name.upper())
            class_list.append('I-'+tag_name.upper())
        else:
            class_list.append(tag_name)
    print('Total Samples Processed:', len(examples))
    print('labels skipped:', labels_skipped)
    return examples, all_labels, class_list

def preprocess_for_inferencing(model_meta, texts:List[str], tokenizer):
    special_tokens_count = tokenizer.num_special_tokens_to_add()
    max_allowed_tokens = model_meta.max_seq_length - special_tokens_count - int(model_meta.sep_token_extra)
    features_2d = []
    for text in texts:
        text = parse_text(text)
        last_count = 0
        context_subsets = []
        current_subset = []
        sentences = sent_tokenize(text)
        for sentence in sentences:
            sent_tokens = []
            for word in tokenize(sentence):
                word_tokens = tokenizer.tokenize(word)
                if len(word_tokens) > 0:
                    sent_tokens.extend(word_tokens)
            num_tokens = len(sent_tokens)
            if last_count + num_tokens >= max_allowed_tokens:
                if len(current_subset)==0:
                    #meaning a single sentence is exceeding the max_seq_len of the model
                    context_subsets.append(sent_tokens)
                else:
                    context_subsets.append(current_subset)
                    current_subset = sent_tokens
                    last_count = num_tokens
            else:
                last_count += num_tokens
                current_subset.extend(sent_tokens)
        if len(current_subset)>0:
            context_subsets.append(current_subset)
        input_features = []
        for context_subset in context_subsets:
            input_feature = convert_tokens_to_features(
                    tokenizer = tokenizer,
                    model_meta = model_meta,
                    feature_tokens = context_subset,
                    feature_labels = []
                )
            input_features.append(input_feature)
        features_2d.append(input_features)
    return features_2d

if __name__ == "__main__":

    use_iob2_format = True
    model_meta = ModelMeta()
    model_meta.model_type = 'bert'
    examples, annotations_list, class_list = read_annotation_file([r'traindata.json', r'testdata.json'])
    converted_examples = convert_platform_data_to_ner(examples, annotations_list, class_list, use_iob2_format = use_iob2_format)
    class_map = {i:label for i, label in enumerate(class_list)}
    features = convert_examples_to_features(converted_examples,class_list,tokenizer,use_iob2_format = use_iob2_format)
    use_token_type_ids = "token_type_ids" in tokenizer.model_input_names
    print('here')


import datetime
import sys

import numpy as np
import pandas as pd
import plotly.graph_objects as go
import dash
import dash.dcc as dcc
from dash import html
from dash.dependencies import Input, Output

import update_data

##### Define constants #####
PER_POPULATION = 100
COVID_DATA_FILE = "data/COVID-19_aantallen_gemeente_cumulatief.csv"

##### Utility functions #####
def round_significant_digits(x, n=2):
    if type(x) in [int, float]:
        if x == 0:
            return 0
        if x > 1:
            return round(x, n)

    power = 10 ** np.ceil(np.log10(abs(x)))
    return round(x / power, n) * power

## Metrics to display ##
METRICS = ['Total_reported', 'Hospital_admission', 'Deceased']

COVID_DATA = pd.read_csv(
    COVID_DATA_FILE,
    sep=";"
)
COVID_DATA["Date_of_report"] = pd.to_datetime(COVID_DATA["Date_of_report"])

date_now = datetime.datetime.now().date()
date_max_df = COVID_DATA["Date_of_report"].max().date()
print(f"Date now: {date_now} Date in df: {date_max_df}")
if date_now - date_max_df > datetime.timedelta(days=1):
    print("Data is older than 1 day, fetching from URL")
    try:
        update_data.update()
        COVID_DATA = pd.read_csv(
            COVID_DATA_FILE,
            sep=";"
        )
        COVID_DATA["Date_of_report"] = pd.to_datetime(
            COVID_DATA["Date_of_report"])
    except:
        print("Cannot read url reverting to local, reading locally.")

# Remove all data points which don't have a municipality name
COVID_DATA = COVID_DATA[COVID_DATA["Municipality_name"] != ""]
COVID_DATA = COVID_DATA[(COVID_DATA["Municipality_name"].notna())]

## Replace faulty data ##
COVID_DATA["Province"] = COVID_DATA["Province"].replace({
    "FryslÃ¢n": "Friesland",
    "Fryslân": "Friesland",
})
# Disambiguate Mun name by Mun code
COVID_DATA["Municipality_name"] = COVID_DATA.groupby(
    "Municipality_code")["Municipality_name"].transform(lambda x: sorted(x)[0])

##  Read population data ##
mun_population_data = pd.read_csv("data/NL_Population_Latest.csv")
mun_population_data = mun_population_data.rename(columns={
    "PopulationOn31December_20": "Population"
})

## Merge the population data ##
COVID_DATA = COVID_DATA.merge(
    mun_population_data,
    left_on="Municipality_code",
    right_on="Regions",
    how="inner"
)

## Compute daily values ##
COVID_DATA = COVID_DATA.sort_values(
    by=["Municipality_code", "Municipality_name", "Date_of_report"]
)
for metric in METRICS:
    COVID_DATA[f"Daily_{metric}"] = COVID_DATA.groupby("Municipality_name")[metric].transform(
        lambda x: x.diff().fillna(0)
    )

## Aggregate data per province ##
PROVINCE_COLS = [
    f"Daily_{metric}" for metric in METRICS] + METRICS + ["Population"]
PROVINCIAL_COVID_DATA = COVID_DATA.groupby(["Province", "Date_of_report"]).agg(
    {col: np.sum for col in PROVINCE_COLS}
).reset_index()

## Calculate unique municipalities which have both population and covid data ##
UNIQUE_MUNICIPALITIES = np.unique(COVID_DATA["Municipality_name"])
UNIQUE_PROVINCES = np.unique(COVID_DATA["Province"])

## Setup the dash app ##
app = dash.Dash(
    __name__,
    assets_folder='assets',
    url_base_pathname='/',
    show_undo_redo=True,
)
app.title = "COVID-19 Dashboard - The Netherlands"
server = app.server

app.layout = html.Div([
    html.Div([
        html.H6("Select municipalities:"),
        dcc.Dropdown(
            id='selected_municipalities',
            options=[
                {'label': i, 'value': i}
                for i in sorted(UNIQUE_MUNICIPALITIES)
            ],
            value=sorted(["'s-Gravenhage", 'Utrecht', 'Eindhoven', 'Heerlen']),
            style={
                'textAlign': 'center',
                'align': 'center',
                'border-radius': '25px',
                'margin-left': '5%',
                'width': '90%'
            },
            multi=True,
        ),
        html.Br(),
    ], style={
        'width': '40%',
        'display': 'inline-block',
        'vertical-align': 'top',
        'textAlign': 'center',
        'border': '2px black solid',
        'border-radius': '25px',
        'margin': '5px',
        'height': '100%',
    }),
    html.Div([
        html.H6("Metric:"),
        dcc.Dropdown(
            id='covid_metric',
            options=[{'label': i.replace("_", " "), 'value': i}
                     for i in METRICS],
            value=METRICS[0],
            style={
                'textAlign': 'left',
                'align': 'center',
                'border-radius': '25px',
                'margin-left': '5%',
                'width': '90%'
            },
        ),
        html.Br(),
    ], style={
        'width': '15%',
        'display': 'inline-block',
        'vertical-align': 'top',
        'textAlign': 'center',
        'border': '2px black solid',
        'border-radius': '25px',
        'margin': '5px',
        'height': '100%',
    }),
    html.Div([
        html.H6("Moving avg. (days)"),
        dcc.Input(
            id="moving_avg",
            type="number",
            placeholder="Rolling Window",
            min=1,
            max=100,
            step=1,
            value=5,
            debounce=False,
            style={
                'align': 'center',
                'vertical-align': 'top',
                'textAlign': 'left',
                'border-radius': '25px',
                'margin-left': '30%',
                'margin-right': '30%',
                'width': '40%',
            }
        ),
        html.Br(),
        html.Br()
    ], style={
        'width': '15%',
        'display': 'inline-block',
        'vertical-align': 'top',
        'textAlign': 'center',
        'border': '2px black solid',
        'border-radius': '25px',
        'margin': '5px',
        'height': '100%',
    }),
    html.Div([
        html.H6("Data type:"),
        dcc.RadioItems(
            id='data_type',
            options=[
                {'label': i, 'value': i}
                for i in ["Absolute", "Population Adjusted"]
            ],
            value="Population Adjusted",
            style={
                'textAlign': 'left',
                'align': 'center',
                'border-radius': '25px',
                'margin-left': '5%',
                'width': '90%'
            },
        ),
        html.Br(),
    ], style={
        'width': '12%',
        'display': 'inline-block',
        'vertical-align': 'top',
        'textAlign': 'center',
        'border': '2px black solid',
        'border-radius': '25px',
        'margin': '5px',
        'height': '100%',
    }),
    html.Br(),
    html.Div([
        dcc.Graph(
            id='daily_figure',
        )
    ], style={
        'width': '49%',
        'display': 'inline-block',
        'padding': '0 20'
    }),
    html.Div([
        dcc.Graph(
            id='total_figure',
        )
    ], style={
        'width': '49%',
        'display': 'inline-block',
        'padding': '0 20'
    }),
    html.Hr(),
    html.Br(),
    html.Div([
        html.H6("Select provinces:"),
        dcc.Dropdown(
            id='selected_provinces',
            options=[{'label': i, 'value': i} for i in UNIQUE_PROVINCES],
            value=sorted(
                ['Zuid-Holland', 'Noord-Brabant', 'Utrecht', 'Limburg']),
            style={
                'textAlign': 'center',
                'align': 'center',
                'border-radius': '25px',
                'margin-left': '5%',
                'width': '90%'
            },
            multi=True,
        ),
        html.Br(),
    ], style={
        'width': '40%',
        'display': 'inline-block',
        'vertical-align': 'top',
        'textAlign': 'center',
        'border': '2px black solid',
        'border-radius': '25px',
        'margin': '5px'
    }),
    html.Br(),
    html.Div([
        dcc.Graph(
            id='daily_province_figure',
        )
    ], style={
        'width': '49%',
        'display': 'inline-block',
        'padding': '0 20'
    }),
    html.Div([
        dcc.Graph(
            id='total_province_figure',
        )
    ], style={
        'width': '49%',
        'display': 'inline-block',
        'padding': '0 20'
    }),
])

@app.callback(
    [
        Output('daily_figure', 'figure'),
        Output('total_figure', 'figure'),
    ],
    [
        Input('selected_municipalities', 'value'),
        Input('covid_metric', 'value'),
        Input('moving_avg', 'value'),
        Input('data_type', 'value'),
    ]
)
def process_and_render_muns(selected_municipalities, covid_metric, moving_avg, data_type):
    selected_municipalities = sorted(selected_municipalities)

    if len(selected_municipalities) > 0:
        mun_data = COVID_DATA[COVID_DATA["Municipality_name"].isin(
            selected_municipalities)].copy()
    else:
        return go.Figure(), go.Figure()

    if moving_avg not in [None, 0, 1]:
        mun_data[f"Daily_{covid_metric}"] =\
            mun_data.groupby("Municipality_name")[f"Daily_{covid_metric}"].transform(
                lambda x: x[::-1].rolling(
                    window=int(moving_avg),
                    min_periods=1,
                    center=False
                )
                .mean()[::-1]
        )

    if data_type == "Population Adjusted":
        mun_data[f"Daily_{covid_metric}"] = (
            mun_data[f"Daily_{covid_metric}"] / mun_data["Population"] * 100
        )
        mun_data[covid_metric] = (
            mun_data[covid_metric] / mun_data["Population"] * 100
        )

    ## Round the data to 3 significant digits ##
    mun_data[f"Daily_{covid_metric}"] = mun_data[f"Daily_{covid_metric}"].apply(
        lambda x: round_significant_digits(x, 3))
    mun_data[covid_metric] = mun_data[covid_metric].apply(
        lambda x: round_significant_digits(x, 3))

    ## Add figure for daily numbers ##
    daily_mun_figure = go.Figure()
    for grp, df in mun_data.groupby("Municipality_name"):
        daily_mun_figure.add_trace(
            go.Scatter(
                x=df["Date_of_report"],
                y=df[f"Daily_{covid_metric}"],
                name=grp,
                mode="markers+lines",
                line_shape="spline",
                marker={"size": 2},
                hovertemplate="%{x}
%{y}",
            )
        )
    daily_mun_figure.update_layout(
        title=f"Daily {covid_metric} ({data_type})"
        .title()
        .replace("_", " ")
        .replace("Population Adjusted", f"per {PER_POPULATION} people"),
        title_x=0.5,
    )
    daily_mun_figure.update_xaxes(title="Date of report")

    ## Add figure for total numbers ##
    total_mun_figure = go.Figure()
    for grp, df in mun_data.groupby("Municipality_name"):
        total_mun_figure.add_trace(
            go.Scatter(
                x=df["Date_of_report"],
                y=df[covid_metric],
                name=grp,
                mode="markers+lines",
                line_shape="spline",
                marker={"size": 2},
                hovertemplate="%{x}
%{y}",
            )
        )
    total_mun_figure.update_yaxes(title_text="")
    total_mun_figure.update_xaxes(title_text="Date of report")
    total_mun_figure.update_layout(
        title=f"{covid_metric} ({data_type})"
        .title()
        .replace("_", " ")
        .replace("Population Adjusted", f"per {PER_POPULATION} people"),
        title_x=0.5,
    )

    return (daily_mun_figure, total_mun_figure)

@app.callback(
    [
        Output('daily_province_figure', 'figure'),
        Output('total_province_figure', 'figure'),
    ],
    [
        Input('selected_provinces', 'value'),
        Input('covid_metric', 'value'),
        Input('moving_avg', 'value'),
        Input('data_type', 'value'),
    ]
)
def process_and_render_provinces(selected_provinces, covid_metric, moving_avg, data_type):
    if len(selected_provinces) > 0:
        province_data_selected = PROVINCIAL_COVID_DATA[PROVINCIAL_COVID_DATA["Province"].isin(
            selected_provinces)].copy()
    else:
        province_data_selected = PROVINCIAL_COVID_DATA.copy()

    if data_type == "Population Adjusted":
        province_data_selected[f"Daily_{covid_metric}"] = (
            province_data_selected[f"Daily_{covid_metric}"] /
            province_data_selected["Population"] * 100
        )
        province_data_selected[covid_metric] = (
            province_data_selected[covid_metric] /
            province_data_selected["Population"] * 100
        )

    if moving_avg not in [None, 0, 1]:
        province_data_selected[f"Daily_{covid_metric}"] =\
            province_data_selected.groupby("Province")[f"Daily_{covid_metric}"].transform(
            lambda x: x[::-1].rolling(
                window=int(moving_avg),
                min_periods=1,
                center=False
            )
            .mean()[::-1]
        )

    province_data_selected[f"Daily_{covid_metric}"] = province_data_selected[f"Daily_{covid_metric}"].apply(
        round_significant_digits)
    province_data_selected[covid_metric] = province_data_selected[covid_metric].apply(
        round_significant_digits)

    daily_province_figure = go.Figure()
    for grp, df in province_data_selected.groupby("Province"):
        daily_province_figure.add_trace(
            go.Scatter(
                x=df["Date_of_report"],
                y=df[f"Daily_{covid_metric}"],
                name=grp,
                mode="markers+lines",
                line_shape="spline",
                marker={"size": 2},
                hovertemplate="%{x}
%{y}",
            )
        )
    daily_province_figure.update_xaxes(title_text="Date of report")
    daily_province_figure.update_yaxes(title_text="")
    daily_province_figure.update_layout(
        title=f"Daily {covid_metric} ({data_type})"
        .title()
        .replace("_", " ")
        .replace("Population Adjusted", f"per {PER_POPULATION} people"),
        title_x=0.5,
    )

    total_province_figure = go.Figure()
    for grp, df in province_data_selected.groupby("Province"):
        total_province_figure.add_trace(
            go.Scatter(
                x=df["Date_of_report"],
                y=df[covid_metric],
                name=grp,
                mode="markers+lines",
                line_shape="spline",
                marker={"size": 2},
                hovertemplate="%{x}
%{y}",
            )
        )
    total_province_figure.update_xaxes(title_text="Date of report")
    total_province_figure.update_yaxes(title_text="")
    total_province_figure.update_layout(
        title=f"{covid_metric} ({data_type})"
        .title()
        .replace("_", " ")
        .replace("Population Adjusted", f"per {PER_POPULATION} people"),
        title_x=0.5,
    )

    return (daily_province_figure, total_province_figure)

def main():
    APP_PORT = 5005
    time_now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    print(
        f"Starting dashboard for COVID-19 data in The Netherlands at {time_now}")
    app.run_server(
        debug=True,
        host="0.0.0.0",
        port=APP_PORT
    )

if __name__ == "__main__":
    sys.exit(main())


"""
    Step-Based
    REPS algorithm on Pendulum Environment
    Gaussian Policy, Linear Mean.
    RBF Features
"""
import gym
# from gym.envs.classic_control.pendulum import PendulumEnv
from rl.featurizer.rbf_featurizer import RBFFeaturizer
from rl.trash.value_estimator_np import ValueEstimatorNP
from rl.trash.gaussian_policy_np import GaussianPolicyNP
#
import numpy as np

#
env = gym.make('Pendulum-v0')
# env = RandomJumpEnv()
print("Action space : ", env.action_space)
print("Action space low : ", env.action_space.low)
print("Action space high: ", env.action_space.high)
print("Observation space : ", env.observation_space)
print("Observation space low : ", env.observation_space.low)
print("Observation space high: ", env.observation_space.high)

def calculate_Return(rewards, gamma=0.95):
    R = np.zeros_like(rewards)
    R_sum = 0
    for t in reversed(range(0, rewards.shape[0])):
        R_sum = R_sum * gamma + rewards[t]
        R[t] = R_sum
    # R -= np.mean(R)
    # R = np.exp( (R - np.min(R)) / (np.max(R) - np.min(R)) )
    # beta = 3. / (np.max(rewards) - np.min(rewards))
    # weights = np.exp((rewards-np.max(rewards)) * beta)
    # weights = weights / sum(weights)
    return R
#
def step_reps(env, featurizer, policy_fn, num_episodes, num_steps, num_samples, value_fn, eta_init, v_init, epsilon, gamma=1.0):
    H = 20
    m = featurizer.num_features
    T = 200
    n = env.action_space.shape[0]
    for i_eposide in range(num_episodes):
        # samples
        Phi_n = np.zeros(shape=(T,H,m))
        A = np.zeros(shape=(T,H,n))
        Q = np.zeros(shape=(T,H,1))
        for h in range(H):
            state = env.reset()
            rewards = np.zeros(shape=(T,1))
            # rollouts
            for t in range(T):
                action = policy_fn.predict_step(state)
                next_state, reward, done, _ = env.step(action)
                #
                Phi_n[t, h, :] = featurizer.transform(state)
                A[t, h, :] = action
                rewards[t] = reward
                # Q[t, h, :] = reward
                #
                state = next_state
            Returns = calculate_Return(rewards)
            #
            Q[:, h, :] = Returns
        # norm_Q = np.zeros(shape=(H, T, 1))
        # for h in range(H):
        #     norm_Q[h, :, :] = (Q[h,:,:] - np.min(Q, axis=1)) / (np.max(Q, axis=1) - np.min(Q, axis=1))
        #
        policy_fn.update_step(weights=Q,
                              Phi=Phi_n,
                              Actions=A)
        print(np.sum(Q))

def episode_reps(env, featurizer, policy_fn, num_episodes, num_samples, num_steps):
    for i_episodes in range(num_episodes):
        # init_state = env.reset()
        theta_samples = policy_fn.samples_theta(num_samples)
        rewards = np.zeros(num_samples)
        for i_samples in range(num_samples):
            state = env.reset()
            # state = init_state
            rewards_rollout = np.zeros(shape=num_steps)
            theta = theta_samples[i_samples]
            for t in range(num_steps):
                action = policy_fn.predict_episode(state, theta)
                next_state, reward, done, _ = env.step(action)
                rewards_rollout[t] = reward
                state = next_state
                if done:
                    break
            # Returns = calculate_Return(re)
            rewards[i_samples] = np.sum(rewards_rollout)
        beta = 5. / (np.max(rewards) - np.min(rewards))
        weights = np.exp((rewards-np.max(rewards)) * beta)
        weights = weights / sum(weights)
        policy_fn.update_episode(weights=weights, theta_samples=theta_samples)
        print("episode, ", i_episodes, np.mean(rewards))
        if i_episodes >= 700:
            obs = env.reset()
            while True:
                env.render()
                action = policy_fn.predict_step(obs)
                next_obs, reward, done, _ = env.step(action)
                obs = next_obs
                if done:
                    break

#
dim_featuries = 5
rbf_featurizer = RBFFeaturizer(env=env, dim_features=dim_featuries, beta=50)
# rbf_featurizer.plot_examples()
# time.sleep(1)
# plt.close()
#
policy_fn = GaussianPolicyNP(env, rbf_featurizer)
value_fn = ValueEstimatorNP(rbf_featurizer)
param_v = value_fn.param_v
param_eta = 3
epsilon = 5e-1
#
num_samples = 10
num_steps = 200
num_episodes = 10000
#
# step_reps(env=env,
#           featurizer=rbf_featurizer,
#           policy_fn = policy_fn,
#           value_fn = value_fn,
#           num_episodes = num_episodes,
#           num_steps = num_steps,
#           num_samples=num_samples,
#           eta_init=param_eta,
#           v_init=param_v,
#           epsilon=epsilon,
#           gamma=1.0)
episode_reps(env=env,
             featurizer=rbf_featurizer,
             policy_fn = policy_fn,
             num_episodes=num_episodes,
             num_samples = num_samples,
             num_steps=num_steps)


#! /usr/bin/env python
# -*- coding:utf-8 -*-
from django.conf.urls import url
from . import views

urlpatterns = [
    url(r"^$",views.index, name="index"),
    url(r"^login/$",views.login, name="login"),
    url(r"^regist/$",views.regist,name="regist"),
    url(r"^list/$",views.list, name="list"),
    url(r"^edit/?P<user>(\w+)/$",views.edit,name="edit"),
    url(r"^edit_before/$",views.edit_before,name="edit_before"),
    url(r"^add/$",views.add,name="add"),
    url(r"^delete/?P<user>(\w+)/$",views.delete,name="delete"),
]


from utils.app.const import OPENAI_API_HOST, OPENAI_API_TYPE, OPENAI_API_VERSION, OPENAI_ORGANIZATION
from types.openai import OpenAIModel, OpenAIModelID, OpenAIModels
from trpc import procedure, router
from trpc.errors import TRPCError
import requests
from typing import Optional
from zod import z

async def get_models(ctx, input):
    """
    Retrieve a list of OpenAI models.

    Args:
        ctx (dict): The TRPC context.
        input (dict): The input parameters.

    Returns:
        list: The list of OpenAI models.
    """
    key = input.get('key')

    url = f"{OPENAI_API_HOST}/v1/models"
    if OPENAI_API_TYPE == 'azure':
        url = f"{OPENAI_API_HOST}/openai/deployments?api-version={OPENAI_API_VERSION}"

    headers = {
        'Content-Type': 'application/json',
    }
    if OPENAI_API_TYPE == 'openai':
        headers['Authorization'] = f"Bearer {key or os.environ['OPENAI_API_KEY']}"
    elif OPENAI_API_TYPE == 'azure':
        headers['api-key'] = f"{key or os.environ['OPENAI_API_KEY']}"
    if OPENAI_API_TYPE == 'openai' and OPENAI_ORGANIZATION:
        headers['OpenAI-Organization'] = OPENAI_ORGANIZATION

    response = requests.get(url, headers=headers)

    if response.status_code == 401:
        raise TRPCError(code='UNAUTHORIZED', message='Unauthorized')
    elif response.status_code != 200:
        print(f"OpenAI API returned an error {response.status_code}: {response.text}")
        raise TRPCError(code='INTERNAL_SERVER_ERROR', message='OpenAI API returned an error')

    json_data = response.json()

    models = []
    for model in json_data['data']:
        model_name = model['model'] if OPENAI_API_TYPE == 'azure' else model['id']
        for key, value in OpenAIModelID.items():
            if value == model_name:
                model_info = OpenAIModels[value]
                model_obj = OpenAIModel(
                    id=model['id'],
                    name=model_info['name'],
                    maxLength=model_info['maxLength'],
                    tokenLimit=model_info['tokenLimit'],
                )
                models.append(model_obj)
                break

    return models

models = router({
    'list': procedure.input(z.object({
        'key': z.string().optional()
    })).query(get_models),
})


#!/usr/bin/env python3

# parse_treks.3.py

# Shu-Ting Cho <vivianlily6@hotmail.com>
# Read T-REKS's result and output tsv

# v1 2022/04/21
# v2 2022/03/02
# v3 2022/05/12 Use argparse

# Usage:
# python3 parse_treks.3.py --in_file=~/project/TR02/TR02.23/aa_TREKS/K24263.out --out_file=~/project/TR02/TR02.23/aa_tsv/K24263.tsv

## import modules
import os
import sys
import re
import argparse

# parsing arguments
def parse_args():
	parser = argparse.ArgumentParser(description="Read T-REKS's result and output tsv.")
	parser.add_argument('-i', '--in_file', help="T-REKS's result")
	parser.add_argument('-o', '--out_file', help="output .tsv file")
	return parser.parse_args()

args = parse_args()

out_f = open(str(args.out_file), "w")
out_f.write("seqID\tstart\tend\tscore\n")

# Opening result file
with open(str(args.in_file)) as in_f:
	lines = in_f.readlines()
	for line in lines:
		if line[0] == '>':
			line = line.rstrip()
			seqID = line[1:]

		elif line[:6] == 'Length':
			line = line.rstrip()
			list_info = re.findall(r'from[\s]+([0-9]+)[\s]+to[\s]+([0-9]+)[\s]+-[\s]+Psim:([\S]+)', line)
			start = int(list_info[0][0])
			end = int(list_info[0][1])
			score = float(list_info[0][2])
			out_f.write('%s\t%i\t%i\t%f\n' % (seqID, start, end, score))
			#print('%s\t%i\t%i\t%f' % (seqID, start, end, score))
out_f.close()


# Split csv matrix into csv vectors for pytorch traning
import os
os.chdir("/root/workspace/code/midas/")
import os.path as path
from os.path import join as pj
import argparse
import sys
sys.path.append("modules")
import utils
import csv
import math
from glob import glob
from tqdm import tqdm

parser = argparse.ArgumentParser()
parser.add_argument('--task', type=str, default="ct1_tp1")
o = parser.parse_args()

base_dirs = glob(pj("data", "processed", o.task, "subset_*"))
for base_dir in base_dirs:
    # Specify directories
    in_dir = pj(base_dir, "mat")
    out_dir = pj(base_dir, "vec")
    utils.mkdirs(out_dir, remove_old=True)
    print("\nDirectory: %s" % (in_dir))

    # Load and save data
    mat_names = glob(pj(in_dir, '*.csv'))  # get filenames
    for i, mat_name in enumerate(mat_names):
        # load
        mat = utils.load_csv(mat_name)
        # mat = utils.transpose_list(mat)
        mod = path.splitext(path.basename(mat_name))[0]
        cell_num = len(mat) - 1
        feat_num = len(mat[0]) - 1
        print("Spliting %s matrix: %d cells, %d features" % (mod, cell_num, feat_num))

        # save
        out_mod_dir = pj(out_dir, mod)
        utils.mkdirs(out_mod_dir, remove_old=True)
        vec_name_fmt = utils.get_name_fmt(cell_num) + ".csv"
        vec_name_fmt = pj(out_mod_dir, vec_name_fmt)
        for cell_id in tqdm(range(cell_num)):
            vec_name = vec_name_fmt % cell_id
            utils.save_list_to_csv([mat[cell_id+1][1:]], vec_name)


import sys
import traceback

import opentracing
from opentracing.ext import tags

from signalfx_tracing.utils import padded_hex

SCOPE_KEY = "_signalfx_scope_key"

class TraceMiddleware(object):
    def __init__(self, tracer=None, attributes=None, trace_response_header_enabled=False):
        self.tracer = tracer or opentracing.tracer
        self.attributes = attributes or []
        self.trace_response_header_enabled = trace_response_header_enabled

    def process_request(self, req, resp):
        operation_name = req.path

        try:
            span_ctx = self.tracer.extract(opentracing.Format.HTTP_HEADERS, req.headers)
            scope = self.tracer.start_active_span(operation_name, child_of=span_ctx)
        except (
            opentracing.InvalidCarrierException,
            opentracing.SpanContextCorruptedException,
        ):
            scope = self.tracer.start_active_span(operation_name)

        span = scope.span
        span.set_tag(tags.COMPONENT, "Falcon")
        span.set_tag(tags.HTTP_METHOD, req.method)
        span.set_tag(tags.HTTP_URL, req.uri.split("?")[0])
        span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_SERVER)
        for attr in self.attributes:
            attr_val = getattr(req, attr, None)
            if attr_val:
                span.set_tag(attr, attr_val)

        setattr(req, SCOPE_KEY, scope)

    def process_resource(self, req, resp, resource, params):
        scope = getattr(req, SCOPE_KEY, None)
        if scope is None:
            return
        resource_name = resource.__class__.__name__
        scope.span.set_tag("falcon.resource", resource_name)
        scope.span.set_operation_name(
            "{0}.on_{1}".format(resource_name, req.method.lower())
        )

    def process_response(self, req, resp, resource, req_succeeded=None):
        scope = getattr(req, SCOPE_KEY, None)
        if scope is None:
            return

        status = resp.status

        if resource is None:
            status = "404"

        err_type, exc, tb = sys.exc_info()
        if err_type is not None:
            if not req_succeeded:
                if "HTTPNotFound" in err_type.__name__:
                    status = "404"
                else:
                    status = "500"
                    scope.span.set_tag(tags.ERROR, True)
                    scope.span.set_tag("sfx.error.message", str(exc))
                    scope.span.set_tag("sfx.error.object", str(exc.__class__))
                    scope.span.set_tag("sfx.error.kind", exc.__class__.__name__)
                    if tb:
                        scope.span.set_tag(
                            "sfx.error.stack", "".join(traceback.format_tb(tb))
                        )

        scope.span.set_tag(tags.HTTP_STATUS_CODE, status.split(" ")[0])

        if self.trace_response_header_enabled:
            trace_id = getattr(scope.span.context, "trace_id", 0)
            span_id = getattr(scope.span.context, "span_id", 0)
            if trace_id and span_id:
                resp.append_header("Access-Control-Expose-Headers", "Server-Timing")
                resp.append_header(
                    "Server-Timing", 'traceparent;desc="00-{trace_id}-{span_id}-01"'.format(
                        trace_id=padded_hex(trace_id),
                        span_id=padded_hex(span_id),
                    ),
                )
        scope.close()


# from math import ceil, sqrt
import math
import yaml
import os
import threading
from typing import Union
import rospy
import tf
import numpy as np
import time
from flatland_msgs.srv import MoveModel, MoveModelRequest, SpawnModelRequest, SpawnModel
from flatland_msgs.srv import StepWorld
from geometry_msgs.msg import Pose2D, PoseWithCovarianceStamped, PoseStamped
from pedsim_srvs.srv import SpawnObstacle, SpawnObstacleRequest
from pedsim_msgs.msg import LineObstacle
from pedsim_msgs.msg import LineObstacles

from nav_msgs.msg import OccupancyGrid, Path

from .utils import generate_freespace_indices, get_random_pos_on_map, get_robot_goal_on_map

class RobotManager:
    """
    A manager class using flatland provided services to spawn, move and delete Robot. Currently only one robot
    is managed
    """

    def __init__(self, ns: str, map_: OccupancyGrid, robot_yaml_path: str, timeout=20):
        """[summary]

        Args:
            ns(namespace): if ns == '', we will use global namespace
            map_ (OccupancyGrid): the map info
            robot_yaml_path (str): the file name of the robot yaml file.

        """
        self.ns = ns
        self.ns_prefix = "/" if ns == "" else "/"+ns+"/"

        self.safe_dist_adult=1.0
        self.safe_dist_child=1.2
        self.safe_dist_elder=1.5

        self.is_training_mode = rospy.get_param("/train_mode")
        self.step_size = rospy.get_param("step_size")
        self._get_robot_configration(robot_yaml_path)
        # setup proxy to handle  services provided by flatland
        rospy.wait_for_service(f'{self.ns_prefix}move_model', timeout=timeout)
        rospy.wait_for_service(f'{self.ns_prefix}spawn_model', timeout=timeout)
        rospy.wait_for_service(f'{self.ns_prefix}pedsim_simulator/add_obstacle', timeout=timeout)
        #rospy.wait_for_service('step_world', timeout=20)
        self._srv_move_model = rospy.ServiceProxy(
            f'{self.ns_prefix}move_model', MoveModel)
        self._srv_spawn_model = rospy.ServiceProxy(
            f'{self.ns_prefix}spawn_model', SpawnModel)
        # it's only needed in training mode to send the clock signal.
        self._step_world = rospy.ServiceProxy(
            f'{self.ns_prefix}step_world', StepWorld)
        self.__add_obstacle_srv = rospy.ServiceProxy(
            f'{self.ns_prefix}pedsim_simulator/add_obstacle' ,SpawnObstacle, persistent=True)

        # publisher
        # publish the start position of the robot
        # self._initialpose_pub = rospy.Publisher(
        #     'initialpose', PoseWithCovarianceStamped, queue_size=1)
        self._goal_pub = rospy.Publisher(
            f'{self.ns_prefix}goal', PoseStamped, queue_size=1, latch=True)

        self.update_map(map_)
        self._spawn_robot(robot_yaml_path)

        # path generated by the  global planner
        self._global_path = Path()
        # the timestamp will be used for checking whether the global planner can find a valid path
        # between new start position and new goal
        self._old_global_path_timestamp = None
        self._new_global_path_generated = False
        # a condition variable used for
        self._global_path_con = threading.Condition()

    def _spawn_robot(self, robot_yaml_path: str):
        request = SpawnModelRequest()
        request.yaml_path = robot_yaml_path
        request.name = "myrobot"
        request.ns = self.ns
        self._srv_spawn_model(request)

    def _get_robot_configration(self, robot_yaml_path):
        """get robot info e.g robot name, radius, Laser related infomation

        Args:
            robot_yaml_path ([type]): [description]
        """
        self.ROBOT_NAME = os.path.basename(robot_yaml_path).split('.')[0]
        with open(robot_yaml_path, 'r') as f:
            robot_data = yaml.safe_load(f)
            # get robot radius
            for body in robot_data['bodies']:
                if body['name'] == "base_footprint":
                    for footprint in body['footprints']:
                        if footprint['type'] == 'circle':
                            self.ROBOT_RADIUS = footprint.setdefault(
                                'radius', 0.2)
            # get laser_update_rate
            for plugin in robot_data['plugins']:
                if plugin['type'] == 'Laser':
                    self.LASER_UPDATE_RATE = plugin.setdefault(
                        'update_rate', 1)

    def update_map(self, new_map: OccupancyGrid):
        self.map = new_map
        # a tuple stores the indices of the non-occupied spaces. format ((y,....),(x,...)
        self._free_space_indices = generate_freespace_indices(self.map)

    def move_robot(self, pose: Pose2D):
        """move the robot to a given position

        Args:
            pose (Pose2D): target postion
        """
        # call service move_model

        srv_request = MoveModelRequest()
        srv_request.name = self.ROBOT_NAME
        srv_request.pose = pose

        # call service
        self._srv_move_model(srv_request)
        if self.is_training_mode:
            # a necessaray procedure to let the flatland publish the
            # laser,odom's Transformation, which are needed for creating
            # global path
            # assert self.step_size * \
            #     self.LASER_UPDATE_RATE == 1, f"TO run the traning successfully, make sure the laser_update_rate*step_size == 1 \
            #     \n\tcurrent step_size:\t {self.step_size}\n\tcurrent laser's update rate:\t {self.LASER_UPDATE_RATE} "
            for _ in range(math.ceil(1/(self.step_size*self.LASER_UPDATE_RATE))):
                self._step_world()

    def set_start_pos_random(self):
        start_pos = Pose2D()
        start_pos.x, start_pos, start_pos.theta = get_random_pos_on_map(
            self._free_space_indices, self.map, self.ROBOT_RADIUS)
        self.move_robot(start_pos)

    def set_start_pos_goal_pos(self, isCirclePattern:bool, start_pos: Union[Pose2D, None]= None,
                                                                goal_pos: Union[Pose2D, None] = None, min_dist=7,
                                                                obs_dict=None, forbiddenPoints=None):
        """set up start position and the goal postion. Path validation checking will be conducted. If it failed, an
        exception will be raised.

        Args:
            start_pos (Union[Pose2D,None], optional): start position. if None, it will be set randomly. Defaults to None.
            goal_pos (Union[Pose2D,None], optional): [description]. if None, it will be set randomly .Defaults to None.
            isCirclePattern:bool.  if true, start_pos and goal_pos will be spawned around the circle, otherwise will be set randomly.
            min_dist (float): minimum distance between start_pos and goal_pos
            obs_dict: observations from the last time step
        Exception:
            Exception("can not generate a path with the given start position and the goal position of the robot")
        """

        def dist(x1, y1, x2, y2):
            return math.sqrt((x1 - x2)**2 + (y1 - y2)**2)

        forbiddenZones=[]
        if obs_dict is not None:
            # print("calculate the forbidden zones")
            coordinates=obs_dict['human_coordinates_in_robot_frame'].T
            tys=obs_dict['human_type']
            for i, coordinate in enumerate(coordinates):
                if tys[i]==0: #adult
                    forbiddenZones.append((coordinate[0],coordinate[1],self.safe_dist_adult*1.05))
                elif tys[i]==1: #child
                    forbiddenZones.append((coordinate[0],coordinate[1],self.safe_dist_child*1.05))
                elif tys[i]==3: #elder
                    forbiddenZones.append((coordinate[0],coordinate[1],self.safe_dist_elder*1.05))

        if forbiddenPoints is not None:
            for coordinate in forbiddenPoints: # use the safe_dist of elder becuase it is the largest among all types of humans
                forbiddenZones.append((coordinate[0],coordinate[1],self.safe_dist_elder*1.05))

        if start_pos is None or goal_pos is None:
            # if any of them need to be random generated, we set a higher threshold,otherwise only try once
            max_try_times = 100
        else:
            max_try_times = 1

        i_try = 0
        start_pos_ = None
        goal_pos_ = None
        while i_try < max_try_times:
            if isCirclePattern:
                if start_pos is None:
                    start_pos_ = Pose2D()
                    goal_pos_ = Pose2D()
                    start_pose, end_pose = get_robot_goal_on_map(
                        self._free_space_indices, self.map, 10 ,self.ROBOT_RADIUS ,forbiddenZones)
                    start_pos_.x, start_pos_.y, start_pos_.theta= start_pose[0], start_pose[1], start_pose[2]
                    goal_pos_.x, goal_pos_.y, goal_pos_.theta= end_pose[0] , end_pose[1] , end_pose[2]
                    goal_pos_.x, goal_pos_.y, goal_pos_.theta= 20 , 8 , 0
                    start_pos_.x, start_pos_.y, start_pos_.theta= -2, 8, 0
                    # print('g', goal_pos_.x)
                else:
                    start_pos_ = start_pos
                    goal_pos_ = goal_pos
            else:
                if start_pos is None:
                    start_pos_ = Pose2D()
                    start_pos_.x, start_pos_.y, start_pos_.theta = get_random_pos_on_map(
                        self._free_space_indices, self.map, self.ROBOT_RADIUS, forbidden_zones=forbiddenZones)
                    start_pos_.x, start_pos_.y, start_pos_.theta= -3.5, -3, 0
                else:
                    start_pos_ = start_pos
                if goal_pos is None:
                    goal_pos_ = Pose2D()
                    goal_pos_.x, goal_pos_.y, goal_pos_.theta = get_random_pos_on_map(
                        self._free_space_indices, self.map, self.ROBOT_RADIUS, forbidden_zones=forbiddenZones)
                    goal_pos_.x, goal_pos_.y, goal_pos_.theta= 23.5 , 16 , 0
                else:
                    goal_pos_ = goal_pos
            if dist(start_pos_.x, start_pos_.y, goal_pos_.x, goal_pos_.y) < min_dist:
                i_try += 1
                continue
            # move the robot to the start pos
            self.move_robot(start_pos_)
            try:
                # publish the goal, if the gobal plath planner can't generate a path, a, exception will be raised.
                self.publish_goal(goal_pos_.x, goal_pos_.y, goal_pos_.theta)
                self.setGoalInfoToPedsim(goal_pos_)
                # print("publish goal")
                break
            except rospy.ServiceException:
                i_try += 1
        if i_try == max_try_times:
            # TODO Define specific type of Exception
            raise rospy.ServiceException(
                "can not generate a path with the given start position and the goal position of the robot")
        else:
            return start_pos_, goal_pos_

    def _validate_path(self):
        """ after publish the goal, the global planner should publish path. If it's not published within 0.1s, an exception will
        be raised.

        Raises:
            Exception: [description]
        """

        with self._global_path_con:
            self._global_path_con.wait_for(
                predicate=self._new_global_path_generated, timeout=0.1)
            if not self._new_global_path_generated:
                raise rospy.ServiceException(
                    "can not generate a path with the given start position and the goal position of the robot")
            else:
                self._new_global_path_generated = False  # reset it

    def publish_goal(self, x, y, theta):
        """
        Publishing goal (x, y, theta)
        :param x x-position of the goal
        :param y y-position of the goal
        :param theta theta-position of the goal
        """
        self._old_global_path_timestamp = self._global_path.header.stamp
        goal = PoseStamped()
        goal.header.stamp = rospy.get_rostime()
        goal.header.frame_id = "map"
        goal.pose.position.x = x
        goal.pose.position.y = y
        quaternion = tf.transformations.quaternion_from_euler(0, 0, 0)
        goal.pose.orientation.w = quaternion[0]
        goal.pose.orientation.x = quaternion[1]
        goal.pose.orientation.y = quaternion[2]
        goal.pose.orientation.z = quaternion[3]
        self._goal_pub.publish(goal)
        # self._validate_path()

    def _global_path_callback(self, global_path: Path):
        with self._global_path_con:
            self._global_path = global_path
            if self._old_global_path_timestamp is None or global_path.header.stamp > self._old_global_path_timestamp:
                self._new_global_path_generated = True
            self._global_path_con.notify()

    def setGoalRadius(self, goal_radius):
        self.goalRadius=goal_radius

    def setGoalInfoToPedsim(self, goal_pos):
        #partition of the goal area as a polygon
        vertices=np.array([[goal_pos.x, goal_pos.y+1.1*self.goalRadius],
                                                [goal_pos.x, goal_pos.y-1.1*self.goalRadius],
                                                [goal_pos.x+1.1*self.goalRadius, goal_pos.y],
                                                [goal_pos.x-1.1*self.goalRadius, goal_pos.y]])
        add_pedsim_srv=SpawnObstacleRequest()
        size=vertices.shape[0]
        for i in range(size):
            lineObstacle=LineObstacle()
            lineObstacle.start.x,lineObstacle.start.y=vertices[i,0], vertices[i,1]
            lineObstacle.end.x,lineObstacle.end.y=vertices[(i+1)%size,0], vertices[(i+1)%size,1]
            add_pedsim_srv.staticObstacles.obstacles.append(lineObstacle)
        # tell the pedsim the position of goal
        self.__add_obstacle_srv.call(add_pedsim_srv)


import unittest
from mock import Mock, patch

import foundations_contrib.helpers.shell as shell

from foundations_spec.helpers.spec import Spec
from foundations_spec.helpers import let

class TestShell(Spec):

    @let
    def winreg(self):
        # hack to deal with testing on non-windows machines
        module_callback = self.patch('foundations_contrib.helpers.shell._winreg_module')
        module_callback.return_value = Mock()
        return module_callback.return_value

    def test_find_bash_returns_default_bash(self):
        self.assertEqual('/bin/bash', shell.find_bash())

    @patch('os.name', 'nt')
    def test_queries_correct_registry_on_windows(self):
        self.winreg.HKEY_CLASSES_ROOT = 'HKEY_CLASSES_ROOT'
        self.winreg.QueryValue.return_value = 'null 0 0'
        shell.find_bash()
        self.winreg.QueryValue.assert_called_with('HKEY_CLASSES_ROOT', 'Directory\shell\git_shell\command')

    @patch('os.name', 'nt')
    def test_find_bash_returns_bash_on_windows(self):
        self.winreg.QueryValue.return_value = '"C:\\path to\\git-bash.exe" 0 0'
        self.assertEqual('C:\\path to\\bin\\bash.exe', shell.find_bash())

    @patch('os.name', 'nt')
    def test_find_bash_returns_bash_on_windows_different_path(self):
        self.winreg.QueryValue.return_value = '"C:\\Program Files\\git\\git-bash.exe" 3 2'
        self.assertEqual('C:\\Program Files\\git\\bin\\bash.exe', shell.find_bash())


                                                      #LEETCODE STRING PROBLEMS

#QUESTION NUMBER :: 844. Backspace String Compare  'Easy'
'''-------------------------------------------------------------------------------------------------------------------------------------'''
class Solution:
    def backspaceCompare(self, s: str, t: str) -> bool:
        def process_string(s: str) -> str:
            stack = []
            for char in s:
                if char != '#':
                    stack.append(char)
                elif stack:
                    stack.pop()
            return ''.join(stack)

        return process_string(s) == process_string(t)
'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER :: 917. Reverse Only Letters

class Solution:
    def reverseOnlyLetters(self, s: str) -> str:
        alpha_chars = [ char for char in s if char.isalpha()]
        reversed_chars = []
        for char in s:
            if char.isalpha():
                reversed_chars.append(alpha_chars.pop())
            else:
                reversed_chars.append(char)
        return ''.join(reversed_chars)

'''--------------------------------------------------------------------------------------------------------------------------------------'''

#QUESTION NUMBER : 2810. Faulty Keyboard

class Solution:
    def finalString(self, s: str) -> str:
       rev_str = ""
       for i in range(len(s)):
           if s[i] == "i":
               rev_str = rev_str[:i][::-1]
           else:
               rev_str += s[i]
       return rev_str

'''--------------------------------------------------------------------------------------------------------------------------------------'''

#QUESTION NUMBER : 1185. Day of the Week

from collections import Counter
from datetime import datetime
import calendar
from itertools import accumulate
import math
class Solution:
    def dayOfTheWeek(self, day: int, month: int, year: int) -> str:
        date = ""
        if day < 10:
            date += "0"
        date += str(day) + "/"
        if month < 10:
            date += "0"
        date += str(month) + "/"
        date += str(year)
        return calendar.day_name[datetime.strptime(date,"%d/%m/%Y").weekday()]

'''--------------------------------------------------------------------------------------------------------------------------------------'''

#QUESTION NUMBER : 2744. Find Maximum Number of String Pairs

class Solution:
    def maximumNumberOfStringPairs(self, words: List[str]) -> int:
        count = 0
        for i in range(len(words)-1):
            for j in range(i+1,len(words)):
                if words[i]==words[j][::-1]:
                    count += 1

'''--------------------------------------------------------------------------------------------------------------------------------------'''

#QUESTION NUMBER :  1332. Remove Palindromic Subsequences

# Method : 1

class Solution:
    def removePalindromeSub(self, s: str) -> int:
       return 1 if s == s [::-1] else 2

# Method Number: 2
class Solution:
    def removePalindromeSub(self, s: str) -> int:
        if not s: return 0
        l,r = 0,len(s)-1
        while l<r:
            if s[l] != s[r]:
                return 2
            l += 1
            r -= 1
        return 1

'''--------------------------------------------------------------------------------------------------------------------------------------'''

#QUESTION NUMBER : 2788. Split Strings by Separator

class Solution:
    def splitWordsBySeparator(self, words: List[str], separator: str) -> List[str]:
        ls = []
        for data in words:
            word = data.split(separator)
            for data in word:
                if(data != ""):
                    ls.append(data)
        return ls

'''--------------------------------------------------------------------------------------------------------------------------------------'''

#QUESTION NUMBER :      2710. Remove Trailing Zeros From a String

class Solution:
    def removeTrailingZeros(self, num: str) -> str:
       while(num[-1] == '0'):
           num = num[:-1]
       return num

'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER : 1021. Remove Outermost Parentheses

class Solution:
    def removeOuterParentheses(self, s: str) -> str:
        res = []
        count = 0
        for i in s :
            if i == '(' and count > 0:
                res.append(i)
            if i == ')' and count > 1:
                res.append(i)
            count += 1 if i == '(' else -1
        return "".join(res)

'''--------------------------------------------------------------------------------------------------------------------------------------'''

#QUESTION NUMBER : 1678. Goal Parser Interpretation

class Solution:
    def interpret(self, command: str) -> str:
         command = command.replace("()","o")
         command = command.replace("(al)", "al")
         return command
'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER : 1528. Shuffle String

class Solution:
    def restoreString(self, s: str, indices: List[int]) -> str:
        res = ''
        for i in range(len(indices)):
           res += s[indices.index(i)]
        return res

'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER : 1221. Split a String in Balanced Strings
class Solution:
    def balancedStringSplit(self, s: str) -> int:
       return list(accumulate(1 if c =='R' else -1 for c in s)).count(0)
'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER : 1592. Rearrange Spaces Between Words

class Solution:
    def reorderSpaces(self, text: str) -> str:
        space = text.count(" ")
        words = text.split()
        wordlen = len(words)
        if wordlen-1>0:
            c = space//(wordlen-1)
        else:
            c = space
        res = ""
        for i in words:
            res += i
            if space >= c:
                res += " "*c
                space -= c

        if space>0:
            res += " "*space
        return res

'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER : 2325. Decode the Message
class Solution:
    def decodeMessage(self, key: str, message: str) -> str:
       dic = {' ':' '}
       c = 97
       for i in range(len(key)):
           if key[i] not in dic:
               dic[key[i]] = chr(c)
               c += 1

       result = ''
       for i in range(len(message)):
            result += dic[message[i]]
       return result

'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER : 2160. Minimum Sum of Four Digit Number After Splitting Digits

class Solution:
    def minimumSum(self, num: int) -> int:
        nums = list(str(num))
        nums.sort()
        return int(nums[0] + nums[-1]) + int(nums[1] + nums[2])

'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER : 1859. Sorting the Sentence
class Solution:
    def sortSentence(self, s: str) -> str:
        words = s.split()

        sorted_words = sorted(words, key=lambda x: int(''.join(filter(str.isdigit, x))))

        words_without_numbers = [''.join(filter(str.isalpha, word)) for word in sorted_words]

        result = ' '.join(words_without_numbers)

        return result
'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER : 1844. Replace All Digits with Characters
class Solution:
    def replaceDigits(self, s: str) -> str:
        result =''
        for i in range(len(s)):
            if s[i].isnumeric() == True:
                result += chr(ord(s[i-1])+int(s[i]))
            else:
                result +=s[i]
        return result

'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER  : 1812. Determine Color of a Chessboard Square

class Solution:
    def squareIsWhite(self, coordinates: str) -> bool:
        alpha = ['a','b','c','d','e','f','g','h']
        for i in range(len(alpha)):
            for j in range(1,9):
                if alpha[i] + str(j) == coordinates:
                    if (i+j) %  2 == 0:
                        return True
        else:
            return False

'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER  : 1374. Generate a String With Characters That Have Odd Counts

class Solution:
    def generateTheString(self, n: int) -> str:
        return "a" * n if  n % 2 else "a" * (n-1) +"b"

'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER  : 2000. Reverse Prefix of Word
class Solution:
    def reversePrefix(self, word: str, ch: str) -> str:
        reverse = ""
        if ch not in word:
            return word
        for i in range(len(word)):
            if word[i] == ch:
                reverse += word[0:i+1]
                reverse = reverse[::-1]
                reverse += word[i+1:]
                break
        return reverse

'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER  :1941. Check if All Characters Have Equal Number of Occurrences

#Method 1
class Solution:
    def areOccurrencesEqual(self, s: str) -> bool:
        sets = set()
        for i in range(len(s)):
            count = 0
            for j in range(len(s)):
                if s[i] == s[j]:
                    count += 1
            sets.add(count)

        return True if len(sets) == 1 else False

#Mehtod 2
class Solution:
    def areOccurrencesEqual(self, s: str) -> bool:
        lst=[]
        for i in s:
            c=s.count(i)
            lst.append(c)
        if min(lst)==max(lst):
            return True
        else:
            return False

#Method 3
class Solution:
    def areOccurrencesEqual(self, s: str) -> bool:
        char_count = {}
        for char in s:
            char_count[char] = char_count.get(char, 0) + 1

        return len(set(char_count.values())) == 1

'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER  : 1309. Decrypt String from Alphabet to Integer Mapping

#Method 1
class Solution:
    def freqAlphabets(self, s: str) -> str:
        for i in range(26,0,-1):
            s = s.replace(str(i)+"#", chr(ord("a")+i-1)) if i>9 else s.replace(str(i), chr(ord("a")+i-1))
        return s

#Method 2
class Solution:
    def freqAlphabets(self, s: str) -> str:
        ans = ""
        i = len(s)-1
        while i>=0:
            if s[i]=="#":
                ans = ans + chr(int(s[i-2:i])+96)
                i=i-2
            else:
                ans = ans + chr(int(s[i])+96)
            i -= 1
        return ans[::-1]
'''--------------------------------------------------------------------------------------------------------------------------------------'''

#QUESTION NUMBER  : 2278. Percentage of Letter in String

class Solution:
    def percentageLetter(self, s: str, letter: str) -> int:
        count = s.count(letter)
        percentage = (count / len(s)) * 100
        return math.floor(percentage)

'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER  : 2351. First Letter to Appear Twice
class Solution:
    def repeatedCharacter(self, s: str) -> str:
        res = ''
        for i in range(len(s)):
            if s[i] in res:
                return s[i]
            else:
                res += s[i]
        return res

'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER  : 1684. Count the Number of Consistent Strings

#Method 1
class Solution:
    def countConsistentStrings(self, allowed: str, words: List[str]) -> int:
        count = 0
        for word in words:
            if all(char in allowed for char in word):
                count += 1
        return count

#Method 2
class Solution:
    def countConsistentStrings(self, allowed: str, words: List[str]) -> int:
        res = 0
        allow = Counter(allowed)
        for word in words:
            flag = 1
            for letter in word:
                if letter not in allow:
                    flag = 0
                else:
                    continue
            if flag:
                res += 1
        return res
'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER  : 2108. Find First Palindromic String in the Array
class Solution:
    def firstPalindrome(self, words: List[str]) -> str:
        for i in range(len(words)):
            current_word = words[i]
            if current_word == current_word[::-1]:
                return current_word
        return ""

'''--------------------------------------------------------------------------------------------------------------------------------------'''
#QUESTION NUMBER  : 2185. Counting Words With a Given Prefix

class Solution:
    def prefixCount(self, words: List[str], pref: str) -> int:
        count = 0
        for i in range(len(words)):
            current_word = words[i]
            if current_word[:len(pref)] == pref:
                count += 1
        return count