import pytest
import json
import pandas as pd
import pandas.util.testing as tm

from conftest import package
from conftest import load_json, load_csv

####### Preparation

# Load test_functions_parameters.json containing the scenarios for the tests
dictionary_json = load_json("test_functions_parameters.json")

####### Tests

### Simple Objects

arguments, values = dictionary_json["function_simple_objects"].values()
@pytest.mark.parametrize(arguments, values)
def test_function_simple_objects(x, y, element, result):
    '''
        Test function of function_simple_objects. This test is able
        to handle any function taking any number of the following
        simple objects as inputs and outputs :
            - integers
            - strings
            - floats
            - booleans

        To do :
            1 - Relate it to the right function by replacing the names
            2 - Give the right elements as input and parameters in "test_functions_parameters".
    '''

    output = package.function_simple_objects(x,y,element)

    assert output == result

### Lists

arguments, values = dictionary_json["function_list"].values()
@pytest.mark.parametrize(arguments, values)
def test_function_list(filename, case):
    '''
        Test function of function_list. This test shows an example of how to
        handle a function taking a list as input and outputs.

        To customize it :
            1 - Relate it to the right function by replacing the names
            2 - Give the right elements as input and parameters in "test_functions_parameters".
            3 - Add the right json file in the /json folder to generate your list inputs and outputs.
    '''

    l, l_result = load_json(filename, case)
    l_output = package.function_list(l)

    assert l_output == l_result

### Pandas Dataframes

arguments, values = dictionary_json["function_dataframe"].values()
@pytest.mark.parametrize(arguments, values)
def test_function_dataframe(df_filename, df_result_filename):
    '''
        Test function of function_dataframe. This test shows an
        example of how to handle a function taking a pandas dataframe as input
        and output.

        To customize it :
            1 - Relate it to the right function by replacing the names
            2 - Give the right elements as input and parameters in "test_functions_parameters".
            3 - Add the right csv files in the /csv folder to generate your dataframe inputs and outputs.
    '''

    df, df_result = load_csv(df_filename), load_csv(df_filename)
    df_output = package.function_dataframe(df)

    tm.assert_frame_equal(df_output, df_result)

### Global Example (Simple Objects + Lists + Dataframes)

arguments, values = dictionary_json["function_complex"].values()
@pytest.mark.parametrize(arguments, values)
def test_function_complex(df_filename, l_filename, case, x, s, df_result_filename, arg_1_result):
    '''
        Test function of function_complex. This test shows an
        example of how to handle a complex combination of inputs and outpus.
        This test is a simple combination of the three test presented above.

        To customize it :
            1 - Relate it to the right function by replacing the names
            2 - Give the right elements as input and parameters in "test_functions_parameters".
            3 - Add the right csv files in the /csv folder to generate your dataframe inputs and outputs.
            4 - Add the right json files in the /json folder to generate your list inputs and outputs.
    '''

    df, df_result = load_csv(df_filename), load_csv(df_result_filename)
    l = load_json(l_filename, case)

    df_output, arg_1_output = package.function_complex(df, l, x, s)

    tm.assert_frame_equal(df_output, df_result)
    assert arg_1_output == arg_1_result


"""API functionality for websites"""
import logging
import os
from datetime import datetime
from typing import Optional
from uuid import UUID

from django.conf import settings
from django.core.files.uploadedfile import UploadedFile
from django.db.models import CharField, Q, QuerySet
from django.db.models.functions import Cast, Length
from magic import Magic
from mitol.common.utils import max_or_none, now_in_utc
from mitol.mail.api import get_message_sender

from content_sync.constants import VERSION_DRAFT, VERSION_LIVE
from main.utils import NestableKeyTextTransform
from users.models import User
from videos.constants import (
    YT_MAX_LENGTH_DESCRIPTION,
    YT_MAX_LENGTH_TITLE,
    YT_THUMBNAIL_IMG,
)
from websites.constants import (
    CONTENT_FILENAME_MAX_LEN,
    PUBLISH_STATUS_ABORTED,
    PUBLISH_STATUS_ERRORED,
    PUBLISH_STATUS_SUCCEEDED,
    PUBLISH_STATUSES_FINAL,
    RESOURCE_TYPE_VIDEO,
)
from websites.messages import (
    PreviewOrPublishFailureMessage,
    PreviewOrPublishSuccessMessage,
)
from websites.models import Website, WebsiteContent, WebsiteStarter
from websites.utils import get_dict_field, get_dict_query_field, set_dict_field

log = logging.getLogger(__name__)

def get_valid_new_filename(
    website_pk: str,
    dirpath: Optional[str],
    filename_base: str,
    exclude_text_id: Optional[str] = None,
) -> str:
    """
    Given a filename to act as a base/prefix, returns a filename that will satisfy unique constraints,
    adding/incrementing a numerical suffix as necessary.

    Examples:
        In database: WebsiteContent(filename="my-filename")...
            get_valid_new_filename("my-filename") == "my-filename2"
        In database: WebsiteContent(filename="my-filename99")...
            get_valid_new_filename("my-filename99") == "my-filename100"
    """  # noqa: E501
    website_content_qset = WebsiteContent.objects.all_with_deleted().filter(
        website_id=website_pk, dirpath=dirpath
    )
    if exclude_text_id is not None:
        website_content_qset = website_content_qset.exclude(text_id=exclude_text_id)
    filename_exists = website_content_qset.filter(filename=filename_base).exists()
    if not filename_exists:
        return filename_base
    return find_available_name(
        website_content_qset,
        filename_base,
        "filename",
        max_length=CONTENT_FILENAME_MAX_LEN,
    )

def get_valid_new_slug(slug_base: str, path: str) -> str:
    """
    Given a slug to act as a base/prefix, returns a slug that will satisfy unique constraints,
    adding/incrementing a numerical suffix as necessary.
    """  # noqa: E501
    starter_qset = WebsiteStarter.objects.exclude(path=path)
    slug_exists = starter_qset.filter(slug=slug_base).exists()
    if not slug_exists:
        return slug_base
    return find_available_name(starter_qset, slug_base, "slug", max_length=30)

def find_available_name(
    website_content_qset: QuerySet,
    initial_filename_base: str,
    fieldname: str,
    max_length: Optional[int] = CONTENT_FILENAME_MAX_LEN,
    extension: Optional[str] = None,
) -> str:
    """
    Returns a filename with the lowest possible suffix given some base filename. If the applied suffix
    makes the filename longer than the filename max length, characters are removed from the
    right of the filename to make room.

    EXAMPLES:
    initial_filename_base = "myfile"
        Existing filenames = "myfile"
        Return value = "myfile1"
    initial_filename_base = "myfile"
        Existing filenames = "myfile", "myfile1" through "myfile5"
        Return value = "myfile6"
    initial_filename_base = "abcdefghijklmnopqrstuvwxyz" (26 characters, assuming 26 character max)
        Existing filenames = "abcdefghijklmnopqrstuvwxyz"
        Return value = "abcdefghijklmnopqrstuvwxy1"  # pragma: allowlist secret
    initial_filename_base = "abcdefghijklmnopqrstuvwxy" (25 characters long, assuming 26 character max)
        Existing filenames = "abc...y", "abc...y1" through "abc...y9"
        Return value = "abcdefghijklmnopqrstuvwx10"  # pragma: allowlist secret
    """  # noqa: E501, D401
    # Keeps track of the number of characters that must be cut from the filename to be less than  # noqa: E501
    # the filename max length when the suffix is applied.
    chars_to_truncate = 0 if len(initial_filename_base) < max_length else 1
    # Any query for suffixed filenames could come up empty. The minimum suffix will be added to  # noqa: E501
    # the filename in that case.
    current_min_suffix = 2
    if extension is None:
        extension = ""
    while chars_to_truncate < len(initial_filename_base):
        name_base = initial_filename_base[
            0 : len(initial_filename_base) - chars_to_truncate
        ]
        kwargs = {f"{fieldname}__regex": rf"{name_base}[0-9]+{extension}"}
        # Find names that match the namebase and have a numerical suffix, then find the max suffix  # noqa: E501
        existing_names = website_content_qset.filter(**kwargs).values_list(
            fieldname, flat=True
        )
        if extension:
            existing_names = [
                os.path.splitext(name)[0] for name in existing_names  # noqa: PTH122
            ]  # noqa: PTH122, RUF100
        max_suffix = max_or_none(
            int(filename[len(name_base) :]) for filename in existing_names
        )
        if max_suffix is None:
            return f"{''.join([name_base, str(current_min_suffix)])}{extension}"
        else:
            next_suffix = max_suffix + 1
            candidate_name = "".join([name_base, str(next_suffix), extension])
            # If the next suffix adds a digit and causes the filename to exceed the character limit,  # noqa: E501
            # keep searching.
            if len(candidate_name) <= max_length:
                return candidate_name
        # At this point, we know there are no suffixes left to add to this filename base that was tried,  # noqa: E501
        # so we will need to remove characters from the end of that filename base to make room for a longer  # noqa: E501
        # suffix.
        chars_to_truncate = chars_to_truncate + 1
        available_suffix_digits = max_length - (
            len(initial_filename_base) - chars_to_truncate
        )
        # If there is space for 4 digits for the suffix, the minimum value it could be is 1000, or 10^3  # noqa: E501
        current_min_suffix = 10 ** (available_suffix_digits - 1)
    return None

def fetch_website(filter_value: str) -> Website:
    """
    Attempts to fetch a Website based on several properties
    """  # noqa: D401
    if len(filter_value) in {32, 36}:
        try:
            parsed_uuid = UUID(filter_value, version=4)
            website = Website.objects.filter(uuid=parsed_uuid).first()
            if website is not None:
                return website
        except ValueError:
            pass
    website_results = Website.objects.filter(
        Q(name__iexact=filter_value)
        | Q(title__iexact=filter_value)
        | Q(short_id__iexact=filter_value)
    ).all()
    if len(website_results) == 0:
        msg = f"Could not find a Website with a matching uuid, name, short_id, or title ('{filter_value}')"  # noqa: E501
        raise Website.DoesNotExist(msg)
    if len(website_results) == 1:
        return website_results[0]

    sorted_results = sorted(
        website_results, key=lambda _website: 1 if _website.name == filter_value else 2
    )
    return next(sorted_results)

def is_ocw_site(website: Website) -> bool:
    """Return true if the site is an OCW site"""
    return website.starter and website.starter.slug == settings.OCW_COURSE_STARTER_SLUG

def update_youtube_thumbnail(
    website_id: str, metadata: dict, overwrite=False  # noqa: FBT002
):
    """Assign a youtube thumbnail url if appropriate to a website's metadata"""
    website = Website.objects.get(uuid=website_id)
    if is_ocw_site(website):
        youtube_id = get_dict_field(metadata, settings.YT_FIELD_ID)
        if youtube_id and (
            not get_dict_field(metadata, settings.YT_FIELD_THUMBNAIL) or overwrite
        ):
            set_dict_field(
                metadata,
                settings.YT_FIELD_THUMBNAIL,
                YT_THUMBNAIL_IMG.format(video_id=youtube_id),
            )

def videos_with_unassigned_youtube_ids(website: Website) -> list[WebsiteContent]:
    """Return a list of WebsiteContent objects for videos with unassigned youtube ids"""
    if not is_ocw_site(website):
        return []
    query_resource_type_field = get_dict_query_field(
        "metadata", settings.FIELD_RESOURCETYPE
    )
    query_id_field = f"metadata__{'__'.join(settings.YT_FIELD_ID.split('.'))}"
    return WebsiteContent.objects.filter(
        Q(website=website)
        & Q(**{query_resource_type_field: RESOURCE_TYPE_VIDEO})
        & (
            Q(**{f"{query_id_field}__isnull": True})
            | Q(**{f"{query_id_field}": None})
            | Q(**{query_id_field: ""})
        )
    )

def videos_with_truncatable_text(website: Website) -> list[WebsiteContent]:
    """Return a list of WebsiteContent objects with text fields that will be truncated in YouTube"""  # noqa: E501
    if not is_ocw_site(website):
        return []
    query_resource_type_field = get_dict_query_field(
        "metadata", settings.FIELD_RESOURCETYPE
    )
    yt_description_fields = settings.YT_FIELD_DESCRIPTION.split(".")
    return (
        WebsiteContent.objects.annotate(
            desc_len=Length(
                Cast(
                    NestableKeyTextTransform("metadata", *yt_description_fields),
                    CharField(),
                )
            )
        )
        .annotate(title_len=Length("title"))
        .filter(
            Q(website=website)
            & Q(**{query_resource_type_field: RESOURCE_TYPE_VIDEO})
            & (
                Q(desc_len__gt=YT_MAX_LENGTH_DESCRIPTION)
                | Q(title_len__gt=YT_MAX_LENGTH_TITLE)
            )
        )
    )

def videos_missing_captions(website: Website) -> list[WebsiteContent]:
    """Return a list of WebsiteContent objects for videos with unassigned captions"""
    if not is_ocw_site(website):
        return []
    query_resource_type_field = get_dict_query_field(
        "metadata", settings.FIELD_RESOURCETYPE
    )
    query_caption_field = get_dict_query_field("metadata", settings.YT_FIELD_CAPTIONS)
    return WebsiteContent.objects.filter(
        Q(website=website)
        & Q(**{query_resource_type_field: RESOURCE_TYPE_VIDEO})
        & (Q(**{query_caption_field: None}) | Q(**{query_caption_field: ""}))
    )

def draft_content(website: Website) -> list[WebsiteContent]:
    """Return a list of WebsiteContent objects that are set to Draft"""
    query_draft_field = get_dict_query_field("metadata", "draft")
    return WebsiteContent.objects.filter(
        Q(website=website) & Q(**{query_draft_field: True})
    )

def mail_on_publish(
    website_name: str, version: str, success: bool, user_id: int  # noqa: FBT001
):
    """Send a publishing success or failure message to the requesting user"""
    message = (
        PreviewOrPublishSuccessMessage if success else PreviewOrPublishFailureMessage
    )
    website = Website.objects.get(name=website_name)
    with get_message_sender(message) as sender:
        sender.build_and_send_message(
            User.objects.get(id=user_id),
            {
                "site": {
                    "title": website.title,
                    "url": website.get_full_url(version),
                },
                "version": version,
            },
        )

def detect_mime_type(uploaded_file: UploadedFile) -> str:
    """Detect mime type of an uploaded file"""
    magic = Magic(mime=True)
    chunk = next(uploaded_file.chunks(chunk_size=2048))
    return magic.from_buffer(chunk)

def reset_publishing_fields(website_name: str):
    """Reset all publishing fields to allow a fresh publish request"""
    now = now_in_utc()
    Website.objects.filter(name=website_name).update(
        has_unpublished_live=True,
        has_unpublished_draft=True,
        live_publish_status=None,
        draft_publish_status=None,
        live_publish_status_updated_on=now,
        draft_publish_status_updated_on=now,
        latest_build_id_live=None,
        latest_build_id_draft=None,
    )

def update_website_status(
    website: Website,
    version: str,
    status: str,
    update_time: datetime,
    unpublished=False,  # noqa: FBT002
):
    """Update some status fields in Website"""
    if version == VERSION_DRAFT:
        user = website.draft_last_published_by
        update_kwargs = {
            "draft_publish_status": status,
            "draft_publish_status_updated_on": update_time,
        }
        if status in PUBLISH_STATUSES_FINAL:
            if status == PUBLISH_STATUS_SUCCEEDED:
                update_kwargs["draft_publish_date"] = update_time
                update_kwargs["draft_last_published_by"] = None
            else:
                # Allow user to retry
                update_kwargs["has_unpublished_draft"] = True
    elif unpublished:
        user = website.last_unpublished_by
        update_kwargs = {
            "unpublish_status": status,
            "unpublish_status_updated_on": update_time,
            "live_publish_status": None,
            "live_publish_status_updated_on": None,
            "latest_build_id_live": None,
        }
    else:
        user = website.live_last_published_by
        update_kwargs = {
            "live_publish_status": status,
            "live_publish_status_updated_on": update_time,
        }
        if status in PUBLISH_STATUSES_FINAL:
            if status == PUBLISH_STATUS_SUCCEEDED:
                if website.first_published_to_production is None:
                    update_kwargs["first_published_to_production"] = update_time
                update_kwargs["publish_date"] = update_time
                update_kwargs["live_last_published_by"] = None
            else:
                # Allow user to retry
                update_kwargs["has_unpublished_live"] = True
    Website.objects.filter(name=website.name).update(**update_kwargs)
    if status in (PUBLISH_STATUS_ERRORED, PUBLISH_STATUS_ABORTED):
        log.error("A %s pipeline build failed for %s", version, website.name)
    if status in PUBLISH_STATUSES_FINAL and user and not unpublished:
        mail_on_publish(
            website.name, version, status == PUBLISH_STATUS_SUCCEEDED, user.id
        )

def get_content_warnings(website):
    """
    Return array with error/warning messages for any website content missing expected data
    (currently: video youtube ids and captions) or any draft content.
    """  # noqa: E501
    missing_youtube_ids = videos_with_unassigned_youtube_ids(website)

    missing_youtube_ids_titles = [video.title for video in missing_youtube_ids]

    missing_captions_titles = [
        video.title for video in videos_missing_captions(website)
    ]

    truncatable_video_titles = [
        video.title for video in videos_with_truncatable_text(website)
    ]

    draft_content_titles = [content.title for content in draft_content(website)]

    messages = []

    if len(missing_youtube_ids_titles) > 0:
        messages.append(
            f"The following video resources require YouTube IDs: {', '.join(missing_youtube_ids_titles)}"  # noqa: E501
        )
    if len(missing_captions_titles) > 0:
        messages.append(
            f"The following videos have missing captions: {', '.join(missing_captions_titles)}"  # noqa: E501
        )
    if len(truncatable_video_titles) > 0:
        messages.append(
            f"The following videos have titles or descriptions that will be truncated on YouTube: {', '.join(truncatable_video_titles)}"  # noqa: E501
        )

    if len(draft_content_titles) > 0:
        messages.append(
            f"The following content is still set to Draft: {', '.join(draft_content_titles)}"  # noqa: E501
        )

    return messages

def sync_website_title(content: WebsiteContent):
    """Sync sitemetadata title with Website.title"""
    title = get_dict_field(content.metadata, settings.FIELD_METADATA_TITLE)
    if title:
        content.website.title = title
        content.website.save()

def get_website_in_root_website_metadata(website, version):
    """
    Get a dict of metadata for website type WebsiteContent objects

    Args:
        website (Website): The Website to get the metadata object for
        version (string): The version (draft / live) that the metadata will be used with

    Returns:
        dict: A dict of metadata keys to be used with a WebsiteContent object of type "website"
    """  # noqa: E501
    # Start with the sitemetadata WebsiteContent for the given Website
    site_metadata = WebsiteContent.objects.get(
        website=website, type="sitemetadata"
    ).metadata
    # We want this content to show up in lists, but not render pages
    site_metadata["_build"] = {"list": True, "render": False}
    # Set the content to draft if the site is unpublished or has never been published to LIVE  # noqa: E501
    if website.unpublish_status is not None or (
        version != VERSION_LIVE and website.publish_date is None
    ):
        site_metadata["draft"] = True
    # Carry over url_path for proper linking
    site_metadata["url_path"] = website.url_path
    return site_metadata


import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

import clusters
import plot

data = clusters.get_data()

num_clusters = 4
pipe = clusters.get_pipeline_hierarchical(num_clusters)
pipe.fit(data)
_, _, data_pca = clusters.get_pca_data_hierarchical(num_clusters)
data_with_clusters = data
data_with_clusters['predicted_cluster'] = pipe.fit_predict(data)

mean_vals = data_with_clusters.groupby('predicted_cluster', as_index=False).mean()
max_vals = data_with_clusters.groupby('predicted_cluster', as_index=False).max()
min_vals = data_with_clusters.groupby('predicted_cluster', as_index=False).min()
std_dev_vals = data_with_clusters.groupby('predicted_cluster', as_index=False).std()

# print(min_vals.to_markdown())

# Finally we plot all of our data and make it look a bit pretty
plt.style.use("fivethirtyeight")
plt.figure(figsize=(8, 8))
sns.scatterplot(
    x="component_1",
    y="component_2",
    s=50,
    data=data_pca,
    hue="predicted_cluster",
    style="predicted_cluster",
    palette="Set2",
)
plt.title("Hierarchical clustering")
plt.show()

plt.style.use("fivethirtyeight")
plt.figure(figsize=(8, 8))
sns.scatterplot(
    x="component_1",
    y="component_3",
    s=50,
    data=data_pca,
    hue="predicted_cluster",
    style="predicted_cluster",
    palette="Set2",
)
plt.title("Hierarchical clustering")
plt.show()

plt.style.use("fivethirtyeight")
plt.figure(figsize=(8, 8))
sns.scatterplot(
    x="component_2",
    y="component_3",
    s=50,
    data=data_pca,
    hue="predicted_cluster",
    style="predicted_cluster",
    palette="Set2",
)
plt.title("Hierarchical clustering")
plt.show()

# Create a data frame containing our centroids
plot.display_parallel_coordinates(data_with_clusters, num_clusters)

plot.display_parallel_coordinates_centroids(mean_vals, num_clusters)


#CHALLENGE 1: REVERSE STRING
'''
reverse_string('awesome') # 'emosewa'
reverse_string('Colt') # 'tloC'
reverse_string('Elie') # 'eilE'
'''

def reverse_string(string):
    return string[::-1]

##############################################################

#CHALLENGE 2: LIST CHECK

'''
list_check([[],[1],[2,3], (1,2)]) # False
list_check([1, True, [],[1],[2,3]]) # False
list_check([[],[1],[2,3]]) # True
'''

def list_check(lst):
    for x in lst:
        if type(x)!= list:
            return False
    return True

##############################################################

#CHALLENGE 3: REMOVE EVERY OTHER

'''
remove_every_other([1,2,3,4,5]) # [1,3,5]
remove_every_other([5,1,2,4,1]) # [5,2,1]
remove_every_other([1]) # [1]
'''

def remove_every_other(lst):
    new_list = []
    go = "yes"
    for x in lst:
        if go == "yes":
            new_list.append(x)
            go = "no"
        else:
            go = "yes"
    return new_list

##############################################################

#CHALLENGE 4: SUM PAIRS

'''
sum_pairs([4,2,10,5,1], 6) # [4,2]
sum_pairs([11,20,4,2,1,5], 100) # []
'''

def sum_pairs(lst1, num):
    lst = lst1
    for x in lst1:
        if num-x in lst1:
            return[x,num-x]
        else:
            lst.remove(x)
    return []

##############################################################

#CHALLENGE 5: VOWEL COUNT

'''
vowel_count('awesome') # {'a': 1, 'e': 2, 'o': 1}
vowel_count('Elie') # {'e': 2, 'i': 1}
vowel_count('Colt') # {'o': 1}
'''

def vowel_count(string):
    lst = []
    dct = {}
    for x in string.lower():
        if x in "aeiou":
            lst.append(x)
    for x in lst:
       dct[x] = lst.count(x)

    return(dct)

##############################################################

#CHALLENGE 6: TITLEIZE

'''
titleize('this is awesome') # "This Is Awesome"
titleize('oNLy cAPITALIZe fIRSt') # "ONLy CAPITALIZe FIRSt"
'''

def titleize(string):
    return " ".join([x[0].capitalize()+x[1:] for x in string.split(" ")])

##############################################################

#CHALLENGE 7: FIND FACTORS

'''
find_factors(10) # [1,2,5,10 ]
find_factors(11) # [1,11]
find_factors(111) # [1,3,37,111 ]
find_factors(321421) # [1,293,1097,321421 ]
find_factors(412146) # [1,2,3,6,7,9,14,18,21,42,63,126,3271,6542,9813,19626,22897,29439,45794,58878,68691,137382,206073,412146]
'''

def find_factors(num):
    return [x for x in range(1, num+1) if num%x==0]

##############################################################

#CHALLENGE 8: INCLUDES

'''
includes([1, 2, 3], 1) # True
includes([1, 2, 3], 1, 2) # False
includes({ 'a': 1, 'b': 2 }, 1) # True
includes({ 'a': 1, 'b': 2 }, 'a') # False
includes('abcd', 'b') # True
includes('abcd', 'e') # False
'''

def includes(coll, val, index=0):
    if type(coll) == list or type(coll) == str:
        return val in coll[index:]
    elif type(coll) == dict:
        return val in coll.values()

##############################################################

#CHALLENGE 9: TRUNCATE

'''
truncate("Super cool", 2) # "Truncation must be at least 3 characters."
truncate("Super cool", 1) # "Truncation must be at least 3 characters."
truncate("Super cool", 0) # "Truncation must be at least 3 characters."
truncate("Hello World", 6) # "Hel..."
truncate("Problem solving is the best!", 10) # "Problem..."
truncate("Another test", 12) # "Another t..."
truncate("Woah", 4) # "W..."
truncate("Woah", 3) # "..."
truncate("Yo",100) # "Yo"
truncate("Holy guacamole!", 152) # "Holy guacamole!"
'''

def truncate(string, num):
    if num < 3:
        return "Truncation must be at least 3 characters."
    elif len(string)<num-3:
        return string
    return("{}...".format(string[:num-3]))

##############################################################


t = int(input())

for i in range(t):
    s = [[0,0]]
    res = []
    n = int(input())
    for j in range(n):
        x,y = map(int,input().split())
        s.append([x,y])

    s = sorted(s)
    c = True
    for j in range(len(s)-1):
        xi = s[j+1][0] - s[j][0]
        yi = s[j+1][1] - s[j][1]
        if xi < 0 or yi < 0:
            print('NO')
            c = False
            break
        else:
            sss = 'R' * xi + 'U' * yi
            res.append(sss)

    if c == True:
        print('YES')
        print(''.join(res))


from collections import defaultdict
from typing import Any, Dict, List, Text

from neo4j import GraphDatabase
from rasa_sdk.knowledge_base.storage import KnowledgeBase

def _dict_to_cypher(data):
    pieces = []
    for k, v in data.items():
        piece = "{}: '{}'".format(k, v)
        pieces.append(piece)

    join_piece = ", ".join(pieces)

    return "{" + join_piece + "}"

class Neo4jKnowledgeBase(KnowledgeBase):
    def __init__(self, uri, user, password):
        self._driver = GraphDatabase.driver(uri, auth=(user, password))

        self.representation_attribute = defaultdict(lambda: "name")

        self.relation_attributes = {
            "Singer": {},
            "Album": {},
            "Song": {"singer": "SUNG_BY", "album": "INCLUDED_IN"},
        }

        super().__init__()

    def close(self):
        self._driver.close()

    async def get_attributes_of_object(self, object_type: Text) -> List[Text]:
        # transformer for query
        object_type = object_type.capitalize()

        result = self.do_get_attributes_of_object(object_type)

        return result

    def do_get_attributes_of_object(self, object_type) -> List[Text]:
        with self._driver.session() as session:
            result = session.write_transaction(
                self._do_get_attributes_of_object, object_type
            )

        result = result + list(self.relation_attributes[object_type].keys())

        return result

    def _do_get_attributes_of_object(self, tx, object_type) -> List[Text]:
        query = "MATCH (o:{object_type}) RETURN o LIMIT 1".format(
            object_type=object_type
        )
        print(query)
        result = tx.run(
            query,
        )

        record = result.single()

        if record:
            return list(record[0].keys())

        return []

    async def get_representation_attribute_of_object(self, object_type: Text) -> Text:
        """
        Returns a lamdba function that takes the object and returns a string
        representation of it.
        Args:
            object_type: the object type
        Returns: lamdba function
        """
        return self.representation_attribute[object_type]

    def do_get_objects(
        self,
        object_type: Text,
        attributions: Dict[Text, Text],
        relations: Dict[Text, Text],
        limit: int,
    ):
        with self._driver.session() as session:
            result = session.write_transaction(
                self._do_get_objects, object_type, attributions, relations, limit
            )

        return result

    def do_get_object(
        self,
        object_type: Text,
        object_identifier: Text,
        key_attribute: Text,
        representation_attribute: Text,
    ):
        with self._driver.session() as session:
            result = session.write_transaction(
                self._do_get_object,
                object_type,
                object_identifier,
                key_attribute,
                representation_attribute,
                self.relation_attributes[object_type],
            )

        return result

    @staticmethod
    def _do_get_objects(
        tx,
        object_type: Text,
        attributions: Dict[Text, Text],
        relations: Dict[Text, Text],
        limit: int,
    ):
        print("<_do_get_objects>: ", object_type, attributions, relations, limit)
        if not relations:
            # attr only, simple case
            query = "MATCH (o:{object_type} {attrs}) RETURN o LIMIT {limit}".format(
                object_type=object_type,
                attrs=_dict_to_cypher(attributions),
                limit=limit,
            )
            print(query)
            result = tx.run(
                query,
            )

            return [dict(record["o"].items()) for record in result]
        else:
            basic_query = "MATCH (o:{object_type} {attrs})".format(
                object_type=object_type,
                attrs=_dict_to_cypher(attributions),
            )
            sub_queries = []
            for k, v in relations.items():
                sub_query = "MATCH (o)-[:{}]->({{name: '{}'}})".format(k, v)

            where_clause = "WHERE EXISTS { " + sub_query + " }"
            for sub_query in sub_queries[1:]:
                where_clause = "WHERE EXISTS { " + sub_query + " " + where_clause + " }"

            query = (
                basic_query + " " + where_clause + " RETURN o LIMIT {}".format(limit)
            )

            print(query)
            result = tx.run(
                query,
            )

            return [dict(record["o"].items()) for record in result]

    @staticmethod
    def _do_get_object(
        tx,
        object_type: Text,
        object_identifier: Text,
        key_attribute: Text,
        representation_attribute: Text,
        relation: Dict[Text, Text],
    ):
        print(
            "<_do_get_object>: ",
            object_type,
            object_identifier,
            key_attribute,
            representation_attribute,
            relation,
        )
        # preprocess attr value
        if object_identifier.isdigit():
            object_identifier = int(object_identifier)
        else:
            object_identifier = '"{}"'.format(object_identifier)

        # try match key first
        query = "MATCH (o:{object_type} {{{key}:{value}}}) RETURN o, ID(o)".format(
            object_type=object_type, key=key_attribute, value=object_identifier
        )
        print(query)
        result = tx.run(
            query,
        )
        record = result.single()

        if record:
            attr_dict = dict(record[0].items())
            oid = record[1]
        else:
            # try to match representation attribute
            query = "MATCH (o:{object_type} {{{key}:{value}}}) RETURN o, ID(o)".format(
                object_type=object_type,
                key=representation_attribute,
                value=object_identifier,
            )
            print(query)
            result = tx.run(
                query,
            )
            record = result.single()
            if record:
                attr_dict = dict(record[0].items())
                oid = record[1]
            else:
                # finally, failed
                attr_dict = None

        if attr_dict is None:
            return None

        relation_attr = {}
        for k, v in relation.items():
            query = "MATCH (o)-[:{}]->(t) WHERE ID(o)={} RETURN t.name".format(v, oid)
            print(query)
            result = tx.run(query)
            record = result.single()
            if record:
                attr = record[0]
            else:
                attr = None

            relation_attr[k] = attr

        return {**attr_dict, **relation_attr}

    async def get_objects(
        self, object_type: Text, attributes: List[Dict[Text, Text]], limit: int = 5
    ) -> List[Dict[Text, Any]]:
        """
        Query the knowledge base for objects of the given type. Restrict the objects
        by the provided attributes, if any attributes are given.
        Args:
            object_type: the object type
            attributes: list of attributes
            limit: maximum number of objects to return
        Returns: list of objects
        """
        print("get_objects", object_type, attributes, limit)

        # convert attributes to dict
        attrs = {}
        for a in attributes:
            attrs[a["name"]] = a["value"]

        # transformer for query
        object_type = object_type.capitalize()

        # split into attrs and relations
        attrs_filter = {}
        relations_filter = {}
        relation = self.relation_attributes[object_type]
        for k, v in attrs.items():
            if k in relation:
                relations_filter[relation[k]] = v
            else:
                attrs_filter[k] = v

        result = self.do_get_objects(object_type, attrs_filter, relations_filter, limit)

        return result

    async def get_object(
        self, object_type: Text, object_identifier: Text
    ) -> Dict[Text, Any]:
        """
        Returns the object of the given type that matches the given object identifier.
        Args:
            object_type: the object type
            object_identifier: value of the key attribute or the string
            representation of the object
        Returns: the object of interest
        """
        # transformer for query
        object_type = object_type.capitalize()

        result = self.do_get_object(
            object_type,
            object_identifier,
            await self.get_key_attribute_of_object(object_type),
            await self.get_representation_attribute_of_object(object_type),
        )

        return result

if __name__ == "__main__":
    import asyncio

    kb = Neo4jKnowledgeBase("bolt://localhost:7687", "neo4j", "43215678")
    loop = asyncio.get_event_loop()

    result = loop.run_until_complete(kb.get_objects("singer", [], 5))
    print(result)

    result = loop.run_until_complete(
        kb.get_objects("singer", [{"name": "name", "value": "周杰伦"}], 5)
    )
    print(result)

    result = loop.run_until_complete(
        kb.get_objects(
            "song",
            [{"name": "name", "value": "晴天"}, {"name": "album", "value": "叶惠美"}],
            5,
        )
    )
    print(result)

    result = loop.run_until_complete(kb.get_object("singer", "0"))
    print(result)

    result = loop.run_until_complete(kb.get_object("singer", "周杰伦"))
    print(result)

    result = loop.run_until_complete(kb.get_object("song", "晴天"))
    print(result)

    result = loop.run_until_complete(kb.get_attributes_of_object("singer"))
    print(result)

    result = loop.run_until_complete(kb.get_attributes_of_object("song"))
    print(result)

    loop.close()


import sys

sys.exit()

#1
import random
d = int(input("Enter the no. of dice to roll: "))
sum = 0

for x in range(d):
    dice = random.randint(1,6)
    print(dice)
    sum = sum + dice

print(f'The sum of the numbers on the dice is {sum}.')

#2
print("Enter an empty string if you want to quit.")
x = 1
num_l = []

while x > 0:
    num = input(int("Enter a number: "))

    if num == "":
        break
    else:
        num_l.append(int(num))

num_l.sort(reverse=True)
print(num_l)
print(f"The five greatest numbers are {num_l[0:5]}.")

#3
num = int(input("Enter a number: "))

if num >= 1:
    for i in range(2, num):
        if (num % i) == 0:
            print(f'{num} is not a prime number.')
            break
    else:
        print(f'{num} is a prime number.')

else:
    print(f'{num} is neither a prime or a composite number.')

#4
city = []

for c in range(5):
    n_city = input("Enter the name of a city: ")
    city.append(n_city)
#print(city)

for c in city:
    print(c)


# !/usr/bin/python
# -*- coding: utf-8 -*-
"""
@IDE--Env : PyCharm--
@Project  :  ChatGPT_api
@File     :  server.py
@Time     :  2023/2/10 17:26
@Author   :  gpc
@Contact  :  phone@163.com
@License  :  (C)Copyright 2021
@Desc     :  None
"""
import requests
from flask import Flask
from flask import request
from flask_cors import *
import traceback
import json
import logging
import time

app = Flask(__name__)
CORS(app, supports_credentials=True)
logging.basicConfig(filename='log.txt', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
conf = json.load(open("config.json"))

def chatgpt35_api(request_data):
    """offical api"""
    # data = {
    #     "messages": [{"role": "user", "content": prompt}],
    #     "max_tokens": conf["max_tokens"],
    #     "model": conf["model"],
    #     "top_p": conf["top_p"],
    #     "temperature": 0.5,
    #     "n": num
    # }
    headers = {
        "content-type": "application/json",
        'Authorization': "Bearer " + conf["key"]
    }
    response = requests.post(url=conf["url"], headers=headers, json=request_data)
    answer = [elem["message"] for elem in response.json()['choices']]
    return answer

@app.route('/api/chatgpt/v2', methods=['GET', 'POST'])
def extract_info():
    code = 200
    log_text = ""
    answer = ""
    elapse = ""
    try:
        request_data = json.loads(request.get_data(as_text=True))
        log_text = "access message as below\naccess parameter: \n"
        log_text += "\tqps_data: {qps_data}".format(qps_data=request_data)
        st = time.time()
        answer = chatgpt35_api(request_data)  # 20220419
        elapse = f"{round(time.time() - st, 3)} s"
        msg = "成功"
    except Exception as e:
        log_text += "exception args: {args}".format(args=e.args)  # 异常信息
        log_text += traceback.format_exc() + "\n"  # 异常详细信息
        code = 500
        msg = "服务器内部错误！"
    data = {"answer": answer, "elapse": elapse}
    response = json.dumps({"code": code, "msg": msg, "data": data}, ensure_ascii=False)
    log_text += "\nreturned data: \n\t{data}".format(data=data)  # 接口返回数据
    logging.info(log_text)
    return response

if __name__ == '__main__':
    app.config['JSON_AS_ASCII'] = False
    from time import strftime, localtime
    with open("log.txt", "a+", encoding="utf8") as f:
        f.write("\nService on port 5200 started ^_-\n" + strftime("%Y-%m-%d %H:%M:%S", localtime()) + "\n")
    app.run(host='0.0.0.0', port=5200, processes=True)  # threaded=True


N=int(input())
count=0
nums=[0]*(N+1)
for n in range(2, N+1):
    if nums[n]==0:
        count+=1
        for j in range(n, N+1, n):
            nums[j]=1

print(count)

Algorithm/인프런_파이썬알고리즘풀이/섹션2/소수에라토스테네스체.py

소수에라토스테네스체.py


"""Build models.

Author: Mengye Ren (mren@cs.toronto.edu)
"""
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import tensorflow as tf

from fewshot.models.model_factory import get_model
from fewshot.models.model_factory import get_module

def build_backbone(config):
  """Builds a backbone network."""
  if config.backbone_class in [
      'c4_backbone', 'c4_double_backbone', 'c4_gn_backbone',
      'c4_plus_fc_backbone'
  ]:
    bb_config = config.c4_config
  elif config.backbone_class in [
      'resnet_backbone', 'resnet_gn_backbone', 'resnet_snail_backbone',
      'resnet_12_backbone'
  ]:
    bb_config = config.resnet_config
  else:
    raise ValueError('Unknown backbone class {}'.format(config.backbone_class))
  bb = get_module(config.backbone_class, bb_config)
  if config.roi_pooling_backbone:
    bb = get_module('roi_pooling_backbone', bb_config, bb)
  return bb

def build_pretrain_net(config, backbone=None):
  """Builds a regular classification network for pretraining."""
  if backbone is None:
    backbone = build_backbone(config)
  model = get_model("pretrain_net", config, backbone)
  return model

def build_memory_module(config, backbone):
  """Builds a memory module."""

  # --------------------------------------------------------
  # Figure out model dimensions.
  D = backbone.get_output_dimension()[0]
  K = config.num_classes + 1
  if config.model_class in ['lstm_net', 'lstm_sigmoid_net']:
    if config.add_stage_id:
      inp_dim = D + 2 * K
    else:
      inp_dim = D + K
  elif config.model_class in [
      'online_proto_net', 'online_proto_sigmoid_net', 'proto_mem_net',
      'proto_mem_sigmoid_net', 'oml_sigmoid_net', 'oml_trunc_sigmoid_net',
      'online_example_net', 'mixture_proto_net', 'online_classifier_net'
  ]:
    inp_dim = D
  elif config.model_class in ['cpm_net', 'proto_plus_rnn_net']:
    inp_dim = D
  else:
    raise ValueError('Unknown model class {}'.format(config.model_class))

  # --------------------------------------------------------
  # Build different memory modules.
  config.memory_net_config.use_ssl_beta_gamma_write = config.hybrid_config.use_ssl_beta_gamma_write  # NOQA
  config.memory_net_config.fix_unknown = config.fix_unknown
  config.memory_net_config.unknown_id = config.num_classes
  config.hybrid_config.readout_type = config.mann_config.readout_type

  if config.memory_class in [
      'min_dist_proto_memory', 'ssl_min_dist_proto_memory',
      'ssl_min_dist_forget_proto_memory'
  ]:
    name = 'proto_memory'
    memory = get_module(
        config.memory_class,
        name,
        inp_dim,
        config.memory_net_config,
        dtype=tf.float32)

  elif config.memory_class in [
      'online_matchingnet_memory', 'online_imp_memory',
      'online_mixture_memory', 'matchingnet_memory', 'online_ocsvm',
      'online_ovrsvm', 'online_lr'
  ]:
    name = 'imp'
    memory = get_module(
        config.memory_class,
        name,
        inp_dim,
        max_items=config.memory_net_config.max_items,
        max_classes=config.memory_net_config.max_classes,
        unknown_id=config.num_classes,
        log_sigma_init=config.memory_net_config.log_sigma_init,
        log_lambda_init=config.memory_net_config.log_lambda_init,
        radius_init=config.memory_net_config.radius_init,
        similarity=config.memory_net_config.similarity,
        dtype=tf.float32)
  elif config.memory_class in ['lstm']:
    name = 'lstm'
    memory = get_module(
        config.memory_class,
        name,
        inp_dim,
        config.lstm_config.hidden_dim,
        layernorm=config.lstm_config.layernorm,
        dtype=tf.float32)
  elif config.memory_class in ['stack_lstm']:
    name = config.memory_class
    memory = get_module(
        config.memory_class,
        name,
        inp_dim,
        config.lstm_config.hidden_dim,
        config.lstm_config.nstack,
        layernorm=config.lstm_config.layernorm,
        dtype=tf.float32)
  elif config.memory_class in ['dnc']:
    name = config.memory_class
    memory = get_module(
        config.memory_class,
        name,
        inp_dim,
        config.mann_config,
        dtype=tf.float32)
  elif config.memory_class in [
      'dnc_writehead_v2',
      'dnc_writeheadfeed2',
  ]:
    label_dim = K
    name = config.memory_class
    memory = get_module(
        config.memory_class,
        name,
        inp_dim,
        label_dim,
        config.mann_config,
        dtype=tf.float32)
  elif config.memory_class in ['proto_plus_rnn_ssl_v4', 'cpm_ssl']:
    memory_class = config.sub_memory_class
    memory_class2 = config.sub_memory_class2
    if memory_class.startswith('dnc'):
      rnn_memory = get_module(
          memory_class, 'dnc', inp_dim, config.mann_config, dtype=tf.float32)
    elif memory_class in ['stack_lstm']:
      rnn_memory = get_module(
          memory_class,
          "stack_lstm",
          inp_dim,
          config.lstm_config.hidden_dim,
          config.lstm_config.nstack,
          layernorm=config.lstm_config.layernorm,
          dtype=tf.float32)
    elif memory_class in ['lstm']:
      rnn_memory = get_module(
          memory_class,
          "lstm",
          inp_dim,
          config.lstm_config.hidden_dim,
          layernorm=config.lstm_config.layernorm,
          dtype=tf.float32)
    proto_memory = get_module(
        memory_class2,
        'proto_memory',
        inp_dim,
        config.memory_net_config,
        dtype=tf.float32)
    proto_plus_rnn = get_module(
        config.memory_class,
        'proto_plus_rnn',
        proto_memory,
        rnn_memory,
        config.hybrid_config,
        dtype=tf.float32)
    return proto_plus_rnn

  elif config.memory_class in ['oml']:
    oml = get_module(
        config.memory_class, 'oml', config.oml_config, dtype=tf.float32)
    return oml

  else:
    raise ValueError('Unknown memory class {}'.format(config.memory_class))
  return memory

def build_fewshot_net(config, backbone=None):
  """Builds a prototypical network for few-shot evaluation."""
  if backbone is None:
    backbone = build_backbone(config)
  assert config.model_class in [
      'proto_net', 'temp_proto_net', 'mask_proto_net', 'proto_net_var',
      'classifier_net', 'classifier_l1_net', 'classifier_spatiall1_net',
      'classifier_mlp_net', 'classifier_mask_dist_net',
      'classifier_mask_dist_net_v2', 'matching_net', 'maml_net', 'maml_l1_net',
      'imaml_net', 'tadam_net', 'proto_prod_net', 'mask_proto_net_solver',
      'maml_last_net', 'tafe_net'
  ]
  fewshot_net = get_model(config.model_class, config, backbone)
  return fewshot_net

def build_net(config, backbone=None, memory=None, distributed=False):
  """Build a memory based lifelong learning model.

  Args:
    config: Model config.
    backbone: Backbone network.
    memory: Memory network.
  """
  if backbone is None:
    backbone = build_backbone(config)
  if memory is None:
    memory = build_memory_module(config, backbone)
  model = get_model(
      config.model_class, config, backbone, memory, distributed=distributed)
  return model


import sys

#Zad1
def foo1():
    zdanie = input("Podaj zdanie: ")
    spacje = zdanie.count(" ")
    print(f'W tym zdaniu występuje {spacje} spacji.')
    # return spacje

# foo1()

#Zad2
def foo2():
    print("Podaj pierwszą wartość: ")
    x = int(sys.stdin.readline())
    print("Podaj drugą wartość: ")
    y = int(sys.stdin.readline())
    sys.stdout.write(str(x*y))
    # return x*y

# foo2()

#Zad3
def foo3():
    a = int(input("Podaj a: "))
    b = int(input("Podaj b: "))
    c = int(input("Podaj c: "))

    if (a > 0) and (a <= 10):
        print("a jest w przedziale (0, 10>")
        # return True
    elif (a > b) or (b > c):
        print("a>b lub b>c")
        # return True
    else:
        print("Warunek zadania nie jest spełniony")
        # return False

# foo3()

#Zad4
# for x in range(5, 51, 5): print(x)
for x in [x for x in range(0,51) if x%5==0 and x!=0]: print(x)

#Zad5
def foo5():
    liczby = input("Podaj liczby po spacj: ")
    liczby = map(lambda x: int(x)**2, liczby.split())
    print(list(liczby))
    # return(list(liczby))

# foo5()

#Zad6
def foo6():
    lista = []
    stop = False
    while not stop:
        _in = input("Podaj liczbę (lub 'stop' jeśli chcesz zakończyć dodawanie liczb do listy): ")
        if _in == 'stop':
            stop = not stop
        else:
            lista.append(int(_in))
    print(lista)
    # return(lista)

# foo6()

#Zad7 - nie wiem po co tu while xD ale niech będzie
def foo7():
    val = input("Podaj liczbę składającą się z wielu cyfr: ")
    lista = list(val)
    wynik = 0
    while len(lista) > 0:
        wynik += int(lista.pop(0))
    print(wynik)
    # return(wynik)

# foo7()

#Zad8
def foo8():
    val = int(input("Podaj wysokość wieży (nie większa niż 10): "))
    if val < 10:
        for x in range(1,val+1):
            print("A"*x)
    else:
        print("Mordzia zbastuj bajerke, miało być nie większa niż 10")

# foo8()

#Zad9
def foo9():
    for x in range(1, 101):
        for y in range(1, 101):
            multiply = x*y
            print(f'{x} * {y} = {multiply}')

# foo9()

#Zad10
def foo10():
    h = int(input("Podaj wysokość: "))
    if h >= 3 and h <= 9:
        if h%2==0:
            print("Nie chce mi się wyjątków obsługiwać, więc będzie o wiersz większa haha :P")
        for x in range(h//2 + 1):
            print(" " * (h - x), "o" * (2*x + 1))
        for x in range(h//2-1, -1, -1):
            print(" " * (h - x), "o" * (2*x + 1))
    else:
        print("No coś średnio ta liczba pasuje w przedział <3, 9> mordzia.")
foo10()


import os
import dedupe
import pandas as pd

import src.data as data
from src.utils import parse_args

if __name__ == '__main__':
    # ## SETTING UP
    DIR_INPUT = 'input'
    DIR_OUTPUT = 'output'
    FILE_CANON = os.path.join(DIR_INPUT, 'cats.yaml')
    FILE_TEST = os.path.join(DIR_INPUT, 'test.yaml')

    # parse args
    args = parse_args(FILE_CANON)
    category = args['Category']

    # setup dependent paths
    DIR_OUTPUT_CATEGORY = os.path.join(DIR_OUTPUT, category)
    FILE_SETTINGS = os.path.join(DIR_OUTPUT_CATEGORY, 'example_learned_settings')

    # ## LOAD DATA
    # test
    test = data.get_test(FILE_TEST, category)

    # canonical
    canonical = data.get_canonical(FILE_CANON, category)

    # ## LOAD GAZETTEER
    with open(FILE_SETTINGS, 'rb') as f:
        gazetteer = dedupe.StaticGazetteer(f)
    gazetteer.index(canonical)

    # ## EVALUATE
    for entity, samples in test.items():
        print(f'entity: {entity}')

        results = gazetteer.search(samples, n_matches=3, generator=False)

        for smaple_id, ranking in results:
            print(f"\ttest sample: {samples[smaple_id]['title']}")

            for entity_id, score in ranking:
                print(f"\t\tcandidate: {canonical[entity_id]['title']}, confidence: {100*score:.2f}")


#!/usr/bin/env python3
import os
import sys
import traceback

from bs4 import BeautifulSoup as bs
import requests
import logging
from os import path
from pathlib import Path
import pickle
from pprint import pprint
DEBUG = True

# ATTENTION: Change below to set different directory to save files into.
SAVE_DIRECTORY = path.join(
    str(Path.home()), 'Downloads/Podcasts/podcast_downloader')
SAVED_PODCASTS_FILE_NAME = 'saved_podcasts.txt'
SAVED_PODCASTS_FILE_PATH = os.path.join(
    SAVE_DIRECTORY, SAVED_PODCASTS_FILE_NAME)
EXCLUDE_LIST_PATH = os.path.join(
    SAVE_DIRECTORY, 'exclude_list.pkl')
# ATTENTION: Change below to set different minimum disk space.
MIN_SPACE_ALLOWED_MB = 5*1024
logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s',
    filename= path.join(SAVE_DIRECTORY,'podcast.log'), level=logging.INFO,
     datefmt='%Y-%m-%d %H:%M:%S')

def convert_date_to_YYYYMMDD_date(date_string):
    """Converts date

    Args:
        date_string (str): Date in formate like <Sun, 6 Mar 2022 19:08:44 PST> commonly found in RSS feeds.

    Returns:
        str: Date in YYYYMMDD format
    """
    date_parts = date_string.split()

    if len(date_parts[1]) == 1:
        date_parts[1] = '0' + date_parts[1]

    new_date_string = date_parts[3] + '-' + \
        get_month_num(date_parts[2]) + '-' + date_parts[1]
    return new_date_string

def get_month_num(month: str) -> str:
    month = str.lower(month)
    if (month[0:2] == 'ja'):
        return '01'
    elif (month[0] == 'f'):
        return '02'
    elif (month[0:3] == 'mar'):
        return '03'
    elif (month[0] == 'a'):
        return '04'
    elif (month[0:3] == 'may'):
        return '05'
    elif (month[0:3] == 'jun'):
        return '06'
    elif (month[0:3] == 'jul'):
        return '07'
    elif (month[0:3] == 'aug'):
        return '08'
    elif (month[0:3] == 'sep'):
        return '09'
    elif (month[0:3] == 'oct'):
        return '10'
    elif (month[0:3] == 'nov'):
        return '11'
    elif (month[0:3] == 'dec'):
        return '12'
    else:
        raise ValueError('failed to convert date')

def get_renamed_podcast_titles_and_audio_urls(rss_url):
    """Collects the podcast title and download link from the rss_url provided. The files will be renamed with the date in YYYYMMDD format coming before the original title name.

    Args:
        rss_url (string): _description_

    Returns:
        [][]: a nested list, where each entry is is in the from of [<newly named podcast title>, <url where podcast is found>]
    """

    source = requests.get(rss_url, timeout=3).text
    soup = bs(source, 'lxml')
    items = soup('item')
    podcast_details = []
    for item in items:
        # item details
        title = item.title.text
        pub_date = item.pubdate.text
        audioUrl = item.enclosure['url']

        # renaming item and adding to list
        convertedDate = convert_date_to_YYYYMMDD_date(pub_date)
        podcast_details.append(
            (convertedDate + '_' + title + '.mp3', audioUrl))
    return podcast_details

def ensure_exclude_list_exists():
    """ A list of file to ignore from the RSS  feed is stored in a dictionary saved as a pickle file. This function ensures tht file exists by creating an empty dictionary if none is present
    """

    if not path.isfile(EXCLUDE_LIST_PATH):
        empty_dict = {}
        with open(EXCLUDE_LIST_PATH, "wb") as exclude_file:
            pickle.dump(empty_dict, exclude_file)
            logging.info('No exclude list was present, so one was created')

def get_podcasts_to_exclude_by_exclusion_date(podcast_details, exclude_before_date):
    exclude_list = []
    for pcTitle, _ in podcast_details:
        if pcTitle[:10] < exclude_before_date:
            exclude_list.append(pcTitle)

    return exclude_list

def get_filtered_podcast_download_list(podcast_name, podcast_details, exclude_before_date):
    """Returns list of podcast detail for podcasts that are in the in the saved exclude list or before the exclude by date if it is the first time checking for that podcast.

    """
    exclude_dict = None
    with (open(EXCLUDE_LIST_PATH, "rb")) as exclude_file:
        exclude_dict = pickle.load(exclude_file)

        if not podcast_name in exclude_dict:
            podcasts_to_exclude = get_podcasts_to_exclude_by_exclusion_date(
                podcast_details, exclude_before_date)
            exclude_dict[podcast_name] = podcasts_to_exclude

        filtered_podcast_details = list(
            filter(lambda pc: pc[0] not in exclude_dict[podcast_name], podcast_details))

    with (open(EXCLUDE_LIST_PATH, "wb")) as exclude_file:
        pickle.dump(exclude_dict, exclude_file, -1)
        logging.info(f'Files from RSS feed that will NOT be downloaded for <{podcast_name}> are: <{exclude_dict[podcast_name]}>')
        logging.info(f'Files from RSS feed that WILL be downloaded for <{podcast_name}> are: <{filtered_podcast_details}>')
        return filtered_podcast_details

def download_file(url, filename, download_chunk_size=10 * 1024 * 1024):
    with requests.get(url, stream=True) as req:
        logging.info(f'Attempting to download <{filename}>')
        req.raise_for_status()
        file_size_MB = int(req.headers['Content-Length'])/1024**2
        logging.info(f'Size of file to be downloaded {file_size_MB}')
        statvfs_diskspace_tool = os.statvfs('/')
        disc_space_left_MB = (
            statvfs_diskspace_tool.f_bavail * statvfs_diskspace_tool.f_frsize) / 1024**2

        hard_disk_space_after_download_MB = disc_space_left_MB-file_size_MB

        logging.info(
            f'Disk space after download will be completed: {hard_disk_space_after_download_MB} MB.\tMin space allowed on file system: {MIN_SPACE_ALLOWED_MB} MB.')
        if hard_disk_space_after_download_MB < MIN_SPACE_ALLOWED_MB:
            raise OSError(
                f'Program only downloads when there is more than {MIN_SPACE_ALLOWED_MB/1024} GB of hd space remaining. Remaining space is <{hard_disk_space_after_download_MB}>')
        with open(filename,
                  'wb',
                  ) as new_audio_file:
            for chunk in req.iter_content(chunk_size=download_chunk_size):
                new_audio_file.write(chunk)
        logging.info(f'Download of <{filename}> is complete')

def download_podcasts_from_rss_url(podcast_name, podcast_rss_url, exclude_before_date):
    # TODO autoformat subDirectory to dir that is legal and without spaces
    logging.info(f'Looking up information for <{podcast_name}> from url<{podcast_rss_url}>')
    # creating directories for podcast in system
    sub_directory = path.join(SAVE_DIRECTORY, podcast_name)
    if not os.path.exists(sub_directory):
        logging.info(f'created new directory at <{sub_directory}>')
        os.makedirs(sub_directory)
    os.chdir(sub_directory)

    podcast_details = get_renamed_podcast_titles_and_audio_urls(
        podcast_rss_url)
    DEBUG and pprint(podcast_details)
    podcasts_to_download = get_filtered_podcast_download_list(
        podcast_name, podcast_details, exclude_before_date)

    for podcast_title, url in podcasts_to_download:
        download_file(url, podcast_title)
        add_podcast_title_to_exclude_file(podcast_name, podcast_title)

def download_file_or_raise_error(file):
    try:
        download_file(file)
    except OSError as e:
        msg = f'Error: Could not complete download.\n{e,traceback.format_exc()}'
        logging.error(msg)
    except Exception:
        raise Exception(f'Error: Could not complete download.\n{traceback.format_exc()}'
                        )

def add_podcast_title_to_exclude_file(podcast_name, podcast_title):
    with (open(EXCLUDE_LIST_PATH, "rb")) as exclude_file:
        exclude_dict = pickle.load(exclude_file)
        exclude_dict[podcast_name].append(podcast_title)
    with (open(EXCLUDE_LIST_PATH, "wb")) as exclude_file:
        pickle.dump(exclude_dict, exclude_file, -1)
        logging.info(f'Exclude file has been updated. <{podcast_name}> has the title of <{podcast_title}> added to the exclusion list')

def get_list_of_podcasts(directory):
    podcasts = []
    podcast_filename = path.join(directory, 'podcastTitles.txt')
    with open(podcast_filename, 'r') as pc_file:
        for line in pc_file.readlines():
            split = line.split('\t')
            podcast = split[0].strip()
            url = split[1].strip()
            podcasts.append((podcast, url))

    return podcasts

def ensure_dir_exists(dir):

    if not path.isdir(dir):
        Path(dir).mkdir(parents=True)

def get_podcast_to_download_details_from_file():
    podcast_details = []
    with open(SAVED_PODCASTS_FILE_PATH, 'r') as f:
        for line in f:
            single_podcast_detail = [x.strip() for x in line.split('\t')]
            podcast_details.append(single_podcast_detail)
    return podcast_details

def run_program():
    logging.info('Starting program...')
    ensure_exclude_list_exists()
    podcast_detail_list = get_podcast_to_download_details_from_file()
    for pc in podcast_detail_list:
        download_podcasts_from_rss_url(*pc)

    DEBUG and print(f'saving podcasts in: {SAVE_DIRECTORY}')

if __name__ == '__main__':

    if len(sys.argv) == 1:
        run_program()
    else:
        arg1 = sys.argv[1].lower()
        if arg1 == 'add' or arg1 == 'a':
            podcast_name = input("Enter name of podcast: ")
            rss_url = input("Enter the rss feed xml url: ")
            print('The program will download all podcasts after the start date specified')
            exclude_before_date = input(
                'Please enter a start date in YYYY-MM-DD format: ')

            # TODO could make a check to see if podcast is already in list
            # TODO add format validators to user input
            with open(SAVED_PODCASTS_FILE_PATH, 'a+') as f:
                f.write(f'{podcast_name}\t{rss_url}\t{exclude_before_date}\n')
        elif arg1 == 'h' or arg1 == 'help' or arg1 == '-h' or arg1 == '--help':
            print('feature not implemented yet')
            # TODO - add instructions here
        elif arg1 == 'd' or arg1 == 'del':
            print('feature not implemented yet')

            # TODO add option to delete from list here
        else:
            print(f'<{arg1}> is not a valid argument')


import setuptools

with open("README.md", "r", encoding="utf-8") as fh:
    long_description = fh.read()

setuptools.setup(
    name="mOps-thezaza101",
    version="0.0.1",
    author="thezaza101",
    author_email="test@test.com",
    description="Mathematical operations using base python",
    long_description="Mathematical operations using base python...",
    long_description_content_type="text/markdown",
    url="https://github.com/verbaros/mOps",
    project_urls={
        "Bug Tracker": "https://github.com/verbaros/mOps/issues",
    },
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: BSD 3-Clause License",
        "Operating System :: OS Independent"
    ],
    package_dir={"": "src"},
    packages=setuptools.find_packages(where="src"),
    python_requires=">=3.6",
)


from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QGridLayout
from PyQt5.QtGui import QPainter, QColor, QPixmap, QPen, QBrush
from random import randrange
from Ui import Ui_MainWindow
import sys

class Main(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(Main, self).__init__()
        self.setupUi(self)

        self.pushButton.clicked.connect(self.draw_circle)
        self.label = QLabel(self)
        self.label.setPixmap(QPixmap(1200, 800))
        self.label.setStyleSheet('background-color: rgb(255, 255, 255);')
        self.lout = QGridLayout(self.centralWidget())
        self.lout.addWidget(self.pushButton, 0, 0)
        self.lout.addWidget(self.label, 1, 0)

    def draw_circle(self):
        x, y = randrange(0, 1000), randrange(0, 800)
        w = randrange(500)
        self.qp = QPainter()
        self.qp.begin(self.label.pixmap())
        self.qp.setPen(QPen(QColor(randrange(256), randrange(256), randrange(256)), 6))
        self.qp.drawEllipse(x, y, w, w)
        self.qp.end()
        self.update()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    m = Main()
    m.show()
    sys.exit(app.exec())


import random

from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.views import APIView
from rest_framework import status
from serializers import *
from datetime import datetime, timedelta
from models import User, Visit
from threat import *

class APIRoot(APIView):
    def get(self, request):
        return Response({
            'IP Details': reverse('threat_details', request=request),
        })

class IPDetailsView(APIView):
    def get(self, request, *args, **kwargs):
        ip = kwargs['ip']

        # User's source address
        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            user_addr = x_forwarded_for.split(',')[0]
        else:
            user_addr = request.META.get('REMOTE_ADDR')

        # Generate IPDetails object for serialization
        details_request = IPDetails(ip, *args, **kwargs)

        # Serialize object
        result = DetailsSerializer(details_request)

        response = Response(result.data, status=status.HTTP_200_OK)

        # Set UTC time
        current_datetime = datetime.utcnow()
        epoch            = int((current_datetime - datetime(1970, 1, 1)).total_seconds())
        expires          = addYear(current_datetime, 1)

        # Handle Cookies
        if 'alienvaultid' in request.COOKIES:
            avid = request.COOKIES['alienvaultid']

            response.set_cookie('last_visit', epoch, expires=expires)

        else:
            # Didn't find cookie, set one.
            avid = ''.join(["%s" % random.randint(0, 9)
                           for num in range(0, 11)])
            response.set_cookie('alienvaultid', avid, expires=expires)
            response.set_cookie('last_visit', epoch, expires=expires)

        user = User(alienvaultid=avid)
        user.save()

        visit = Visit(user=user,
                      address=user_addr,
                      timestamp=epoch,
                      endpoint=kwargs['endpoint'] + ip)
        visit.save()

        return response

class Traffic(APIView):
    def get(self, request, *args, **kwargs):
        users    = User.objects.all()
        data     = []
        idx    = None

        for user in users:
            idx = None
            # Find index of user in data
            for i, j in enumerate(data):
                if user.alienvaultid in j['alienvaultid']:
                    idx = i

            # Found a user in list
            if idx is not None:
                # Add visits to existing user's visits list
                for v in user.visit_set.all():
                    data[idx]['visits'].append(json.loads(str(v)))
            # New user in list
            else:
                tmp_dict = {'alienvaultid': user.alienvaultid}
                tmp_dict['visits'] = []
                for v in user.visit_set.all():
                    tmp_dict['visits'].append(json.loads(str(v)))
                data.append(tmp_dict)

        data = sorted(data, key=lambda k: k['alienvaultid'])

        response = Response(data)

        return response

##############################################################################
def addYear(date, year):
    """
    Takes a datetime object and adds years to it.
    """

    result = date + timedelta(366 * year)

    if year > 0:
        while result.year - date.year > year or date.month < result.month or date.day < result.day:
            result += timedelta(-1)
    elif year < 0:
        while result.year - date.year < year or date.month > result.month or date.day > result.day:
            result += timedelta(1)

    return result


import unittest
import flaskdav
import utils
import tempfile
import shutil

class FlaskdavTestCase(unittest.TestCase):

    @classmethod
    def setUpClass(self):
        '''
           create temporary dir and files, and flaskdav test instance
        '''
        self.app = flaskdav.app

        self.app.fs_path = tempfile.mkdtemp()
        f = open(self.app.fs_path + '/file.txt', 'w')
        f.write('pouet')
        f.close()
        f = open(self.app.fs_path + '/file.js', 'w')
        f.close()
        f = open(self.app.fs_path + '/file.png', 'w')
        f.close()

        self.app.fs_handler = utils.FilesystemHandler(self.app.fs_path, '/webdav/')
        self.server = self.app.test_client()

    '''
        GET method
    '''
    # status code
    def test_get_root_return_code_200(self):
        response = self.server.get('/')
        self.assertEqual(response.status_code, 200)

    def test_get_webdav_root_return_code_200(self):
        response = self.server.get('/webdav/')
        self.assertEqual(response.status_code, 200)

    # MIME type
    def test_plaintext_content_type(self):
        response = self.server.get('/webdav/file.txt')
        self.assertEqual(response.mimetype, 'text/plain')

    def test_plaintext_content_type(self):
        response = self.server.get('/webdav/file.js')
        self.assertEqual(response.mimetype, 'application/x-javascript')

    def test_png_content_type(self):
        response = self.app.test_client().get('/webdav/file.png')
        self.assertEqual(response.mimetype, 'image/png')

    # response body
    def test_response_body(self):
        f = open(self.app.fs_path + '/file.txt', 'w')
        f.write('pouet')
        f.close()
        response = self.server.get('/webdav/file.txt')
        self.assertEqual(response.data, 'pouet')

    '''
        PUT method
    '''
    # status code
    def test_put_root_return_code_405(self):
        response = self.server.put('/')
        self.assertEqual(response.status_code, 405)

    def test_put_webdav_return_code_201(self):
        response = self.server.put('/webdav/put.txt', '')
        self.assertEqual(response.status_code, 201)

    # file content modification
    def test_put_webdav_content_changed(self):
        response = self.server.put('/webdav/put.txt', data='couscous')
        f = open(self.app.fs_path + '/put.txt', 'r')
        content = f.read()
        f.close()
        self.assertEqual(content, 'couscous')

    @classmethod
    def tearDownClass(self):
        '''
            cleaning temporary dir
        '''
        shutil.rmtree(self.app.fs_path)

if __name__ == '__main__':
    unittest.main()


#Q. 금값 계산
def gold(weight):
    import locale
    locale.setlocale(locale.LC_ALL, '')
    PPG = 41733
    price = weight*1000*PPG
    s = locale.format_string("%d", price, 1)
    print(s)

#Q.시간 계산
def time(sec):
    hour = sec / 3600
    sec = sec % 3600
    minu = sec / 60
    sec = sec % 60
    print("%d hr, %d min %d sec" %(hour,minu,sec))

#Q. 온도 변환
def temp(t):
    state = t[-1]
    t2 = t[:-1]
    a = int(t2)
    if state=="C":
        print("%dC -> %dF" %(a, a*(9.0/5.0)+32.0))
    elif state == "F":
        print("%dF -> %dC" %(a, (a-32)*5/9.0))
    else:
        print("Error")

#Q. 성적 처리
def score(s):
    if s>=90:
        grade='A'
    elif s>=80:
        grade='B'
    elif s>=70:
        grade='C'
    else:
        grade='D'
    print(grade)

#Q. 가위바위보
def rsp():
    import random
    hands = ["rock", "scissors", "papaers"]
    a = random.choice(hands)
    b = random.choice(hands)
    if a == b:
        print("%s vs %s, Draw." %(a,b))
    elif a == "rock" and b == "scissors" or a == "scissors" and b == "papers" or a == "papers" and b == "rock":
        print("%s vs %s, A wins." %(a,b))
    else:
        print("%s vs %s, B wins." %(a,b))

#Q. 소수 판정
def prime(n):
    for i in range(2,n):
        if n%i==0:
            print("%d is not a prime number." %n)
            return
    print("%d is a prime number." %n)

def prime2(n):
    import math
    k = int(math.sqrt(n))
    for i in range(2,k):
        if n%i==0:
            print("%d is not a prime number." %n)
            return
    print("%d is a prime number." %n)

#Q. 진수 변환(10->8)
def dec2hex(n):
    hex = ""
    while n>0 :
        k = n%8
        x = ("%d" %k)
        hex = x+hex #문자를 문자열에 하나씩 저장
        n = n//8
    print(hex)

#Q. 삼각형 그리기
#몇 줄인지, 각 줄에 공백과 별의 개수는 몇 개인지?
def triangle(n):
    if n%2==0:
        k = n//2
    else:
        k = n//2 + 1
    for i in range(1,k+1):  #줄 수만큼 반복
        s = n-n//2-i+1  #공백 개수
        t = n-2*(k-i)
        board = ""
        for j in range(1,s):
            board += " "
        for j in range(s,s+t):
            board += "*"
        print(board)

#Q. 버블정렬
def bubbleSort(*args):
    a=[]
    for arg in args:
        a.append(arg)
    n=len(a)
    i = 0
    while i<n-1:
        j=n-2
        while j>= i:
            if a[j]>a[j+1]:
                a[j],a[j+1] = a[j+1],a[j]
            j -= 1
        i += 1
    print(a)

#Q. 소인수분해
def pf(n):
    import math
    k = int(math.sqrt(n))
    primes = []
    i = k
    while i>=1:
        if n%i == 0:
            primes.insert(0,i)  #약수
            if i != n//i:   #제곱이 아니면
                primes.append(n//i)
        i -= 1
    print(primes)


"""Module for simulating data for ``mapmuts`` package.

This module contains function to simulate data that can be analyzed
by the ``mapmuts`` package. This simulated data can be useful for
testing the package's performance.

Use the `Seed` function if you want to ensure repeatable output.

Written by Jesse Bloom.

List of functions
--------------------
`Seed` : seeds random number generators used by module.

`MutateNT` : mutates a nucleotide.

`SimulateIlluminaPE` : simulates uniformly fragmented Illumina paired-end library.

`SimulateCounts` : simulates library counts for inference of enrichment ratio.

`SimulatePreferences` : simulates counts for inference of equilibrium preferences.

Documentation for functions
-----------------------------
Documentation for individual functions is provided in their definitions below.
"""

import random
import mapmuts.sequtils
import mapmuts.bayesian

def Seed(seed):
    """Seeds all random number generators used by this module.

    This function seeds all random number generators used by the
    functions in this module. This creates reproducible output if the
    seeding is done before calling other module functions.

    `seed` : the integer seed used to seed the random number generators.

    Currently the generators that are seeded are:

        * random.seed
    """
    random.seed(seed)

def MutateNT(nt, mutrate):
    """Mutates a nucleotide with probability `mutrate`.

    `nt` : a DNA nucleotide character, either upper or lower case (i.e.
    'A', 'C', 'T', 'G', 'a', 'c', 't', 'g')

    `mutrate` : the probability with which the nucleotide is randomly
    mutated to some other nucleotide, so `0 <= mutrate <= 1`.

    Returns a character giving the original nucleotide (with probability
    `1 - mutrate`) or one of the other three nucleotide (with probability
    `mutrate`). Nucleotides are mutated to any of the other three
    nucleotides with equal probability. If the original nucleotide
    is upper-case, the mutant will be too. If the original
    nucleotide is lower-case, the mutant will be too.
    """
    assert 0 <= mutrate <= 1
    if random.random() < mutrate:
        return random.choice({'A':['T', 'C', 'G'], 'T':['A', 'C', 'G'],
                              'C':['T', 'A', 'G'], 'G':['A', 'T', 'C'],
                              'a':['t', 'c', 'g'], 't':['a', 'c', 'g'],
                              'c':['t', 'a', 'g'], 'g':['a', 't', 'c'],
                              }[nt])
    else:
        return nt

def SimulateIlluminaPE(gene, r1_trim3, r2_trim3, readlength,
                        insertlength, libsize, mutrate):
    """Simulates a uniformly fragmented Illumina paired-end library.

    This function simulates creating an Illumina paired-end read
    library. A target nucleotide sequence (given by `gene`) is randomly
    fragmented into small pieces as specified by `insertlength`. The
    inserts are then randomly reverse complemented with a probability of
    0.5. The adaptor sequence specified by `r1_trim3` is then appended to
    the 3' end of the fragment to generate the read 1 (R1) read. The
    read fragment is reverse complemented and the adaptor sequence
    specified by `r2_trim3` is then appended to the 3' of this fragment to
    generate the corresponding read 2 (R2 read). Each fragment-adaptor
    sequence is then subjected to random mutation as specified by
    `mutrate`. Finally, all fragment-adaptor sequences are truncated to
    the length specified by `readlength`. Overall, this creates data
    similar to that which would be expected from an Illumina PE read.

    If `readlength` exceeds the length of the gene fragment plus the
    adaptor sequence, random nucleotides are appended at the 3' end
    until `readlength` is reached.

    CALLING VARIABLES:

    `gene` : a string of DNA nucleotides giving the sequence of the gene that
    is randomly fragmented.

    `r1_trim3` : a string of DNA nucleotides giving the adaptor sequence that
    is appended to the 3' end of R1 fragments.

    `r2_trim3` : a string of DNA nucleotides giving the adaptor sequence that
    is appended to the 3' end of R2 fragments.

    `readlength` : the integer length of the Illumina reads.

    `insertlength` : specifies the length of the inserts (fragments of
    `gene`) that are included in each read between the adaptors. This
    argument should be a 3-tuple. It is of the form `('uniform',
    minlength, maxlength)` where `minlength` and `maxlength` are
    integers. The inserts lengths are then drawn from uniformly from
    the distribution of lengths >= `minlength` and <= `maxlength`.

    `libsize` : the number of read pairs to create.

    `mutrate` : the probabily that each base in the total read
    (insert and adaptor) is randomly mutated to another nucleotide.
    This is designed to simulate sequencing errors. Each nucleotide
    is randomly mutated to any other nucleotide. The mutations are
    applied separately to each read. So for example, if there are
    two 50 nt reads (`readlength == 50`) and `mutrate = 0.01`, then
    there will be an average of one mutation total between the two
    reads (100 nt times 0.01 mutation rate).

    RETURN VARIABLE:

    The returned variable is a 2-tuples of lists, `(r1_reads,
    r2_reads)`. `r1_reads` and `r2_reads` are lists of the same length (with
    ``len(r1_reads) == len(r2_reads) == libsize``). Element `i` of `r1_reads`
    is a string giving that R1 read, and element `i` of `r2_reads` is the
    corresponding string giving that R2 read. In the returned reads, the
    portion of the read that comes from the target gene is upper-case,
    while the portion that comes from the adaptor is lower-case,
    regardless of the case of the input variables for these sequences.
    """
    gene_rc = mapmuts.sequtils.ReverseComplement([gene])[0]
    n = len(gene)
    r1_trim3 = r1_trim3.lower()
    r2_trim3 = r2_trim3.lower()
    if isinstance(insertlength, tuple) and len(insertlength) == 3 and\
            insertlength[0] == 'uniform':
        (minlength, maxlength) = (insertlength[1], insertlength[2])
        assert 0 <= minlength <= maxlength
    else:
        raise ValueError("Invalid value of insertlength: %r" %
                         insertlength)
    assert 0 <= mutrate <= 1
    r1_reads = []
    r2_reads = []
    for i in range(int(libsize)):
        l = random.randint(minlength, maxlength) # length of insert
        fragstart = random.randint(0, n - l) # index start
        if random.random() < 0.5:
            r1 = gene[fragstart : fragstart + l]
        else:
            r1 = gene_rc[fragstart : fragstart + l]
        r1 = r1.upper()
        r2 = mapmuts.sequtils.ReverseComplement([r1])[0]
        r1 = ("%s%s" % (r1, r1_trim3))[ : readlength]
        r2 = ("%s%s" % (r2, r2_trim3))[ : readlength]
        r1 = ''.join([MutateNT(nt, mutrate) for nt in r1])
        r2 = ''.join([MutateNT(nt, mutrate) for nt in r2])
        r1_reads.append(r1)
        r2_reads.append(r2)
    assert len(r1_reads) == len(r2_reads)
    return (r1_reads, r2_reads)

def SimulatePreferences(pi, mu, epsilon, rho, wtcodon, nlibs, libsize, perturb, seed=1):
    """Simulates library counts to test inference of equilibrium preferences.

    This function simulates data for analysis by
    *mapmuts.bayesian.InferPreferencesMCMC*. The variables and what is
    being simulated may be clearer after reading the documentation for
    that function.

    This function requires `` numpy`` and ``pymc``, so trying to run this
    function if *mapmuts.bayesian.PymcAvailable() == False* will raise
    an exception.

    CALLING VARIABLES:

    * *pi* : a dictionary keyed by all 21 amino acids in
      *mapmuts.sequtils.AminoAcids(includestop=True)*, with the
      values giving the preferences for each amino acid (must sum to one).

    * *mu* : the mutagenesis rate in the *mutDNA* library.

    * *epsilon* : a dictionary keyed by 1, 2, and 3 with *epsilon[i]*
      giving the sequencing error rate to codons with *i* differences from *wtcodon*.

    * *rho* : a dictionary keyed by 1, 2, and 3 with *rho[i]* giving
      the reverse-transcription rate to codons with *i* differences from *wtcodon*.

    * *wtcodon* : string giving the wild-type codon, such as *GCA*.

    * *nlibs* : the number of libraries being analyzed.

    * *libsize* : the number of sequences in each library.

    * *perturb* : a number specifying how much we perturb the values of *libsize*
      for each sample, and how much we perturb *epsilon*, *rho*, and *mu* for
      each mutant codon, away from the set values. For each simulation of one of
      these, we set the simulation value to the calling value plus a number
      drawn uniformly from the calling value divided *perturb* to the calling
      value plus *perturb*. So *perturb* must be >= 1, and a reasonable value
      might be 2.0. This tests if the inference still works even if the priors
      aren't quite accurate.

    * *seed* random number seed, 1 by default. Used to seed ``numpy`` seed,
      which is not seeded by *mapmuts.simulate.Seed*.

    OPERATION AND RESULT:

    This function simulates the indicated number and sizes of libraries using
    the specified calling values, but perturbing the appropriate ones as
    specified by *perturb*. The returned variable is *library_stats* which
    has all of the values that you would need to pass this variable
    to *mapmuts.bayesian.InferPreferencesMCMC* for inference. The priors
    for *mu*, *epsilon*, and *rho* are set to the calling values.

    The idea is that you can then apply the inference function to the results
    and test how well you actually infer the true enrichment ratio.
    """
    if not mapmuts.bayesian.PymcAvailable():
        raise ImportError("Cannot import numpy")
    import numpy
    numpy.random.seed(seed)
    codons = mapmuts.sequtils.Codons()
    assert numpy.allclose(1.0, sum(pi.values())), "pi does not sum to one"
    c_pi = []
    for codon in codons:
        aa = mapmuts.sequtils.Translate([('head', codon)])[0][1]
        if not aa:
            aa = '*'
        c_pi.append(pi[aa])
    c_pi = numpy.array(c_pi)
    perturb = float(perturb)
    assert perturb >= 1, "perturb must be >= 1"
    assert wtcodon in codons, "Not a valid wtcodon of %s" % wtcodon
    iwtcodon = codons.index(wtcodon)
    ndiffs = [len([i for i in range(len(codon)) if codon[i] != wtcodon[i]]) for codon in codons]
    rho[0] = 0.0
    epsilon[0] = 0.0
    library_stats = []
    for ilib in range(nlibs):
        libstats = {'mu_prior':mu, 'epsilon_prior':epsilon, 'rho_prior':rho, 'wtcodon':wtcodon}
        muvec = numpy.array([random.uniform(mu / perturb, mu * perturb) for codon in codons])
        muvec[iwtcodon] = 0
        muvec[iwtcodon] = 1.0 - numpy.sum(muvec)
        assert muvec[iwtcodon] > 0, "mu entry for wildtype not > 0, mu too large?"
        rhovec = numpy.array([random.uniform(rho[ndiff] / perturb, rho[ndiff] * perturb) for ndiff in ndiffs])
        rhovec[iwtcodon] = 1.0 - numpy.sum(rhovec)
        assert rhovec[iwtcodon] > 0, "rho entry for wildtype not > 0, rho too large?"
        epsilonvec = numpy.array([random.uniform(epsilon[ndiff] / perturb, epsilon[ndiff] * perturb) for ndiff in ndiffs])
        epsilonvec[iwtcodon] = 1.0 - numpy.sum(epsilonvec)
        assert epsilonvec[iwtcodon] > 0, "epsilon entry for wildtype not > 0, epsilon too large?"
        delta = numpy.zeros(len(codons))
        delta[iwtcodon] = 1.0
        libstats['nrdna_counts'] = dict(zip(codons, numpy.random.multinomial(random.uniform(libsize / perturb, libsize * perturb), epsilonvec)))
        libstats['nrrna_counts'] = dict(zip(codons, numpy.random.multinomial(random.uniform(libsize / perturb, libsize * perturb), epsilonvec + rhovec - delta)))
        libstats['nrmutdna_counts'] = dict(zip(codons, numpy.random.multinomial(random.uniform(libsize / perturb, libsize * perturb), epsilonvec + muvec - delta)))
        libstats['nrmutvirus_counts'] = dict(zip(codons, numpy.random.multinomial(random.uniform(libsize / perturb, libsize * perturb), epsilonvec + rhovec + c_pi * muvec / numpy.dot(c_pi, muvec) - 2.0 * delta)))
        library_stats.append(libstats)
    return library_stats

def SimulateCounts(phi, mu, epsilon, rho, wtcodon, codons, libsize, nlibs, perturb):
    """Simulates library counts to test inference of enrichment ratio.

    This function is designed to simulate data for analysis by
    *mapmuts.bayesian.InferEnrichmentMCMC*. The variables and what is
    being simulated may be clearer after reading the documentation for that
    function.

    This function requires ``numpy``, which is checked for by
    *mapmuts.bayesian.PymcAvailable()*. Trying to run this function
    if that returns *False* will raise an exception, which is fine --
    because this function is designed to test the Bayesian inference,
    you wouldn't want to run it without being able to do the inference anyway!

    CALLING VARIABLES:

    * *phi* : the true enrichment ratio.

    * *mu* : the mutagenesis rate in the *mutDNA* library.

    * *epsilon* : a dictionary keyed by 1, 2, and 3 with *epsilon[i]*
      giving the sequencing error rate to codons with *i* differences from *wtcodon*.

    * *rho* : a dictionary keyed by 1, 2, and 3 with *rho[i]* giving
      the reverse-transcription rate to codons with *i* differences from *wtcodon*.

    * *wtcodon* : string giving the wild-type codon, such as *GCA*.

    * *codons* : a list of one or more mutant codons, such as
      *[GGC, GGG, GGA, GGT]*

    * *libsize* : the number of sequences in each library.

    * *nlibs* : the number of libraries being analyzed.

    * *perturb* : a number specifying how much we perturb the values of *libsize*
      for each sample, and how much we perturb *epsilon*, *rho*, and *mu* for
      each mutant codon, away from the set values. For each simulation of one of
      these, we set the simulation value to the calling value plus a number
      drawn uniformly from the calling value divided *perturb* to the calling
      value plus *perturb*. So *perturb* must be >= 1, and a reasonable value
      might be 2.0. This tests if the inference still works even if the priors
      aren't quite accurate.

    OPERATION AND RESULT:

    This function simulates the indicated number and sizes of libraries using
    the specified calling values, but perturbing the appropriate ones as
    specified by *perturb*. The returned variable is *library_stats* which
    has all of the values that you would need to pass this variable
    to *mapmuts.bayesian.InferEnrichmentMCMC* for inference. The priors
    for *mu*, *epsilon*, and *rho* are set to the calling values.

    The idea is that you can then apply the inference function to the results
    and test how well you actually infer the true enrichment ratio.
    """
    if not mapmuts.bayesian.PymcAvailable():
        raise ImportError("Cannot import numpy")
    import numpy
    perturb = float(perturb)
    assert perturb >= 1, "perturb must be >= 1"
    libtypes = ['dna', 'rna', 'mutdna', 'mutvirus']
    library_stats = []
    for ilib in range(nlibs):
        libstats = {'mu_prior':mu, 'epsilon_prior':epsilon, 'rho_prior':rho}
        for libtype in libtypes:
            libstats["Nr%s" % libtype] = random.uniform(libsize / perturb, libsize * perturb)
            libstats['nr%s_list' % libtype] = []
        for codon in codons:
            ndiffs = len([i for i in range(len(codon)) if codon[i] != wtcodon[i]])
            assert 1 <= ndiffs <= 3
            imu = random.uniform(mu / perturb, mu * perturb)
            iepsilon = random.uniform(epsilon[ndiffs] / perturb, epsilon[ndiffs] * perturb)
            irho = random.uniform(rho[ndiffs] / perturb, rho[ndiffs] * perturb)
            for libtype in libtypes:
                ilibsize = libstats['Nr%s' % libtype]
                if libtype == 'dna':
                    nexpected = iepsilon * ilibsize
                elif libtype == 'rna':
                    nexpected = (iepsilon + irho) * ilibsize
                elif libtype == 'mutdna':
                    nexpected = (iepsilon + imu) * ilibsize
                elif libtype == 'mutvirus':
                    nexpected = (iepsilon + irho + phi * imu) * ilibsize
                else:
                    raise ValueError("Invalid libtype")
                n = numpy.random.poisson(nexpected)
                libstats['nr%s_list' % libtype].append(n)
        library_stats.append(libstats)
    return library_stats

if __name__ == '__main__':
    import doctest
    doctest.testmod()


from __future__ import annotations

import asyncio
import contextvars
import importlib.resources
import math
import time
import uuid
import warnings
from types import TracebackType
from typing import cast

from coredis.client import Redis, RedisCluster
from coredis.commands import Script
from coredis.exceptions import LockError, ReplicationError
from coredis.tokens import PureToken
from coredis.typing import AnyStr, Generic, KeyT, Optional, StringT, Type, Union

with warnings.catch_warnings():
    warnings.simplefilter("ignore", DeprecationWarning)
    EXTEND_SCRIPT = Script(
        script=importlib.resources.read_text(__package__, "extend.lua")
    )
    RELEASE_SCRIPT = Script(
        script=importlib.resources.read_text(__package__, "release.lua")
    )

class LuaLock(Generic[AnyStr]):
    """
    A shared, distributed Lock using LUA scripts.

    The lock can be used with both :class:`coredis.Redis`
    and :class:`coredis.RedisCluster` either explicitly or as an async context
    manager::

        import asyncio
        import coredis
        from coredis.exceptions import LockError
        from coredis.recipes.locks import LuaLock

        async def test():
            client = coredis.Redis()
            async with LuaLock(client, "mylock", timeout=1.0):
                # do stuff
                await asyncio.sleep(0.5)
                # lock is implictly released when the context manager exits
            try:
                async with LuaLock(client, "mylock", timeout=1.0):
                    # do stuff that takes too long
                    await asyncio.sleep(1)
                    # lock will raise upon exiting the context manager
            except LockError as err:
                # roll back stuff
                print(f"Expected error: {err}")
            lock = LuaLock(client, "mylock", timeout=1.0)
            await lock.acquire()
            # do stuff
            await asyncio.sleep(0.5)
            # do more stuff
            await lock.extend(1.0)
            await lock.release()

        asyncio.run(test())
    """

    @classmethod
    @RELEASE_SCRIPT.wraps(client_arg="client")
    async def lua_release(  # type: ignore[empty-body]
        cls,
        client: Union[Redis[AnyStr], RedisCluster[AnyStr]],
        name: KeyT,
        expected_token: StringT,
    ) -> int:
        ...

    @classmethod
    @EXTEND_SCRIPT.wraps(client_arg="client")
    async def lua_extend(  # type: ignore[empty-body]
        cls,
        client: Union[Redis[AnyStr], RedisCluster[AnyStr]],
        name: KeyT,
        expected_token: StringT,
        additional_time: int,
    ) -> int:
        ...

    local: contextvars.ContextVar[Optional[StringT]]

    def __init__(
        self,
        client: Union[Redis[AnyStr], RedisCluster[AnyStr]],
        name: StringT,
        timeout: Optional[float] = None,
        sleep: float = 0.1,
        blocking: bool = True,
        blocking_timeout: Optional[float] = None,
    ):
        """
        :param timeout: indicates a maximum life for the lock.
         By default, it will remain locked until :meth:`release` is called.
         ``timeout`` can be specified as a float or integer, both representing
         the number of seconds to wait.

        :param sleep: indicates the amount of time to sleep per loop iteration
         when the lock is in blocking mode and another client is currently
         holding the lock.

        :param blocking: indicates whether calling :meth:`acquire` should block until
         the lock has been acquired or to fail immediately, causing :meth:`acquire`
         to return ``False`` and the lock not being acquired. Defaults to ``True``.

        :param blocking_timeout: indicates the maximum amount of time in seconds to
         spend trying to acquire the lock. A value of ``None`` indicates
         continue trying forever. ``blocking_timeout`` can be specified as a
         :class:`float` or :class:`int`, both representing the number of seconds to wait.
        """
        self.client: Union[Redis[AnyStr], RedisCluster[AnyStr]] = client
        self.name = name
        self.timeout = timeout
        self.sleep = sleep
        self.blocking = blocking
        self.blocking_timeout = blocking_timeout
        self.local = contextvars.ContextVar[Optional[StringT]]("token", default=None)
        if self.timeout and self.sleep > self.timeout:
            raise LockError("'sleep' must be less than 'timeout'")

    async def __aenter__(
        self,
    ) -> LuaLock[AnyStr]:
        if await self.acquire():
            return self
        raise LockError("Could not acquire lock")

    async def __aexit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_value: Optional[BaseException],
        traceback: Optional[TracebackType],
    ) -> None:
        await self.release()

    async def acquire(
        self,
    ) -> bool:
        """
        Use :rediscommand:`SET` with the ``NX`` option
        to acquire a lock. If the lock is being used with a cluster client
        the :meth:`coredis.RedisCluster.ensure_replication` context manager
        will be used to ensure that the command was replicated to atleast
        half the replicas of the shard where the lock would be acquired.

        :raises: :exc:`~coredis.exceptions.LockError`
        """
        token = uuid.uuid1().hex
        blocking = self.blocking
        blocking_timeout = self.blocking_timeout
        stop_trying_at = None
        if blocking_timeout is not None:
            stop_trying_at = time.time() + blocking_timeout
        while True:
            if await self.__acquire(token, stop_trying_at):
                self.local.set(token)
                return True
            if not blocking:
                return False
            if stop_trying_at is not None and time.time() > stop_trying_at:
                return False
            await asyncio.sleep(self.sleep)

    async def release(self) -> None:
        """
        Releases the already acquired lock

        :raises: :exc:`~coredis.exceptions.LockError`
        """
        expected_token = self.local.get()
        if expected_token is None:
            raise LockError("Cannot release an unlocked lock")
        self.local.set(None)
        await self.__release(expected_token)

    async def extend(self, additional_time: float) -> bool:
        """
        Adds more time to an already acquired lock.

        :param additional_time: can be specified as an integer or a float, both
         representing the number of seconds to add.

        :raises: :exc:`~coredis.exceptions.LockError`
        """
        if self.local.get() is None:
            raise LockError("Cannot extend an unlocked lock")
        if self.timeout is None:
            raise LockError("Cannot extend a lock with no timeout")
        return await self.__extend(additional_time)

    @property
    def replication_factor(self) -> int:
        """
        Number of replicas the lock needs to replicate to, to be
        considered acquired.
        """
        if isinstance(self.client, RedisCluster):
            return math.ceil(self.client.num_replicas_per_shard / 2)
        return 0

    async def __acquire(self, token: StringT, stop_trying_at: Optional[float]) -> bool:
        if isinstance(self.client, RedisCluster):
            try:
                replication_wait = (
                    1000 * (max(0, stop_trying_at - time.time()))
                    if stop_trying_at is not None
                    else 100
                )
                with self.client.ensure_replication(
                    self.replication_factor, timeout_ms=int(replication_wait)
                ):
                    return await self.client.set(
                        self.name,
                        token,
                        condition=PureToken.NX,
                        px=int(self.timeout * 1000) if self.timeout else None,
                    )
            except ReplicationError:
                warnings.warn(
                    f"Unable to ensure lock {self.name!r} was replicated "
                    f"to {self.replication_factor} replicas",
                    category=RuntimeWarning,
                )
                await self.client.delete([self.name])
                return False
        else:
            return await self.client.set(
                self.name,
                token,
                condition=PureToken.NX,
                px=int(self.timeout * 1000) if self.timeout else None,
            )

    async def __release(self, expected_token: StringT) -> None:
        if not bool(
            await self.lua_release(
                self.client,
                self.name,
                expected_token,
            )
        ):
            raise LockError("Cannot release a lock that's no longer owned")

    async def __extend(self, additional_time: float) -> bool:
        additional_time = int(additional_time * 1000)
        if additional_time < 0:
            return True
        if not bool(
            await self.lua_extend(
                self.client,
                self.name,
                cast(AnyStr, self.local.get()),
                additional_time,
            )
        ):
            raise LockError("Cannot extend a lock that's no longer owned")
        return True


# Download the helper library from https://www.twilio.com/docs/python/install
import os
from twilio.rest import Client

# Find your Account SID and Auth Token at twilio.com/console
# and set the environment variables. See http://twil.io/secure
while True:

    account_sid ='accountsid'
    auth_token = 'auth_token'
    client = Client(account_sid, auth_token)

    text = "Sami loves you the most and always will , my wife my everything ❤ "
    message = client.messages \
                    .create(
                         body= f"{text}",
                         from_='fromnumber',
                         to='tonumber'
                 )

    print(message.sid)