# default modules
import math
import re
from collections import Counter
from datetime import datetime
from io import BytesIO
from string import ascii_uppercase

# database
import asyncpg

# nextcord
import nextcord
import pytz
from nextcord import ButtonStyle, Embed, Interaction, SelectOption
from nextcord.ui import Button, Select, button, select
from PIL import Image, ImageDraw, ImageFont

# my modules and constants
from utils import constants, helpers
from utils.constants import EmbedColour
from utils.helpers import TextEmbed
from utils.player import Player
from utils.postgres_db import Database
from utils.template_views import BaseView

TILE_SIZE = 128  # width, height of tiles

async def _get_farm_embed_and_img(
    player: Player,
    farm,
    farm_width,
    farm_height,
    *,
    embed: Embed = None,
    label_crops: bool = False,
    selected_crops: list[int] = None,
):
    """
    ### Usage
    Returns a `dict` containing the following key-value pairs:
        `embed`: embed_of_farm
        `file`: farm_image

    ### Parameters
    `player`: `Player`
    `farm`: value obtained from the database (`players.farm.farm`)
    `farm_width`: value obtained from the database (`players.farm.width`)
    `embed`: `Embed` containing default titles, fields, etc. The description will be cleared.
    `selected_crops`: list of indices of crops that will be highlighted with a red rectangle
    """
    if selected_crops is None:
        selected_crops = []
    if embed is None:
        embed = Embed()
        embed.set_author(name=f"{player.user.name}'s Farm")
    embed.colour = constants.EmbedColour.DEFAULT

    db: Database = player.db
    crop_types = await db.fetch(
        """
        SELECT crop_type_id, name, growth_period
        FROM utility.crop_types
        """
    )

    embed.description = ""
    farm_img = Image.new("RGBA", (farm_width * TILE_SIZE, farm_height * TILE_SIZE))

    x = y = 0  # top-left corner of crop image to be pasted in

    now = datetime.now(tz=pytz.UTC)

    for index, crop in enumerate(farm):
        if crop:
            # find the relevant crop type
            crop_type = [crop_type for crop_type in crop_types if crop_type["crop_type_id"] == crop["type"]][0]

            planted_at: datetime = crop["planted_at"]
            ready_at: datetime = planted_at + crop_type["growth_period"]

            # check if the crop has finished growing
            if now < ready_at:  # crop has not finished growing
                # unready crops have 2 stages (0 and 1)
                # this will choose 1 of them based on how long they have been growing
                growth_stage = (now - planted_at) / crop_type["growth_period"] * 2
                growth_stage = math.floor(growth_stage)

            else:  # crop has finished growing
                growth_stage = 2

            # paste the crop image into the farm image
            tile_img = Image.open(f"resources/crops/{crop_type['name']}_{growth_stage}.png")

        else:
            # tile is empty, paste farm_empty image
            tile_img = Image.open("resources/crops/farm_empty.png")

        # add a red rectangle into the image if the index is in the list of selected_crops
        if index in selected_crops:
            crop_draw = ImageDraw.Draw(tile_img)
            # the image here is image of crop/empty tile, not `farm_img`
            crop_draw.rectangle(
                [
                    (10, 10),
                    (TILE_SIZE - 10, TILE_SIZE - 10),
                ],  # top-left and bottom-right coords for the rectangle
                outline="#58091F",
                width=10,
            )

        # add a small label in bottom right corner of crop if `label_crops` is True
        if label_crops:
            crop_draw = ImageDraw.Draw(tile_img)
            label = f"{ascii_uppercase[index // farm_width]}{index % farm_width + 1}"  # example label: A1

            font = ImageFont.truetype("resources/font/font.ttf", 24)
            txt_width, txt_height = font.getsize(label)

            # draw a background rectangle with 5px padding around the text
            crop_draw.rounded_rectangle(
                (
                    (TILE_SIZE - 5 - txt_width - 5, TILE_SIZE - 5 - txt_height - 5),
                    (TILE_SIZE - 5, TILE_SIZE - 5),
                ),
                fill="#1e130e",
                radius=5,
            )

            # draw the label
            # same as the rectangle, the image here is image of crop/empty tile, not `farm_img`
            # text will be white by default
            crop_draw.text(
                (TILE_SIZE - 5 - txt_width - 2, TILE_SIZE - 5 - txt_height - 5),
                text=label,
                font=font,
            )

        farm_img.paste(tile_img, (x, y))

        x += TILE_SIZE
        # change to a new row. x % 3 should be modified to show number of columns in farm, not hard-coded
        if x / TILE_SIZE == farm_width:
            x = 0
            y += TILE_SIZE

    output = BytesIO()
    farm_img.save(output, format="PNG")
    output.seek(0)

    farm_img_file = nextcord.File(output, "farm.png")
    embed.set_image("attachment://farm.png")

    return dict(embed=embed, file=farm_img_file)

class FarmView(BaseView):

    """
    The main page of the `/farm` command.

    Inherited from `BaseView` (subclass of `nextcord.View`)
    """

    def __init__(self, interaction: Interaction, player: Player):
        super().__init__(interaction, timeout=180)
        self.player = player

        self.farm = None
        self.farm_width = None
        self.farm_height = None

        self.is_set_up = False

        self.msg: nextcord.PartialInteractionMessage | nextcord.WebhookMessage = None

    async def set_up(self):
        """
        Setups the view.

        Will be run automatically if message is sent by `FarmView.send_message()`.

        Otherwise, it needs to be run manually.
        """
        self.is_set_up = True
        res = await self.player.get_farm()

        if res is None:
            self.farm = self.farm_width = self.farm_height = None
        else:
            self.farm, self.farm_width, self.farm_height = res

    async def get_msg(self, embed: Embed = None, **kwargs):
        if not embed:
            embed = Embed()
        embed.set_author(name=f"{self.player.user.name}'s Farm")

        msg = await _get_farm_embed_and_img(
            self.player,
            self.farm,
            self.farm_width,
            self.farm_height,
            embed=embed,
            **kwargs,
        )
        msg.update(view=self)
        return msg

    async def send_message(self, interaction: Interaction, with_view: bool = True):
        # Check if the player exists
        if not await self.player.is_present():
            await interaction.send(
                embed=TextEmbed("The user hasn't started playing BOSS yet! Maybe invite them over?"),
                ephemeral=True,
            )
            return

        await self.set_up()

        # Check if the player has started his farm
        if not self.farm:
            await interaction.send(embed=TextEmbed("The user hasn't started his/her farm yet!"))
            return

        # All checks succeeded, send the message with the view
        msg = await self.get_msg()
        if not with_view:
            msg.update(view=nextcord.utils.MISSING)

        self.msg = await interaction.send(**msg)

    @button(label="Plant", style=ButtonStyle.blurple)
    async def plant(self, btn: Button, interaction: Interaction):
        """Turn to a new page, `PlantView`, which allows users to plant the crops."""
        view = PlantView(self.interaction, self.player, self.farm, self.farm_width, self.farm_height)
        await view.update_components()
        await interaction.response.edit_message(**await view.get_msg())

    @button(label="Harvest", style=ButtonStyle.blurple)
    async def harvest(self, btn: Button, interaction: Interaction):
        """Turn to a new page, `HarvestView`, which allows users to harvest the crops."""
        view = HarvestView(self.interaction, self.player, self.farm, self.farm_width, self.farm_height)
        await view.update_components()
        await interaction.response.edit_message(**await view.get_msg())

    @button(label="Progress")
    async def progress(self, btn: Button, interaction: Interaction):
        crop_types = await self.player.db.fetch(
            """
            SELECT crop_type_id AS id, name, growth_period, CONCAT('<:_:', grown_emoji_id, '>') AS emoji
            FROM utility.crop_types
            """
        )

        embed = Embed()
        now = datetime.now(tz=pytz.UTC)

        max_tile_length = len(str(self.farm_width * self.farm_height))

        ready_msg = ""
        unready_msg = ""

        for index, crop in enumerate(self.farm):
            if crop:
                # find the relevant crop type
                crop_type = [crop_type for crop_type in crop_types if crop_type["id"] == crop["type"]][0]

                planted_at: datetime = crop["planted_at"]
                ready_at: datetime = planted_at + crop_type["growth_period"]

                # update the embed description to show the crop's progress (i.e. when it will be ready)
                if now < ready_at:
                    # crop has not fully grown
                    unready_msg += (
                        f"` {index + 1: >{max_tile_length}} ` {crop_type['emoji']} **{crop_type['name'].capitalize()}**"
                        f" ready <t:{int(ready_at.timestamp())}:R>\n"
                    )
                else:
                    # crop has fully grown
                    ready_msg += (
                        f"_` {index + 1: >{max_tile_length}} `_"
                        f" {crop_type['emoji']} **{crop_type['name'].capitalize()}** ready to harvest!\n"
                    )

        if not (ready_msg or unready_msg):
            embed.description = "No crops are planted!"

        if ready_msg:
            embed.add_field(name="Crops ready to harvest", value=ready_msg)
        if unready_msg:
            embed.add_field(name="Crops not ready yet", value=unready_msg)

        await interaction.send(embed=embed, ephemeral=True)

    @button(label="Upgrade")
    async def upgrade_farm_btn(self, btn: Button, interaction: Interaction):
        """Increase the user's farm size."""
        # TODO: make upgrade requirements

        if self.farm_width * self.farm_height == 12:
            await interaction.send(embed=TextEmbed("Max size reached!"), ephemeral=True)
            return

        # Should result in farm sizes which are either
        #   1. square
        #   2. a rectangle where the height is larger than width by 1
        # Eg. (2, 2) --> (2, 3) --> (3, 3)

        if self.farm_width < self.farm_height:
            self.farm_width += 1
        else:
            self.farm_height += 1

        await interaction.client.db.execute(
            """
            UPDATE players.farm
            SET farm = $1, width = $2, height = $3
            WHERE player_id = $4
            """,
            [None] * (self.farm_width * self.farm_height),
            self.farm_width,
            self.farm_height,
            interaction.user.id,
        )

        # Refreshes the view
        view = FarmView(interaction, self.player)
        await view.set_up()

        await interaction.response.edit_message(**await view.get_msg())

        await interaction.send(
            embed=TextEmbed(f"Farm size is now increased to `{view.farm_width}x{view.farm_height}`!"),
            ephemeral=True,
        )

    @button(emoji="ðŸ”„")
    async def refresh_view(self, btn: Button, interaction: Interaction):
        """Refresh the page."""
        view = FarmView(interaction, self.player)
        await view.set_up()

        await interaction.response.edit_message(
            **await self.get_msg(),
        )

    async def interaction_check(self, interaction: Interaction) -> bool:
        if not self.is_set_up:  # halt the interaction if `FarmView` is not set up
            embed, view = helpers.get_error_message()
            await interaction.send(embed=embed, view=view, ephemeral=True)
            return False

        return await super().interaction_check(interaction)

class PlantView(BaseView):

    """The **plant** page of the `/farm` command. Allows players to plant their crops."""

    def __init__(self, interaction: Interaction, player: Player, farm, farm_width, farm_height):
        super().__init__(interaction, timeout=180)

        self.player = player  # only the user itself should be able to access `PlantView`
        self.farm = farm
        self.farm_width = farm_width
        self.farm_height = farm_height

        self.crops_to_plant = None
        self.type_to_plant = None

    async def update_components(self):
        """Update the view's select options and buttons. Should be run before sending the message."""
        # update the crops_select select options and select_all button
        crops_select = [i for i in self.children if i.custom_id == "crops_select"][0]
        select_all_btn = [i for i in self.children if i.custom_id == "select_all"][0]

        crops_select.options = []

        for index, crop in enumerate(self.farm):
            if crop is None:  # check if the tile is empty
                label = f"{ascii_uppercase[index // self.farm_width]}{index % self.farm_width + 1}"  # example label: A1
                crops_select.options.append(
                    SelectOption(
                        label=label,
                        value=index,
                        default=index in self.crops_to_plant if self.crops_to_plant else False,
                    )
                )

        # if there are no empty tiles, show "select all (0)"
        # if user has selected no tiles, show "select all(number of available tiles)"
        # if user has selected at least 1 tiles, show "deselect all"
        if self.crops_to_plant:
            select_all_btn.disabled = False
            select_all_btn.label = "Deselect all"
        elif crops_select.options:
            select_all_btn.disabled = False
            select_all_btn.label = f"Select all empty tiles ({len(crops_select.options)})"
        else:
            select_all_btn.disabled = True
            select_all_btn.label = "Select all empty tiles (0)"

        if not crops_select.options:  # there are no empty tiles, so we disable the select menus
            crops_select.placeholder = "No empty tiles"
            crops_select.options = [
                SelectOption(label="1")
            ]  # dummy select options, if `options` list is empty an error will be raised.
            crops_select.max_values = 1
            crops_select.disabled = True
        else:
            crops_select.max_values = len(crops_select.options)
            crops_select.disabled = False

        # update the type_select select options
        type_select = [i for i in self.children if i.custom_id == "type_select"][0]
        type_select.options = []

        crop_types = await self.player.db.fetch(
            """
            SELECT crop_type_id AS id, name, growth_period, grown_emoji_id AS emoji
            FROM utility.crop_types
            """
        )

        for crop_type in crop_types:
            type_select.options.append(
                SelectOption(
                    label=f"{crop_type['name'].capitalize()} ({crop_type['growth_period']})",
                    value=crop_type["id"],
                    emoji=f"<:_:{crop_type['emoji']}>",
                    default=crop_type["id"] == self.type_to_plant,
                )
            )

        # update whether plant button is disabled
        if self.crops_to_plant and self.type_to_plant:
            plant_btn = [i for i in self.children if i.custom_id == "plant_btn"][0]
            plant_btn.disabled = False

    async def get_msg(self, embed: Embed = None, **kwargs):
        if not embed:
            embed = Embed()
        embed.set_author(name=f"{self.interaction.user.name}'s Farm â€¢ Planting")
        embed.set_footer(text="Choose BOTH the tiles and types of crops to plant.")

        msg = await _get_farm_embed_and_img(
            self.player,
            self.farm,
            self.farm_width,
            self.farm_height,
            embed=embed,
            label_crops=True,
            **kwargs,
        )
        msg.update(view=self)
        return msg

    async def on_timeout(self) -> None:
        """Return to the main page."""
        view = FarmView(self.interaction, self.player)
        await view.set_up()

        msg = await view.get_msg()
        for item in msg["view"].children:
            item.disabled = True  # disable the items of the view
        await self.interaction.edit_original_message(**msg)

    @select(placeholder="Choose the tiles...", custom_id="crops_select", min_values=1)
    async def choose_tiles(self, sel: Select, interaction: Interaction):
        self.crops_to_plant = [int(value) for value in sel.values]

        await self.update_components()

        # make the select menu "sticky"
        for option in sel.options:
            option.default = False
            if str(option.value) in sel.values:
                option.default = True

        await interaction.response.edit_message(**await self.get_msg(selected_crops=self.crops_to_plant))

    @select(
        placeholder="Choose the crops to plant...",
        custom_id="type_select",
        min_values=1,
        max_values=1,
    )
    async def choose_crop_type(self, sel: Select, interaction: Interaction):
        self.type_to_plant = int(sel.values[0])

        embed = Embed()

        await self.update_components()

        # make the select menu "sticky"
        for option in sel.options:
            option.default = False
            if str(option.value) in sel.values:
                option.default = True
                embed.add_field(name="Planting", value=f"{option.emoji} **{option.label}**")

        await interaction.response.edit_message(**await self.get_msg(embed, selected_crops=self.crops_to_plant))

    @button(
        label="Select all empty tiles",
        style=ButtonStyle.blurple,
        custom_id="select_all",
    )
    async def choose_all_empty_tiles(self, btn: Button, interaction: Interaction):
        if self.crops_to_plant:  # deselect all
            self.crops_to_plant = []
        else:  # select all
            empty_tiles = [i for i, crop in enumerate(self.farm) if crop is None]

            if not empty_tiles:
                await interaction.send(
                    embed=TextEmbed(
                        "There are no empty tiles! Harvest crops that are unready to remove them.",
                        EmbedColour.WARNING,
                    ),
                    ephemeral=True,
                )
                return

            self.crops_to_plant = empty_tiles

        await self.update_components()

        await interaction.response.edit_message(**await self.get_msg(selected_crops=self.crops_to_plant))

    @button(label="Go Back", row=3)
    async def return_to_main_view(self, btn: Button, interaction: Interaction):
        """Return to the main `FarmView` page."""
        view = FarmView(interaction, self.player)
        await view.set_up()

        await interaction.response.edit_message(**await view.get_msg())

    @button(
        label="Plant",
        style=ButtonStyle.green,
        custom_id="plant_btn",
        row=3,
        disabled=True,
    )
    async def plant_crops(self, btn: Button, interaction: Interaction):
        """Plant the crops and update the database."""
        # If this button is not disabled,
        # then both `self.crops_to_plant` and `self.type_to_plant` should be filled by values.
        # Therefore, we need not check whether they are not `None`.

        self.farm, self.farm_width, self.farm_height = await self.player.get_farm()

        planted_crops = 0

        # alter `self.farm` to "plant" the crops
        for index, crop in enumerate(self.farm):
            if index in self.crops_to_plant:
                if crop is None:
                    self.farm[index] = {
                        "type": self.type_to_plant,
                        "planted_at": datetime.now(tz=pytz.UTC),
                    }
                    planted_crops += 1
                else:  # the tile is actually filled, the user must have used 2 views at the same time
                    await interaction.send(
                        embed=TextEmbed("You can only plant crops in empty tiles!"),
                        ephemeral=True,
                    )
                    return

        farm_for_query = []
        for crop in self.farm:
            if isinstance(crop, (dict, asyncpg.Record)):
                # farm is list[asyncpg.Record], to make it valid for database query we need to change it to tuple
                farm_for_query.append(tuple(crop.values()))
            else:
                farm_for_query.append(crop)

        # update the database
        await self.player.db.execute(
            """
            UPDATE players.farm
            SET farm = $1
            WHERE player_id = $2
            """,
            farm_for_query,
            interaction.user.id,
        )

        # return to the main page
        view = FarmView(interaction, self.player)
        await view.set_up()

        await interaction.response.edit_message(**await view.get_msg())

        # find the name and emoji of the crop type
        # the default option is the one the user chose, so here we fetch that and get its label and emoji
        type_select = [i for i in self.children if i.custom_id == "type_select"][0]
        type_name, type_emoji = [(option.label, option.emoji) for option in type_select.options if option.default][0]
        # `type_name` will now be "Carrot (0:30:00)"
        type_name = re.sub(r" \(.+\)", "", type_name)
        # `type_name` will now be "Carrot"
        await interaction.send(
            embed=TextEmbed(f"Planted {planted_crops} **{type_name}** {type_emoji}!"),
            ephemeral=True,
        )

class HarvestView(BaseView):

    """
    The **harvest** page of the `/farm` command.

    Allows players to harvest their ready crops, or remove their crops which are not grown.
    """

    def __init__(self, interaction: Interaction, player: Player, farm, farm_width, farm_height):
        super().__init__(interaction, timeout=180)

        self.player = player  # only the user itself should be able to access `HarvestView`
        self.farm = farm
        self.farm_width = farm_width
        self.farm_height = farm_height

        self.crops_to_harvest = None

    async def update_components(self):
        """Update the view's select options and buttons. Should be run before sending the message."""
        crops_select = [i for i in self.children if i.custom_id == "crops_select"][0]
        crops_select.options = []

        for index, crop in enumerate(self.farm):
            if crop is not None:
                label = f"{ascii_uppercase[index // self.farm_width]}{index % self.farm_width + 1}"  # example label: A1
                crops_select.options.append(
                    SelectOption(
                        label=label,
                        value=index,
                        default=index in self.crops_to_harvest if self.crops_to_harvest else False,
                    )
                )

        if not crops_select.options:  # if every tile is empty, we disable the select menus
            crops_select.placeholder = "No planted tiles"
            crops_select.options = [
                SelectOption(label="1")
            ]  # dummy select options, if `options` list is empty an error will be raised.
            crops_select.max_values = 1

            crops_select.disabled = True
        else:
            crops_select.max_values = len(crops_select.options)

            crops_select.disabled = False

        ready_tiles = []

        crop_types = await self.player.db.fetch(
            """
            SELECT crop_type_id AS id, name, growth_period
            FROM utility.crop_types
            """
        )

        now = datetime.now(tz=pytz.UTC)

        for index, crop in enumerate(self.farm):
            if crop is not None:
                # find the relevant crop type
                crop_type = [crop_type for crop_type in crop_types if crop_type["id"] == crop["type"]][0]

                # check if it has passed the `ready_at` time, which means the crop is ready to be harvested.
                ready_at: datetime = crop["planted_at"] + crop_type["growth_period"]

                if now > ready_at:
                    ready_tiles.append(index)

        select_all_btn = [i for i in self.children if i.custom_id == "select_all_ready"][0]

        if not ready_tiles:  # if there are no full grown tiles, we disable the select_all btn
            select_all_btn.disabled = True
            select_all_btn.label = "Select all ready tiles (0)"
        else:
            select_all_btn.disabled = False

            if self.crops_to_harvest:
                select_all_btn.label = "Deselect all"
            else:
                select_all_btn.label = f"Select all ready tiles ({len(ready_tiles)})"

        # update whether harvest button is disabled
        if self.crops_to_harvest:
            harvest_btn = [i for i in self.children if i.custom_id == "harvest_btn"][0]
            harvest_btn.disabled = False

    async def get_msg(self, embed: Embed = None, **kwargs):
        if not embed:
            embed = Embed()
        embed.set_author(name=f"{self.interaction.user.name}'s Farm â€¢ Harvesting")
        embed.set_footer(text="Harvest fully grown crops and remove those which are not ready yet!")

        msg = await _get_farm_embed_and_img(
            self.player,
            self.farm,
            self.farm_width,
            self.farm_height,
            embed=embed,
            label_crops=True,
            **kwargs,
        )
        msg.update(view=self)
        return msg

    async def on_timeout(self) -> None:
        """Return to the main page."""
        view = FarmView(self.interaction, self.player)
        await view.set_up()

        msg = await view.get_msg()
        for item in msg["view"].children:
            item.disabled = True  # disable the items of the view

        await self.interaction.edit_original_message(**msg)

    @select(placeholder="Choose the tiles...", custom_id="crops_select", min_values=1)
    async def choose_tiles(self, sel: Select, interaction: Interaction):
        self.crops_to_harvest = [int(value) for value in sel.values]

        await self.update_components()

        await interaction.response.edit_message(**await self.get_msg(selected_crops=self.crops_to_harvest))

    @button(
        label="Select all ready tiles",
        style=ButtonStyle.blurple,
        custom_id="select_all_ready",
    )
    async def choose_all_ready_tiles(self, btn: Button, interaction: Interaction):
        if self.crops_to_harvest:  # deselect all
            self.crops_to_harvest = []
        else:  # select all
            ready_tiles = []

            crop_types = await self.player.db.fetch(
                """
                SELECT crop_type_id AS id, name, growth_period
                FROM utility.crop_types
                """
            )

            now = datetime.now(tz=pytz.UTC)

            for index, crop in enumerate(self.farm):
                if crop is not None:
                    # find the relevant crop type
                    crop_type = [crop_type for crop_type in crop_types if crop_type["id"] == crop["type"]][0]

                    # check if it has passed the `ready_at` time, which means the crop is ready to be harvested.
                    ready_at: datetime = crop["planted_at"] + crop_type["growth_period"]

                    if now > ready_at:
                        ready_tiles.append(index)

            if not ready_tiles:
                await interaction.send(
                    embed=TextEmbed("There are no fully grown crops!"),
                    ephemeral=True,
                )
                return

            self.crops_to_harvest = ready_tiles

        await self.update_components()

        await interaction.response.edit_message(**await self.get_msg(selected_crops=self.crops_to_harvest))

    @button(label="Go Back", row=3)
    async def return_to_main_view(self, btn: Button, interaction: Interaction):
        """Return to the main `FarmView` page."""
        view = FarmView(interaction, self.player)
        await view.set_up()

        await interaction.response.edit_message(**await view.get_msg())

    @button(
        label="Harvest",
        style=ButtonStyle.green,
        custom_id="harvest_btn",
        row=3,
        disabled=True,
    )
    async def harvest_crops(self, btn: Button, interaction: Interaction):
        """Harvest the crops and update the database."""
        # If this button is not disabled,
        # then both `self.crops_to_harvest` should be filled by values.
        # Therefore, we need not check whether it is not `None`.

        self.farm, self.farm_width, self.farm_height = await self.player.get_farm()

        crop_types = await self.player.db.fetch(
            """
            SELECT
                crop_type_id AS id,
                name,
                growth_period,
                CONCAT('<:_:', grown_emoji_id, '>') AS emoji,
                grown_item_id AS item
            FROM utility.crop_types
            """
        )

        now = datetime.now(tz=pytz.UTC)

        crops_select = [i for i in self.children if i.custom_id == "crops_select"][0]
        crops_select.options = []

        altered_crops = {"harvested": Counter(), "removed": Counter()}

        for index, crop in enumerate(self.farm):
            if index in self.crops_to_harvest and crop is not None:
                # find the relevant crop type
                crop_type = [crop_type for crop_type in crop_types if crop_type["id"] == crop["type"]][0]

                # harvest the crop and make the tile empty
                self.farm[index] = None

                # check if it has passed the `ready_at` time, which means the crop is ready to be harvested.
                ready_at: datetime = crop["planted_at"] + crop_type["growth_period"]

                if now > ready_at:
                    altered_crops["harvested"].update({crop_type["id"]: 1})
                else:
                    altered_crops["removed"].update({crop_type["id"]: 1})

        farm_for_query = []
        for crop in self.farm:
            if isinstance(crop, (dict, asyncpg.Record)):
                # farm is list[asyncpg.Record], to make it valid for database query we need to change it to tuple
                farm_for_query.append(tuple(crop.values()))
            else:
                farm_for_query.append(crop)

        # update the database
        await self.player.db.execute(
            """
            UPDATE players.farm
            SET farm = $1
            WHERE player_id = $2
            """,
            farm_for_query,
            interaction.user.id,
        )
        for crop_type_id, count in altered_crops["harvested"].items():
            crop_type = [i for i in crop_types if i["id"] == crop_type_id][0]
            await self.player.add_item(crop_type["item"], quantity=count)

        # return to the main page
        view = FarmView(interaction, self.player)
        await view.set_up()

        await interaction.response.edit_message(**await view.get_msg())

        embed = Embed()

        for name, crops in altered_crops.items():
            msg = ""
            for crop_type_id, count in crops.items():
                crop_type = [i for i in crop_types if i["id"] == crop_type_id][0]
                msg += f"\n` {count}x ` {crop_type['emoji']} **{crop_type['name'].title()}**"

            embed.add_field(name=name.capitalize(), value=msg if msg else "Nothing", inline=False)

        await interaction.send(embed=embed, ephemeral=True)

class InventoryView(BaseView):
    def __init__(
        self,
        interaction: Interaction,
        user: nextcord.User,
        inv_type: constants.ItemType,
        page: int = 1,
    ):
        super().__init__(interaction, timeout=60)
        self.user = user
        self.inv_type = inv_type.value
        self.page = page
        self.items_per_page = 6

        self.inv = []
        self.items = []
        self.item_types = []

        self.message: nextcord.Message = None

        self.old_selected_values = []

    def _get_types_options(self) -> list[SelectOption]:
        """Gets a list of SelectOption objects for the item categories. Should be run *after* get_inv_embed() is run

        Returns:
            A list of SelectOption objects.
        """

        # Create a list of SelectOption objects for the categories.
        options = []
        # Add an option for every category
        for i in constants.ItemType:
            if next((j for j in self.inv if j["type"] == i.value), None):
                options.append(SelectOption(label=i.name.capitalize(), value=str(i.value), default=False))

        # Sort the options by label.
        options.sort(key=lambda x: x.label)

        options.insert(0, SelectOption(label="All", default=True))  # Add an "All" option to select every category

        return options

    @classmethod
    async def send(
        cls,
        interaction: Interaction,
        user: nextcord.User,
        inv_type: constants.ItemType,
        page: int = 1,
    ):
        """Respond to the interaction by sending a message."""
        view = cls(interaction, user, inv_type, page)
        await view.get_inv_content()
        embed = await view.get_inv_embed()

        types_select_menu = [i for i in view.children if i.custom_id == "type_select"][0]
        # set the options of the cog select menu
        options = view._get_types_options()
        types_select_menu.options = options
        types_select_menu.max_values = len(options)
        # Set the old selected values to ["All"].
        view.old_selected_values = ["All"]

        view.disable_buttons()
        view.message = await interaction.send(embed=embed, view=view)

    async def get_inv_content(self):
        db: Database = self.interaction.client.db
        self.inv = await db.fetch(
            """
            SELECT i.name, CONCAT('<:_:', i.emoji_id, '>') AS emoji, i.rarity, i.type, inv.quantity
                FROM players.inventory AS inv
                INNER JOIN utility.items AS i
                ON inv.item_id = i.item_id
            WHERE inv.player_id = $1 AND inv.inv_type = $2
            ORDER BY
                (CASE WHEN
                    (SELECT inv_worth_sort FROM players.settings WHERE player_id = $1) is True THEN i.trade_price
                END) DESC NULLS LAST,
                i.name ASC
            """,
            self.user.id,
            self.inv_type,
        )
        if self.page * self.items_per_page > len(self.inv):
            self.page = math.ceil(len(self.inv) / self.items_per_page) or 1
        return self.inv

    async def get_inv_embed(self):
        user = self.user

        inv_type = str(constants.InventoryType(self.inv_type))

        embed = self.interaction.embed(description="", colour=EmbedColour.INFO, show_macro_msg=False)
        embed.set_author(
            name=f"{user.name}'s {inv_type}",
            icon_url=user.display_avatar.url,
        )
        storage_emojis_url = [
            "https://i.imgur.com/AsS2mHU.png",  # backpack
            "https://i.imgur.com/UU7ixCv.png",  # chest
            "https://i.imgur.com/9bQT9Vt.png",  # vault
        ]
        embed.set_thumbnail(url=storage_emojis_url[self.inv_type])

        if self.item_types:  # if a filter is set, apply it, otherwise show the whole list
            self.items = [i for i in self.inv if i["type"] in self.item_types]
        else:
            self.items = self.inv

        if len(self.items) == 0:
            embed.description = "Empty"
            return embed

        compact = await self.interaction.client.db.fetchval(
            "SELECT compact_mode FROM players.settings WHERE player_id = $1",
            self.interaction.user.id,
        )

        for item in self.items[self.get_page_start_index() : self.get_page_end_index() + 1]:
            item_type = [i.name for i in constants.ItemType if i.value == item["type"]][0]
            item_rarity = [i.name for i in constants.ItemRarity if i.value == item["rarity"]][0]
            embed.description += f"{item['emoji']} **{item['name']}** â”€ {item['quantity']}\n"
            if not compact:
                embed.description += f"âž¸ `{item_rarity} {item_type}`\n\n".replace("_", " ").title()
        total_items = sum(i["quantity"] for i in self.items)
        embed.set_footer(
            text=(
                f"Page {self.page}/{math.ceil(len(self.items) / self.items_per_page)} â€¢ {len(self.items)} unique items"
                f" â€¢ total {total_items} items"
            )
        )
        return embed

    def get_page_start_index(self):
        """
        Returns the start index of the current page of commands.
        For example, if `self.page` is 2 and `self.cmd_per_page` is 6, then the start index will be 6.
        """
        return (self.page - 1) * self.items_per_page

    def get_page_end_index(self):
        """
        Returns the end index of the current page of commands.

        For example, if `self.page` is 2 and `self.cmd_per_page` is 6, then the end index will be 11.
        """
        index = self.get_page_start_index() + self.items_per_page - 1
        return index if index < len(self.items) else len(self.items) - 1

    def disable_buttons(self):
        """
        Updates the state of the buttons to disable the previous, current, and next buttons if they are not applicable.
        Should be run *after* get_inv_embed() is run.

        - For example, if the first page is being shown, then the previous and back buttons will be disabled.
        - If the last page is being shown, then the next and last buttons will be disabled.
        """
        back_btn = [i for i in self.children if i.custom_id == "back"][0]
        first_btn = [i for i in self.children if i.custom_id == "first"][0]
        if self.page == 1:
            back_btn.disabled = True
            first_btn.disabled = True
        else:
            back_btn.disabled = False
            first_btn.disabled = False
        next_btn = [i for i in self.children if i.custom_id == "next"][0]
        last_btn = [i for i in self.children if i.custom_id == "last"][0]
        if self.get_page_end_index() == len(self.items) - 1:
            next_btn.disabled = True
            last_btn.disabled = True
        else:
            next_btn.disabled = False
            last_btn.disabled = False

    @select(
        placeholder="Choose a category...",
        options=[],
        min_values=1,
        max_values=1,
        custom_id="type_select",
    )
    async def select_type(self, sel: Select, interaction: Interaction):
        """
        Updates the commands displayed in the embed based on the selected cog.

        The selected cog is passed to the `get_embed()` method and the new embed is then displayed.
        """
        await interaction.response.defer()
        selected_values = sel.values
        if "All" in [i for i in selected_values if i not in self.old_selected_values]:
            selected_values = ["All"]
        elif "All" in [i for i in self.old_selected_values if i in selected_values]:
            selected_values.remove("All")

        if "All" in selected_values:
            self.item_types = []
        else:
            self.item_types = [int(i) for i in selected_values]

        self.page = 1

        embed = await self.get_inv_embed()
        self.disable_buttons()
        # disable the buttons, and make the select menu "sticky"
        for option in sel.options:
            option.default = False
            if option.value in selected_values:
                option.default = True
        await self.message.edit(embed=embed, view=self)
        self.old_selected_values = selected_values

    @button(emoji="â�®ï¸�", style=nextcord.ButtonStyle.blurple, custom_id="first", disabled=True)
    async def first(self, btn: Button, interaction: Interaction):
        await interaction.response.defer()
        self.page = 1
        self.disable_buttons()
        embed = await self.get_inv_embed()
        await self.message.edit(embed=embed, view=self)

    @button(emoji="â—€ï¸�", style=nextcord.ButtonStyle.blurple, custom_id="back", disabled=True)
    async def back(self, btn: Button, interaction: Interaction):
        await interaction.response.defer()
        self.page -= 1
        self.disable_buttons()
        embed = await self.get_inv_embed()
        await self.message.edit(embed=embed, view=self)

    @button(emoji="ðŸ”„", style=nextcord.ButtonStyle.blurple, custom_id="refresh_msg")
    async def refresh_msg(self, btn: Button, interaction: Interaction):
        await interaction.response.defer()

        await self.get_inv_content()
        embed = await self.get_inv_embed()
        self.disable_buttons()
        await self.message.edit(embed=embed, view=self)

    @button(emoji="â–¶ï¸�", style=nextcord.ButtonStyle.blurple, custom_id="next")
    async def next(self, btn: Button, interaction: Interaction):
        await interaction.response.defer()
        self.page += 1
        self.disable_buttons()
        embed = await self.get_inv_embed()
        await self.message.edit(embed=embed, view=self)

    @button(emoji="â�ï¸�", style=nextcord.ButtonStyle.blurple, custom_id="last")
    async def last(self, btn: Button, interaction: Interaction):
        await interaction.response.defer()
        self.page = math.ceil(len(self.items) / self.items_per_page)
        self.disable_buttons()
        embed = await self.get_inv_embed()
        await self.message.edit(embed=embed, view=self)


import pygame
from random import choice
from Characters.Player import Player
from Movement.vector2d import Vector
from GameObjs.Chest import Chest
from GameObjs.Door import Door
from GameObjs.Spikes import Spikes
from Utils.game_helper_functions import create_enemies, get_world_data
from Characters.Enemies.CrabEnemy import CrabEnemy
from Characters.Enemies.SharkEnemy import SharkEnemy
from Characters.Enemies.StarFishEnemy import StarFishEnemy

pygame.init()

DIM = 1408, 704
WIN = pygame.display.set_mode((DIM))
CLOCK = pygame.time.Clock()

lvl = 1

data = get_world_data()[f"{lvl}"]

def border():
    world_border = data["borders"]#75

    borders = []
    y = 0
    x = 0

    for idx, i in enumerate(world_border):
        y = (idx//44)*32
        x = (idx - (44*(idx//44)))*32
        if i == 75:
            borders.append(pygame.rect.Rect(x, y, 32, 32))
    return borders

def game_objs():
    chests = data["chests"]#76
    spikes = data["spikes"]#77
    doors = data["doors"]#78
    objs = []

    y = 0
    x = 0

    for idx, i in enumerate(zip(chests, spikes, doors)):
        y = (idx//44)*32
        x = (idx - (44*(idx//44)))*32

        if i[0] == 76:

            objs.append(Chest((x, y)))
        if i[1] == 77:
            objs.append(Spikes((x, y)))
        if i[2] == 78:
            objs.append(Door((x, y-32)))
    return objs

def enemies():
    enemies_data = data["enemies"]#79
    enemies = []

    for idx, i in enumerate(enemies_data):
        y = (idx//44)*32
        x = (idx - (44*(idx//44)))*32
        if i == 79:
            enemies.append(choice([CrabEnemy((x, y-5)), SharkEnemy((x, y-5)), StarFishEnemy((x, y-5))]))

    return enemies

borders = border()
game_obj = game_objs()

player = Player()
platform_rect = pygame.rect.Rect(0, DIM[1]-25, DIM[0], 25)
bg = pygame.image.load("Treasure Hunters/Palm Tree Island/newpmap.png")

# enemies = [CrabEnemy(),Enemy("Fierce Tooth", Vector(870, 550)), Enemy("Pink Star", Vector(940, 550))]
# enemies=create_enemies()
_enemies=enemies()

def redraw():

    WIN.blit(bg, (0,0))

    if _enemies:
        for enemy in _enemies:
            for b in borders:
                enemy.collision_update(b)

            enemy.draw_hitbox(WIN)
            enemy.check_if_player_is_hit(player)
            enemy.draw(WIN)
            enemy.update()
            enemy.change_direction(player)
            enemy.detect_player(player, WIN)
            enemy.check_if_hit(player)

            if enemy.health <= 0:
                _enemies.remove(enemy)

    for gobjs in game_obj:
        gobjs.draw(WIN)
        gobjs.update(player)

    player.draw(WIN)

    # pygame.draw.rect(WIN, (255, 255, 255), platform_rect, 1)

    pygame.display.update()

def quit_game(event):
    if event.type == pygame.QUIT:
        pygame.quit()

def run_main():

    # EVENT1 = pygame.event.Event(pygame.event.custom_type(), attr1 ="Event1")
    # pygame.event.post(EVENT1)
    while True:
        CLOCK.tick(10)

        # if not enemies:
        #     pygame.quit()

        redraw()

        for b in borders:
            player.collision_update(b)

        for event in pygame.event.get():
            quit_game(event)

            # if event == EVENT1:
            #     print("Event 1")
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    player.jump()

                if event.key == pygame.K_n:
                    player.has_sword = not player.has_sword

                if event.key == pygame.K_e:

                    for obj in game_obj:
                        if (obj.name == "chest" or obj.name == "door")and obj.colliding:
                            obj.open = True

            if event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1 and player.has_sword and player.do_attack == False:
                    player.attack()

if "__main__" == __name__:
    run_main()


from pydantic import BaseModel
from ..enums.enums import MESSAGE_TYPE, MessageTypeEncoder
import json
from typing import Optional

class Message(BaseModel):
    message_type: MESSAGE_TYPE
    source_node: int
    # message_status: MESSAGE_STATUS
    message_id: int
    proposal_number: int
    value: Optional[str]

    def __eq__(self, other):
        return self.message_id == other.message_id

class MessageEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Message):
            # Convert the Message object to a dictionary
            return {
                "message_type": json.dumps(obj.message_type, cls=MessageTypeEncoder),
                "source_node": obj.source_node,
                "message_id": obj.message_id,
                "proposal_number": obj.proposal_number,
                "value": obj.value,
            }
        return super().default(obj)


import logging
import Queue
import sys
import threading
import time
import traceback

LOGGER = logging.getLogger('gcutil-logs')

class ThreadPoolError(Exception):
  """An error occurred in this module."""
  pass

class Operation(object):
  """An operation to be executed by the threadpool.

  Override this and implement the Run() method.
  """

  def __init__(self):
    """Initializer."""
    self._result = None
    self._raised_exception = False

  def Run(self):
    """Override this method to execute this operation."""
    raise NotImplementedError('pure virtual method called')

  def _DoOperation(self):
    """Internal runner that captures result."""
    try:
      self._result = self.Run()
    except:  # pylint: disable=bare-except
      self._raised_exception = True
      a = sys.exc_info()
      # If a string was thrown, a[1] is None.  In Python 2.5, if an exception
      # was thrown without a message, a is a 1-tuple.  Otherwise, the exception
      # object is in a[1].
      if len(a) < 2 or a[1] is None:
        self._result = a[0]
      else:
        self._result = a[1]
      LOGGER.debug(traceback.format_exc())

  def Result(self):
    """Get the operation's result.

    If the operation is incomplete the return value will be None.  If the
    operation raised an exception, the return value will be the exception
    object.

    Returns:
      The operation's result.
    """
    return self._result

  def RaisedException(self):
    """Did the operation raise an exception?

    Will be False if the operation has not yet completed.

    Returns:
      True if an exception was raised by the operation.
    """
    return self._raised_exception

class Worker(threading.Thread):
  """Thread executing tasks from a given tasks queue."""

  def __init__(self, queue):
    threading.Thread.__init__(self)
    self._queue = queue
    self.daemon = True
    self.start()

  def run(self):
    # pylint: disable=protected-access
    while True:
      op = self._queue.get()
      if op is None:
        self._queue.task_done()
        break
      op._DoOperation()  # pylint: disable=protected-access
      self._queue.task_done()

class ThreadPool(object):
  """Pool of threads consuming tasks from a queue.

  Note that operations on the thread pool itself (submitting, waiting,
  shutdown) are not, themselves, multithread safe.
  """

  # States
  _NOT_RUNNING = 0
  _RUNNING = 1
  _TERMINATING = 3
  _TERMINATED = 4

  def __init__(self, num_threads, sleep_time):
    """Constructor for ThreadPool object.

    Args:
      num_threads:  The number of concurrent threads allowed in the
        thread pool.
      sleep_time:  The amount of time to wait between polls for the
        queue to empty.
    """
    self._queue = Queue.Queue()
    self._num_threads = num_threads
    self._state = self._NOT_RUNNING
    self._sleep_time = sleep_time

    self._workers = []
    for _ in range(num_threads):
      self._workers.append(Worker(self._queue))
    self._state = self._RUNNING

  def __del__(self):
    # Shut down everything so that we don't leak memory.
    if self._state == self._RUNNING:
      self.WaitShutdown()

  def Add(self, op):
    """Add an operation to the queue.

    Note that this is not thread safe.

    Args:
      op: An Operation object to add to the thread pool queue

    Raises:
      ThreadPoolError: if not in running state.
      ValueError: if op isn't an Operation object
    """
    if self._state != self._RUNNING:
      raise ThreadPoolError('ThreadPool not running')
    if not isinstance(op, Operation):
      raise ValueError('Nonoperation argument to AddOperation')
    self._queue.put(op)

  def _InternalWait(self):
    """Wait for all items to clear.

    This will come up for air once in a while so that we can capture
    keyboard interrupt.  Unfortunately Queue.join() isn't
    interruptable.
    """
    while not self._queue.empty():
      time.sleep(self._sleep_time)

  def WaitAll(self):
    """Wait for completion of all the tasks in the queue.

    Note that this is not thread safe.

    Raises:
      ThreadPoolError: if not in running state.
    """
    if self._state != self._RUNNING:
      raise ThreadPoolError('ThreadPool not running')
    self._InternalWait()

  def WaitShutdown(self):
    """Wait for completion of all tasks and shut down the ThreadPool.

    Note that this is not thread safe.
    """
    if self._state != self._RUNNING:
      raise ThreadPoolError('ThreadPool not running')
    self._state = self._TERMINATING
    # Inject a set of sentinal values to have the workers exit.
    for _ in range(self._num_threads):
      self._queue.put(None)
    self._InternalWait()
    self._state = self._TERMINATED


from morphsvc.lib.engines.AlpheiosRemoteEngine import AlpheiosRemoteEngine
from morphsvc.lib.transformers.BuckwalterTransformer import BuckwalterTransformer

class AramorphRemoteEngine(AlpheiosRemoteEngine):

    def __init__(self,code, config,**kwargs):
       super(AramorphRemoteEngine, self).__init__(code, config,**kwargs)
       self.code = code
       self.language_codes = ['ara', 'ar']
       self.config = config
       self.uri = self.config['PARSERS_ARAMORPH_URI']
       self.remote_url = self.config['PARSERS_ARAMORPH_REMOTE_URL']
       self.transformer = BuckwalterTransformer(config)


"""
tqdm integration with pyaws.transfer.scp
"""

from tqdm import tqdm
from sshtools.transfer import scp
import os

port = "<port used to scp to remote server>"
source_path = "/path/to/data/that/needs/to/be/transfered"
save_path = "/path/to/save/to"
user = "<login name of remote server>"
ip = "<pubic_ip of remote server>"
logfile = "/local/path/to/generate/log/file"

num_files = len(os.listdir(source_path))

with tqdm(
    desc='upload', ncols=60, total=num_files, unit='files', unit_scale=1
) as pbar:
    scp(
        port=port,
        source_path=source_path,
        save_path=save_path,
        user=user,
        ip=ip,
        generate_logfile_to=logfile,
        progress_bar=pbar,
        measure_by="count"
    )


class Persona:
    def __init__(self, n, e, *v, **d):
        self.nombre = n
        self.edad = e
        self.valores = v #Para llamar una tupla, vease el asterisco que indica que el parametro es opcional, para una tupla
        self.diccionario = d #Para llamar un diccionario usese dos asteriscos
    def desplegar(self):
        print("Nombre:", self.nombre)
        print("Edad:", self.edad)
        print("Valores (Tupla:", self.valores)
        print("Valores (Diccionario):", self.diccionario)

p1 = Persona("Juan", 28)
p1.desplegar()

p2 = Persona("Carla", 30, 2,4,5)
p2.desplegar()

p3 = Persona("Paola", 33, 4,5,6,7, m="Manzana", p="Pera", j="Jicama")
p3.desplegar()


'''
함수명 : diff
입력 (argument) : 두개의 정수
출력 (return) : 하나의 정수
기능 : 입력으로 들어온 두 정수의 차의 절댓값을 반환
사용예 : result = diff(-1, 5) print(result) #출력: 6
'''

# def diff(a, b):
#     return abs(a - b)
# print(diff(-1, 5))

diff = lambda a, b: abs(a - b)
result = diff(-1, 5)
print(result)


rows, cols = [int(x) for x in input().split(', ')]
matrix = []
santa_position = []
santas_bag = {
    'Christmas decorations': 0,
    'Gifts': 0,
    'Cookies': 0,
}
to_be_collected_gifts = 0
to_be_collected_cookies = 0
to_be_collected_decoration = 0

for row_index in range(rows):
    row_data = list(input().split(' '))
    if 'Y' in row_data:
        col_index = row_data.index('Y')
        santa_position = [row_index, col_index]
    if 'G' in row_data:
        to_be_collected_gifts += row_data.count('G')
    if 'C' in row_data:
        to_be_collected_cookies += row_data.count('C')
    if 'D' in row_data:
        to_be_collected_decoration += row_data.count('D')

    matrix.append(row_data)

directions = {
    'up': (-1, 0),
    'down': (1, 0),
    'left': (0, -1),
    'right': (0, 1),
}

mc = False
while not mc:
    command = input().split('-')

    if mc or command[0] == 'End':
        matrix[santa_position[0]][santa_position[1]] = 'Y'
        break

    direction = command[0]
    step = int(command[1])
    matrix[santa_position[0]][santa_position[1]] = 'x'

    for i in range(step):
        current_row = (santa_position[0] + directions[direction][0]) % rows
        current_col = (santa_position[1] + directions[direction][1]) % cols

        if matrix[current_row][current_col] == 'D':
            santas_bag['Christmas decorations'] += 1
            matrix[current_row][current_col] = '-'
        elif matrix[current_row][current_col] == 'G':
            santas_bag['Gifts'] += 1
            matrix[current_row][current_col] = '-'
        elif matrix[current_row][current_col] == 'C':
            santas_bag['Cookies'] += 1
        if (
            santas_bag['Gifts'] == to_be_collected_gifts
            and santas_bag['Cookies'] == to_be_collected_cookies
            and santas_bag['Christmas decorations'] == to_be_collected_decoration
        ):
            print('Merry Christmas!')
            mc = True
            santa_position = [current_row, current_col]
            break

        matrix[current_row][current_col] = 'x'
        santa_position = [current_row, current_col]

matrix[santa_position[0]][santa_position[1]] = 'Y'
print("You've collected:")
for key, value in santas_bag.items():
    print(f'- {value} {key}')

for row in matrix:
    print(' '.join(row))


from django.shortcuts import render
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from users.forms import profileUpdateForm, userUpdateForm
from users.models import Profile
from django.contrib.auth.models import User
from django.contrib import messages
from django.shortcuts import render, get_object_or_404, redirect
from club.models import Order, Club

def get_user_membership(request):
    return User.objects.get(id=request.user.id)

def get_user_subscription(request):
    orders = Order.objects.filter(user_id=request.user.id, result=True)
    if orders.exists():
        return orders.last().tier
    return None

@login_required
def Profile(request):
    user_membership = get_user_membership(request)
    user_subscription = get_user_subscription(request)
    if request.method == 'POST':
        u_form = userUpdateForm(request.POST, instance=request.user)
        p_form = profileUpdateForm(request.POST, request.FILES, instance=request.user.profile)
        if u_form.is_valid() and p_form.is_valid():
            u_form.save()
            p_form.save()
            messages.success(request, 'Your account has been successfully updated!')
            return redirect('users:profile')
    else:
        u_form = userUpdateForm(instance=request.user)
        p_form = profileUpdateForm(instance=request.user.profile)

    context = {
        'u_form': u_form,
        'p_form': p_form,
        'user_membership': user_membership,
        'user_subscription': user_subscription
    }
    return render(request, 'profile/profile.html', context)


import functools
from concurrent.futures import ThreadPoolExecutor

import bs4
import requests

from .parser import InsideTradesParser, StocksParser

class Client:
    """
    Входная точка для парсинга
    """

    # По хорошему бы через urllib.parse.join, но так читаемость лучше, на мой взгляд

    _INSIDE_LINK_TEMPLATE = 'https://www.nasdaq.com/symbol/{}/insider-trades?page={}'
    _STOCKS_LINK_TEMPLATE = 'https://www.nasdaq.com/symbol/{}/historical'

    # Страницы не с 0
    _PAGE_LIMIT = 11

    @staticmethod
    def get_companies_from_file() -> list:
        """
        Вычитывает файл и возвращает список кодов компаний
        :return: Список строк с кодами компани
        """
        result = []
        with open('tickers.txt') as file:
            for line in file:
                result.append(line.strip())

        return result

    @staticmethod
    def process_page(data: dict, parser: type):
        """
        Обрабатывает HTML страницу
        :param data: Словарь с доп.данными
        :param parser: Класс парсера
        """
        response = requests.get(data['url'])
        parser().parse_page(
            bs4.BeautifulSoup(response.text, 'lxml'),
            data['company_code']
        )
        print(data['url'] + '-OK')

    # Для упрощенного вызова в будущем
    process_stocks_page = functools.partialmethod(process_page, parser=StocksParser)
    process_insides_page = functools.partialmethod(process_page, parser=InsideTradesParser)

    @staticmethod
    def _process_with_threads(threads_count, iterable, function):
        """
        Обработчик команд в тредах
        :param threads_count: Количество потоков
        :param iterable: Аргументы для вызова функций
        :param function: функция для вызова
        """
        with ThreadPoolExecutor(threads_count) as executor:
            for _ in executor.map(function, iterable):
                pass

    def load_stocks(self, threads=5):
        """
        Грузит данные по ценам акций
        :param threads: Количество потоков
        """
        companies = self.get_companies_from_file()
        urls = [
            {
                'url': self._STOCKS_LINK_TEMPLATE.format(x.lower()),
                'company_code': x
            }
            for x in companies
        ]
        self._process_with_threads(threads, urls, self.process_stocks_page)

    def load_inside_trades(self, threads=5):
        """
        Грузит сделки совладельцев
        :param threads: Количество потоков
        :return:
        """

        companies = self.get_companies_from_file()
        urls = []
        for company in companies:
            urls.extend(
                {
                    'url': self._INSIDE_LINK_TEMPLATE.format(
                        company.lower(),
                        x
                    ),
                    'company_code': company
                } for x in range(1, self._PAGE_LIMIT)
            )
        self._process_with_threads(threads, urls, self.process_insides_page)


import vcf
from bedcheck import *
import sys

def sites(infile, outfile, bedfile=''):

    numconf = 0
    allsites = 0
    nocall = 0
    out = open(outfile, 'w')
    v = vcf.VCFParser(infile)
    samples = v.samples
    samplestats = {}
    for s in samples:
        samplestats[s] = {'all':0, 'nc':0, 'lqref':0, 'lqsnp':0, 'confref':0, 'confsnp':0}
    if bedfile:
        bedregion = BedFileChecker(bedfile)
    for rec in v:
        v.parseinfo(rec)
        if rec.info.END:
            end = rec.info.END
        else:
            end = rec.pos
        v.parsegenotypes(rec)
        for p in range(rec.pos, end + 1):
            if bedfile and not bedregion.inbed(rec.chrom, p - 1):
                # subtract 1 when going from vcf to check in bed
                continue
            #print rec.chrom, rec.pos, rec.info.END, (rec.info.END - rec.pos + 1)
            for el in samples:
                s = getattr(rec, el)
                samplestats[el]['all'] += 1
                if s.GT == './.':
                    samplestats[el]['nc'] += 1
                    continue
                if s.GT == '0/0':
                    if s.DP == 0 or (s.GQ == 0 and s.DP <= 5):
                        samplestats[el]['nc'] += 1
                        continue
                    if s.GQ >= 30:
                        samplestats[el]['confref'] += 1
                    else:
                        samplestats[el]['lqref'] += 1
                else:
                    # variants
                    if s.DP == 0 or (s.GQ == 0 and s.DP <= 5):
                        samplestats[el]['nc'] += 1
                        continue
                    if s.GQ >= 30:
                        samplestats[el]['confsnp'] += 1
                    else:
                        samplestats[el]['lqsnp'] += 1

    hdr = '#sample all_sites conf_ref lq_ref conf_snp lq_snp no_call hq_fr lq_fr'.split()
    tmp = '\t'.join(hdr)
    out.write(tmp + '\n')
    for el in samples:
        ss = samplestats[el]
        tmp = [el, ss['all'], ss['confref'], ss['lqref'], ss['confsnp'], \
                        ss['lqsnp'], ss['nc'], (ss['confref'] + ss['confsnp'] * 1.0)/ss['all'],
                        (ss['lqref'] + ss['lqsnp'] + ss['nc']) * 1.0/ss['all']]
        tmp = [str(el) for el in tmp]
        tmp = '\t'.join(tmp)
        out.write(tmp + '\n')

if __name__=='__main__':

    infile = sys.argv[1]
    outfile = sys.argv[2]
    bedfile = sys.argv[3]
    sites(infile, outfile, bedfile)


#!/usr/bin/env python

import glob
import os
import pathlib
import shutil
from subprocess import call

class AlbumSplitter:
    def __init__(self, album) -> None:
        self.album = album
        self._cue_file = None
        self._flac_file = None
        self._get_cue_file()
        self._get_flac_file()

    def split(self):
        if not self._get_cue_file():
            return

        self._prepare_cue()
        self._convert_to_flac()
        self._split_flac()
        self._remove_flac()
        self._set_tags()
        self._sanitize_filenames()
        self._remove_trash()

    def _prepare_cue(self):
        """
        sed -i '1s/^\xEF\xBB\xBF//' *.cue
        """
        cue_file = self._get_cue_file()
        print('cue_file', cue_file)

        path = pathlib.Path(cue_file)
        text = path.read_text()
        text = text.replace('\xEF\xBB\xBF', '')
        path.write_text(text)

        return cue_file

    def _convert_to_flac(self):
        """
        sox *.wav file.flac
        sox *.wv file.flac
        shntool conv -o flac *.ape
        """
        wav = self._first('*.wav') or self._first('*.wv')
        if wav:
            self._run_cmd(['sox', '-S', wav, 'file.flac'])
        ape = self._first('*.ape')
        if ape:
            self._run_cmd(['shntool', 'conv', '-o', 'flac', ape])

    def _split_flac(self):
        """shnsplit -f *.cue -t '%n - %t' -o flac *flac"""
        self._run_cmd(['shnsplit', '-f', self._get_cue_file(), '-t', '%n - %t', '-o', 'flac', self._get_flac_file()])

    def _remove_flac(self):
        """
        find *.flac -not -regex '[0-9].*flac' -delete
        rm 00\ -\ pregap.flac
        """
        os.remove(self._flac_file)
        pregap_file = pathlib.Path(self.album, '00 - pregap.flac')
        if pregap_file.exists():
            os.remove(pregap_file)

    def _set_tags(self):
        """
        cuetag.sh *.cue *.flac
        """
        self._run_cmd(['cuetag.sh', self._get_cue_file()] + self._all('*.flac'))

    def _sanitize_filenames(self):
        """
        rename -v ':' '' *.flac
        rename -v '?' '' *.flac
        """
        for flac in self._all('*.flac'):
            newname = flac.replace(':', '')
            newname = newname.replace('?', '')
            if newname != flac:
                os.rename(flac, newname)

    def _remove_trash(self):
        """
        rm *.log
        rm -r Scans
        rm -r Covers
        rm *.ape
        rm *.cue
        rm *.wv
        rm *.wav
        """

        files_to_remove = ('*.log', '*.ape', '*.cue', '*.wv', '*.wav')
        for wildcard in files_to_remove:
            for file in self._all(wildcard=wildcard):
                os.remove(file)

        folders_to_remove = ('Scans', 'Covers', 'scans', 'covers', 'Artwork', 'artwork')
        for folder in folders_to_remove:
            shutil.rmtree(pathlib.Path(self.album, folder), ignore_errors=True)

    def _get_cue_file(self):
        if not self._cue_file:
            self._cue_file = self._first('*.cue')
        return self._cue_file

    def _get_flac_file(self):
        if not self._flac_file:
            self._flac_file = self._first('*.flac')
        return self._flac_file

    def _first(self, wildcard):
        files = self._all(wildcard=wildcard)
        return files[0] if files else None

    def _all(self, wildcard):
        items = glob.glob(f'{glob.escape(self.album)}/{wildcard}')
        items.sort()
        return items

    def _run_cmd(self, cmd):
        cmd = list(map(str, cmd))
        call(cmd, cwd=self.album)

class Split:
    def cur_dir(self):
        return pathlib.Path().resolve()

    def run(self):
        current_dir = self.cur_dir()
        self._process_folder(current_dir)

    def _process_folder(self, folder):
        subfolders = [f.path for f in os.scandir(folder) if f.is_dir()]
        subfolders.sort()
        for subfolder in subfolders:
            album_splitter = AlbumSplitter(album=pathlib.Path(folder, subfolder))
            album_splitter.split()
            self._process_folder(folder=subfolder)

Split().run()


"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
KINGDOMS/VIEWS.py
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

import json
import random

from django.shortcuts import render
from django.http import JsonResponse
from django.utils.safestring import mark_safe
from django.contrib.auth.decorators import user_passes_test

import common.utility as CU
import kingdoms.kingdoms as KK

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
REFERENCE
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

def kingdoms(request):
    kingdoms = KK.Reporter.GetKingdoms()
    #kingdoms = ['Amniss', 'Dee\'Ench', 'Azzroth', 'Fahanzel', 'Labuluu', 'Qsi Tesan']
    cKingdom = random.choice(kingdoms)
    props = KK.Reporter.GetKingdomProperties(cKingdom)
    deck = KK.Reporter.GetKingdomDeck(cKingdom)

    context = {
        'kingdoms': mark_safe(json.dumps(kingdoms)),
        'cKingdom': cKingdom,
        'props': mark_safe(json.dumps(props)),
        'deck': mark_safe(json.dumps(deck)),
    }
    return render(request, 'kingdoms.html', context)

def special_cards(request):
    context = {
        'topRanks': mark_safe(json.dumps({})),
    }
    return render(request, 'special_cards.html', context)

def game_rules(request):
    context = {
        'topRanks': mark_safe(json.dumps({})),
    }
    return render(request, 'game_rules.html', context)

def reference_jx(request, command):

    CU.prog_lg.info("ajax command: " + command)

    if command == 'refresh_kingdom':
        kingdom = request.GET.get('kingdom')
        props = KK.Reporter.GetKingdomProperties(kingdom)
        deck = KK.Reporter.GetKingdomDeck(kingdom)

        results = {
            'props': props,
            'deck': deck,
        }
        return JsonResponse(results, safe=False)

    else:
        msg = "command invalid: " + command
        CU.excp_lg.error(msg)
        return JsonResponse(msg, safe=False, status=404)

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
DATA MANAGER
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

def data_load(request):
    kingdomCards = KK.Importer.GetKingdomCards()
    cardReport = KK.Reporter.GetCardReport()

    context = {
        'kingdomCards': mark_safe(json.dumps(kingdomCards)),
        'cardReport': mark_safe(json.dumps(cardReport)),
    }
    return render(request, 'data_load.html', context)

def manager_jx(request, command):

    CU.prog_lg.info("ajax command: " + command)

    if command == 'import_kingdoms':
        KK.Importer.ImportKingdoms()
        results = KK.Importer.GetKingdomCards()
        return JsonResponse(results, safe=False)

    elif command == 'import_cards':
        KK.Importer.ImportCards()
        results = KK.Importer.GetKingdomCards()
        return JsonResponse(results, safe=False)

    elif command == 'clear_tables':
        KK.Importer.ClearTables()
        results = KK.Importer.GetKingdomCards()
        return JsonResponse(results, safe=False)

    else:
        msg = "command invalid: " + command
        CU.excp_lg.error(msg)
        return JsonResponse(msg, safe=False, status=404)

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
END OF FILE
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""


def longestPalindrome(self, s: str) -> str:

        def Palin(left : int, right :int) -> str :
            while left >= 0 and right < len(s) and s[left]==s[right] : # 영역에서 벗어나지 않고 팰린드롬인동안
                left-=1 # 영역 넓히기
                right+=1
            return s[left+1:right] # 어차피 right은 -1 까지만 나오므로 -1을 할 필요가 없음

        if len(s)<2 or s==s[::-1] : return s

        ans = ""
        for i in range(len(s)-1) :
            ans = max(ans,Palin(i,i+1),Palin(i,i+2),key = len)

        return ans


from django.shortcuts import render, redirect
from .models import Book, Auther

def fun(request):
    all_book = Book.objects.all()
    context = {
        'all_book': all_book,
    }
    return render(request, "index.html", context)

def fun2(request):
    all_aurther = Auther.objects.all()
    context = {
        'all_aurther': all_aurther,
    }
    return render(request, "authers.html", context)

def ADDBOOK(request):
    title = request.POST["title"]
    desc = request.POST["desc"]
    Book.objects.create(title=title, desc=desc)
    return redirect("/")

def ADDAUTHERS(request):
    fname = request.POST["fname"]
    lname = request.POST["lname"]
    note = request.POST["note"]
    Auther.objects.create(fname=fname, lname=lname, notes=note)
    return redirect("/authers")

def viewbook(request):
    all_aurther = Auther.objects.all()
    t = request.POST['title1']
    d = request.POST['desc1']
    i = request.POST['ID']
    context = {
        'title': t,
        'desc': d,
        'ID': i,
        'all_aurther': all_aurther,
    }

    return render(request, "viewbook.html", context)

def viewaurther(request):
    all_book = Book.objects.all()
    request.session['name']= request.POST['allname']
    name=request.session['name']
    request.session['Id'] = request.POST['ID']
    ID=request.session['Id']
    request.session['note'] = request.POST['note']
    note=request.session['note']
    context = {
        'name': name,
        'ID': ID,
        'note': note,
        'all_book': all_book,
    }
    return render(request, "viewaurther.html", context)

def assignBook(request):
    a=request.POST["select_book"]
    b=request.POST["ID"]
    this_book = Book.objects.get(id=a)
    this_aurther = Auther.objects.get(id=b)
    this_aurther.books.add(this_book)
    return redirect("/viewaruther")
    pass

def assignaurther(request):
    aurther=request.POST["select_auther"]
    id=request.POST["ID"]
    this_aurther=Auther.objects.get(id=aurther)
    this_book=Book.objects.get(id=id)
    this_book.authers.add(this_aurther)
    return redirect ('/viewbook')


from django.shortcuts import get_object_or_404
from django.http import JsonResponse

from rest_framework import viewsets

from .models import PropertyAddress, PropertyImage
from .serializers import PropertyAddressSerializer, PropertyImageSerializer, CreatePropertyAddressSerializer

from utils.convertAddress import reverseAddress, getCity

class PropertyAddressView(viewsets.ViewSet):

    def get_queryset(self):
        lat = self.request.query_params.get('lat')
        lng = self.request.query_params.get('lng')
        if lat is None or lng is None:
            queryset = PropertyAddress.objects.all()
        else:
            city = getCity(lat, lng)
            queryset = PropertyAddress.objects.filter(city=city)
        return queryset

    def list(self, request):
        queryset = self.get_queryset()
        serializer = PropertyAddressSerializer(queryset, many=True)
        return JsonResponse({'status': True, 'msg': 'Successfully retrieved categories', 'data': serializer.data})

    def retrieve(self, request, pk=None):
        queryset = PropertyAddress.objects.all()
        address = get_object_or_404(queryset, pk=pk)
        serializer = PropertyAddressSerializer(address)

        return JsonResponse({'status': True, 'data': serializer.data})

    def create(self, request):
        user = self.request.user
        lat = request.data['lat']
        lng = request.data['lng']
        data = reverseAddress(lat, lng)

        data['user'] = user.id

        if 'description' in request.data:
            data['property_description'] = request.data['description']

        address_serializer_class = CreatePropertyAddressSerializer(data=data)

        if address_serializer_class.is_valid():
            address_serializer_class.save()
            return JsonResponse(address_serializer_class.data, status=200)
        return JsonResponse(address_serializer_class.errors, status=400)

    def destroy(self, request, pk=None):
        queryset = PropertyAddress.objects.all()
        address = get_object_or_404(queryset, pk=pk)
        serializer = PropertyAddressSerializer(address)
        address.delete()
        return JsonResponse({'delete': True, 'data': serializer.data})

class PropertyImageView(viewsets.ViewSet):

    def list(self, request):
        queryset = PropertyImage.objects.all()
        image_serializer_class = PropertyImageSerializer(queryset, many=True)
        return JsonResponse({'status': True, 'msg': 'Succesfully retrived categories', 'data': image_serializer_class.data})

    def retrieve(self, request, pk=None):
        queryset = PropertyImage.objects.filter(propertyAddress=pk)
        image_serializer_class = PropertyImageSerializer(queryset, many=True)
        return JsonResponse({'status': True, 'data': image_serializer_class.data})

    def create(self, request):
        image_serializer_class = PropertyImageSerializer(data=request.data)
        if image_serializer_class.is_valid():
            image_serializer_class.save()
            return JsonResponse({'status': True, 'data': image_serializer_class.data}, status=200)
        return JsonResponse({'status': False, 'data': image_serializer_class.errors}, status=400)

    def destroy(self, request, pk=None):
        queryset = PropertyImage.objects.all()
        image = get_object_or_404(queryset, pk=pk)
        serializer = PropertyImageSerializer(image)
        image.delete()
        return JsonResponse({'delete': True, 'data': serializer.data})


n = int(input())
j = 1
stack = []
result = []

for _ in range(n):
    num = int(input())
    while j < num+1:
        stack.append(j)
        result.append('+')
        j += 1
    temp = stack.pop()
    result.append('-')
    if temp != num:
        print('NO')
        break
else:
    for re in result:
        print(re)


from odoo import api, fields, models, modules, tools

class IrActionsActWindow(models.Model):
    _inherit = "ir.actions.act_window"

    m2o_res_model = fields.Many2one(
        comodel_name="ir.model",
        string="Res Model",
        store=True,
        help="Res Model",
        ondelete="cascade",
    )

    m2o_src_model = fields.Many2one(
        comodel_name="ir.model",
        string="Src Model",
        store=True,
        help="Src Model",
        ondelete="cascade",
    )

    @api.onchange("m2o_res_model")
    def _onchange_m2o_res_model(self):
        if self.m2o_res_model:
            return dict(value=dict(res_model=self.m2o_res_model.model))

    @api.onchange("m2o_src_model")
    def _onchange_m2o_src_model(self):
        if self.m2o_src_model:
            return dict(value=dict(src_model=self.m2o_src_model.model))


import json
from typing import Any, List, Optional

from pydantic import HttpUrl, validator
from typing_extensions import Literal
import yamale
import yaml

from .base import FauxImmutableModel
from ..paths import FilepathType, LibraryPaths, ModulePaths

__all__ = ["Function", "Module"]

class Tag(FauxImmutableModel):
    # pylint: disable=too-few-public-methods
    name: str
    type: Literal["str", "int", "float"]

class Option(FauxImmutableModel):
    # pylint: disable=too-few-public-methods
    name: str
    type: Literal["str", "int", "float", "bool", "enum"]
    doctxt: str
    required: bool
    enum_values: Optional[List[Any]]
    default: Optional[Any]

    # pylint: disable=no-self-argument,no-self-use
    @validator("name", always=True)
    def _ensure_name_is_valid_identifier(cls, v):
        if not v.isidentifier():
            raise ValueError("option name has to be a valid python identifier")
        return v

    @validator("enum_values", always=True)
    def _check_enum_values_supplied_if_option_is_enum(cls, v, values):
        given_type = values.get("type")
        if given_type == "enum" and (not bool(v)):
            raise ValueError("enum_values required for enum option")
        return v

    @validator("default", always=True)
    def _check_default_is_supplied_if_option_not_required(cls, v, values):
        is_required = bool(values.get("required"))
        if (not is_required) and v is None:
            raise ValueError("default cannot be empty " "when required is False")
        return v

    @validator("default")
    def _cast_default_to_correct_type(cls, v, values):
        given_type = values.get("type")
        if given_type != "enum" and v is not None:
            v = {"str": str, "int": int, "float": float, "bool": bool}[given_type](v)
        return v

    @validator("default")
    def _check_default_is_one_of_enum_values(cls, v, values):
        enum_values = values.get("enum_values")
        if enum_values is not None and v not in enum_values:
            raise ValueError(
                f"permitted values for default: {enum_values} " f"[given: {v}]"
            )
        return v

    # pylint: enable=no-self-argument,no-self-use

class Function(FauxImmutableModel):
    name: str
    doctxt: str
    options: List[Option] = list()
    creates_new_files: bool
    modifies_input_files: bool
    expected_filetype: str
    optional_filetypes: Optional[List[str]]
    output_tags: Optional[List[Tag]]

    # pylint: disable=no-self-argument,no-self-use
    @validator("options", "output_tags", pre=True, always=True)
    def _init_list_if_not_supplied(cls, v):
        return v or list()

    @validator("expected_filetype")
    def _convert_to_lower_and_strip(cls, v):
        return v.lower().strip(" .")

    @validator("optional_filetypes")
    def _map_to_lower_and_strip(cls, v):
        return list(map(lambda x: x.lower().strip(" ."), v)) if v is not None else None

    # pylint: enable=no-self-argument,no-self-use

    def add_option(
        self,
        name: str,
        type: str,
        doctxt: str,
        required: bool,
        enum_values: Optional[List[Any]] = None,
        default: Optional[Any] = None,
    ) -> Option:
        # pylint: disable=redefined-builtin,too-many-arguments

        if name in {o.name for o in self.options}:
            raise RuntimeError(
                f"option {name} already exists " f"for function {self.name}"
            )

        o = Option(
            name=name,
            type=type,
            doctxt=doctxt,
            required=required,
            enum_values=enum_values,
            default=default,
        )

        self.options.append(o)
        return o

    def get_option(self, option_name: str) -> Option:
        filtered = [o for o in self.options if o.name == option_name]

        if len(filtered) == 0:
            raise ValueError(f'cannot find option "{option_name}"')

        return filtered.pop()

    def add_output_tag(self, name: str, type: str):
        # pylint: disable=redefined-builtin
        t = Tag(name=name, type=type)
        self.output_tags.append(t)

class Module(FauxImmutableModel):
    display_name: str
    tagline: str
    doctxt: str
    functions: List[Function] = list()
    icon_url: Optional[HttpUrl]

    # pylint: disable=no-self-argument,no-self-use
    @validator("functions", pre=True, always=True)
    def _init_list_if_not_supplied(cls, v):
        return v or list()

    @validator("icon_url")
    def _check_valid_image(cls, v):
        if v is not None:
            allowed_filetypes = [".jpg", ".jpeg", ".png", ".svg"]
            is_valid = any(v.endswith(ft) for ft in allowed_filetypes)
            if not is_valid:
                raise ValueError(
                    f"{v} is not a valid image URL, "
                    f"allowed file types: {allowed_filetypes}"
                )
        return v

    # pylint: enable=no-self-argument,no-self-use

    def build_function(
        self,
        name: str,
        doctxt: str,
        creates_new_files: bool,
        modifies_input_files: bool,
        expected_filetype: str,
        optional_filetypes: Optional[List[str]] = None,
    ) -> Function:
        # pylint: disable=too-many-arguments

        if name in {f.name for f in self.functions}:
            raise RuntimeError(f"function {name} already exists")

        f = Function(
            name=name,
            doctxt=doctxt,
            creates_new_files=creates_new_files,
            modifies_input_files=modifies_input_files,
            expected_filetype=expected_filetype,
            optional_filetypes=optional_filetypes,
        )

        self.functions.append(f)
        return f

    def get_function(self, function_name: str) -> Function:
        filtered = [f for f in self.functions if f.name == function_name]

        if len(filtered) == 0:
            raise RuntimeError(f'cannot find function "{function_name}"')

        return filtered.pop()

    def validate_with_schema(self, schema_path: Optional[FilepathType] = None):
        schema_path = schema_path or LibraryPaths().module_schema_file
        schema = yamale.make_schema(schema_path)
        data = [(self.dict(), self.__class__.__name__)]
        yamale.validate(schema, data, strict=True)

    def save(self, path: Optional[FilepathType] = None):
        self.validate_with_schema()

        path = path or ModulePaths().module_file

        data = self.dict()
        data["icon_url"] = str(data["icon_url"])

        with open(path, "w") as f:
            f.write(yaml.dump(data, sort_keys=False, default_flow_style=False))

    def serialize(self) -> str:
        data = self.dict()
        data["icon_url"] = str(data["icon_url"])
        return json.dumps(data)

    @classmethod
    def deserialize(cls, data: str) -> "Module":
        data = json.loads(data)
        return cls.parse_obj_and_validate(data)

    @classmethod
    def parse_obj_and_validate(cls, obj: Any) -> "Module":
        module = cls.parse_obj(obj)
        module.validate_with_schema()
        return module

    @classmethod
    def load(cls, path: Optional[FilepathType] = None) -> "Module":
        path = path or ModulePaths().module_file
        with open(path, "r") as f:
            data = yaml.safe_load(f.read())

        return cls.parse_obj_and_validate(data)

    @classmethod
    def build(
        cls,
        display_name: str,
        tagline: str,
        doctxt: str,
        icon_url: Optional[str] = None,
    ) -> "Module":

        data = {
            "display_name": display_name,
            "tagline": tagline,
            "doctxt": doctxt,
            "functions": list(),
            "icon_url": icon_url,
        }

        return cls(**data)


# I'm sure that this is not good practice; I've come across using setuptools / setup.py to do a local dev install
# (e.g. http://stackoverflow.com/questions/1893598/pythonpath-vs-sys-path), but I just don't have the time to figure
# out the intricacies of that mechanism, so proper deployment will have to fall to the wayside for now.
import sys
import os
import unittest

sys.path.insert(1, os.getcwd())
from crawler.utils import fake_response, match_class

class UtilTest(unittest.TestCase):
    """ Test module for Utils class """

    def test_match_class(self):
        result = match_class("class123")
        self.assertEqual(result, "*[contains(concat(' ', @class, ' '), ' class123 ')]")

    def test_fake_response(self):
        response = fake_response('tests/fixtures/example.html')
        self.assertEqual(200, response.status)
        self.assertEqual("http://www.bbc.co.uk", response.url)
        self.assertEqual([u"
Test
"], response.css('div').extract())

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


from django.http import JsonResponse
from django.utils import timezone

def api_endpoint(request):

    slack_name = request.GET.get('slack_name', 'Paschal Johnpaul')
    track = request.GET.get('track', 'backend')

    # current day of the week
    current_day = timezone.now().strftime("%A")

    #current UTC time
    utc_time = timezone.now().strftime("%Y-%m-%dT%H:%M:%SZ")

    # JSON response
    response_data = {
        "slack_name": slack_name,
        "current_day": current_day,
        "utc_time": utc_time,
        "track": track,
        "github_file_url": "https://github.com/Paschal-ike/projectZ/tree/main/myproject",
        "github_repo_url": "https://github.com/Paschal-ike/projectZ.git",
        "status_code": 200
    }

    return JsonResponse(response_data)


""" Mdule to parse CyberGhost plist """
import codecs
import datetime
import logging
import os
import riplib.ccl_bplist
from riplib.plugin import Plugin

__author__ = 'bolodev'
__version__ = '0.1'
__license__ = 'GPLv3'

class UsersCyberGhost(Plugin):
    """
    Parse information from /Users/{username}/Library/Preferences/com.cyberghostsrl.cyberghostmac.plist
    """

    def __init__(self):
        """
        Initialise the class.
        """
        super().__init__()
        self.set_name("User Cyber Ghost VPN Configuration")
        self.set_description("Parse information from /Users/{username}/Library/Preferences/com.cyberghostsrl.cyberghostmac.plist")
        self.set_data_file("com.cyberghostsrl.cyberghostmac.plist")
        self.set_output_file("")  # this will have to be defined per user account
        self.set_type("bplist")

    def parse(self):
        """
        Scan for the plist
        """
        users_path = os.path.join(self._input_dir, "Users")
        if os.path.isdir(users_path):
            user_list = os.listdir(users_path)
            for username in user_list:
                if os.path.isdir(os.path.join(users_path, username)) and not username == "Shared":
                    config = os.path.join(users_path, username, "Library", "Preferences", self._data_file)
                    if os.path.isfile(config):
                        self.__parse_plist(config, username)
                    else:
                        logging.warning("%s does not exist.", config)
                        print("[WARNING] {0} does not exist.".format(config))
        else:
            print("[WARNING] {0} does not exist.".format(users_path))

    def __parse_plist(self, file, username):
        """
        Parse /Users/{username}/Library/Preferences/com.cyberghostsrl.cyberghostmac.plist
        """
        with codecs.open(os.path.join(self._output_dir, "Users_" + username + "_VPN_CyberGhost.txt"), "a", encoding="utf-8") as output_file:
            output_file.write("="*10 + " " + self._name + " " + "="*10 + "\r\n")
            output_file.write("Source File: {0}\r\n\r\n".format(file))
            if os.path.isfile(file):
                bplist = open(file, "rb")
                plist = riplib.ccl_bplist.load(bplist)
                bplist.close()
                try:
                    if "Cyberghost_GUID" in plist:
                        output_file.write("Cyberghost GUID      : {0}\r\n".format(plist["Cyberghost_GUID"]))
                    if "startAtSystemStart" in plist:
                        output_file.write("Start at System Start: {0}\r\n".format(plist["startAtSystemStart"]))
                    if "SULastCheckTime" in plist:
                        output_file.write("SU Last Check Time   : {0}\r\n".format(plist["SULastCheckTime"]))
                    if "RandomPort" in plist:
                        output_file.write("Random Port          : {0}\r\n".format(plist["RandomPort"]))
                    if "useOpenVpnOverTcp" in plist:
                        output_file.write("Use OpenVpn Over Tcp : {0}\r\n".format(plist["useOpenVpnOverTcp"]))
                    if "Cyberghost_RemoteSettings" in plist:
                        output_file.write("Cyberghost Remote Settings:\r\n")
                        remote_settings = plist["Cyberghost_RemoteSettings"]
                        # instdate -> Unix Millisecond
                        date_foo = remote_settings["instdate"]
                        date_foo = datetime.datetime.fromtimestamp(date_foo/1000.0)
                        output_file.write("\tInstall Date           : {0}\r\n".format(date_foo))
                        output_file.write("\tLast Country           : {0}\r\n".format(remote_settings["LastCountry"]))
                        output_file.write("\tLast Server            : {0}\r\n".format(remote_settings["LastServer"]))
                        output_file.write("\tLast Logged In         : {0}\r\n".format(remote_settings["LastLoggedIn"]))
                        output_file.write("\tLas tPlan ID           : {0}\r\n".format(remote_settings["LastPlanID"]))
                        output_file.write("\tENC Connected Server ID: {0}\r\n".format(remote_settings["ENC_ConnectedServerID"]))
                        output_file.write("\tLast Plan Name         : {0}\r\n".format(remote_settings["LastPlanName"]))
                        output_file.write("\tStart Counter          : {0}\r\n".format(remote_settings["startCounter"]))
                except KeyError:
                    pass
            else:
                logging.warning("File: %s does not exist or cannot be found.\r\n", file)
                output_file.write("[WARNING] File: {0} does not exist or cannot be found.\r\n".format(file))
                print("[WARNING] File: {0} does not exist or cannot be found.\r\n".format(file))
            output_file.write("="*40 + "\r\n\r\n")
        output_file.close()