import json
from collections import OrderedDict

import yaml

from pcluster.config.iam_policy_rules import AWSBatchFullAccessInclusionRule, CloudWatchAgentServerPolicyInclusionRule
from pcluster.config.param_types import LOGGER, Param, Section, SettingsParam, StorageData, _ensure_section_existence
from pcluster.config.resource_map import ResourceMap
from pcluster.constants import PCLUSTER_ISSUES_LINK
from pcluster.utils import (
    InstanceTypeInfo,
    disable_ht_via_cpu_options,
    error,
    get_availability_zone_of_subnet,
    get_cfn_param,
    get_default_instance_type,
    get_ebs_snapshot_info,
    get_efs_mount_target_id,
    get_file_section_name,
    get_supported_architectures_for_instance_type,
)

# ---------------------- Params ---------------------- #
class CfnParam(Param):
    """Base class for configuration parameters using CloudFormation parameters as storage mechanism."""

    def from_storage(self, storage_params):
        """Load the param from the related storage data structure."""
        return self.from_cfn_params(storage_params.cfn_params)

    def to_storage(self, storage_params):
        """Write the param into the related storage data structure."""
        cfn_params = storage_params.cfn_params
        cfn_params.update(self.to_cfn())

    def from_cfn_params(self, cfn_params):
        """
        Initialize parameter value by parsing CFN input parameters.

        :param cfn_params: list of all the CFN parameters, used if "cfn_param_mapping" is specified in the definition
        """
        cfn_converter = self.definition.get("cfn_param_mapping", None)
        if cfn_params:
            cfn_value = get_cfn_param(cfn_params, cfn_converter) if cfn_converter else "NONE"
            self.value = self.get_value_from_string(cfn_value)

        return self

    def from_cfn_value(self, cfn_value):
        """
        Initialize parameter value by parsing from a given value coming from CFN.

        :param cfn_value: a value coming from a comma separated CFN param
        """
        self.value = self.get_value_from_string(cfn_value)

        return self

    def to_cfn(self):
        """Convert param to CFN representation, if "cfn_param_mapping" attribute is present in the Param definition."""
        cfn_params = {}
        cfn_converter = self.definition.get("cfn_param_mapping", None)

        if cfn_converter:
            cfn_value = self.get_cfn_value()
            cfn_params[cfn_converter] = str(cfn_value)

        return cfn_params

    def get_cfn_value(self):
        """
        Convert parameter value into CFN value.

        Used when the parameter must go into a comma separated CFN parameter.
        """
        return str(self.value if self.value is not None else self.definition.get("default", "NONE"))

class CommaSeparatedCfnParam(CfnParam):
    """Class to manage comma separated parameters. E.g. additional_iam_policies."""

    def from_file(self, config_parser):
        """
        Initialize parameter value from config_parser.

        :param config_parser: the configparser object from which get the parameter
        """
        section_name = get_file_section_name(self.section_key, self.section_label)
        if config_parser.has_option(section_name, self.key):
            config_value = config_parser.get(section_name, self.key)
            self.value = list(map(lambda x: x.strip(), config_value.split(",")))
            self._check_allowed_values()

        return self

    def get_string_value(self):
        """Convert internal representation into string."""
        return str(",".join(self.value)) if self.value else ""

    def get_value_from_string(self, string_value):
        """Return internal representation starting from string/CFN value."""
        param_value = self.get_default_value()

        if string_value and string_value != "NONE":
            param_value = list(map(lambda x: x.strip(), string_value.split(",")))

        return param_value

    def get_cfn_value(self):
        """
        Convert parameter value into CFN value.

        Used when the parameter must go into a comma separated CFN parameter.
        """
        return str(",".join(self.value) if self.value else self.definition.get("default", "NONE"))

    def get_default_value(self):
        """Get default value from the Param definition if there, empty list otherwise."""
        return self.definition.get("default", [])

class FloatCfnParam(CfnParam):
    """Class to manage float configuration parameters."""

    def from_file(self, config_parser):
        """
        Initialize parameter value from config_parser.

        :param config_parser: the configparser object from which get the parameter
        """
        section_name = get_file_section_name(self.section_key, self.section_label)
        if config_parser.has_option(section_name, self.key):
            try:
                self.value = config_parser.getfloat(section_name, self.key)
                self._check_allowed_values()
            except ValueError:
                self.pcluster_config.error("Configuration parameter '{0}' must be a Float".format(self.key))

        return self

    def get_value_from_string(self, string_value):
        """Return internal representation starting from CFN/user-input value."""
        param_value = self.get_default_value()

        try:
            if string_value is not None:
                string_value = str(string_value).strip()
                if string_value != "NONE":
                    param_value = float(string_value)
        except ValueError:
            self.pcluster_config.warn(
                "Unable to convert the value '{0}' to a Float. "
                "Using default value for parameter '{1}'".format(string_value, self.key)
            )

        return param_value

class BoolCfnParam(CfnParam):
    """Class to manage boolean configuration parameters."""

    def from_file(self, config_parser):
        """
        Initialize parameter value from config_parser.

        :param config_parser: the configparser object from which get the parameter
        """
        section_name = get_file_section_name(self.section_key, self.section_label)
        if config_parser.has_option(section_name, self.key):
            try:
                self.value = config_parser.getboolean(section_name, self.key)
                self._check_allowed_values()
            except ValueError:
                self.pcluster_config.error("Configuration parameter '{0}' must be a Boolean".format(self.key))

        return self

    def get_value_from_string(self, string_value):
        """Return internal representation starting from CFN/user-input value."""
        param_value = self.get_default_value()

        if string_value is not None:
            string_value = str(string_value).strip()
            if string_value != "NONE":
                param_value = string_value == "true"

        return param_value

    def get_string_value(self):
        """Convert internal representation into string."""
        return "NONE" if self.value is None else str(bool(self.value)).lower()

    def get_cfn_value(self):
        """
        Convert parameter value into CFN value.

        Used when the parameter must go into a comma separated CFN parameter.
        """
        return self.get_string_value()

class IntCfnParam(CfnParam):
    """Class to manage integer configuration parameters."""

    def from_file(self, config_parser):
        """
        Initialize param_value from config_parser.

        :param config_parser: the configparser object from which get the parameter
        """
        section_name = get_file_section_name(self.section_key, self.section_label)
        if config_parser.has_option(section_name, self.key):
            try:
                self.value = config_parser.getint(section_name, self.key)
                self._check_allowed_values()
            except ValueError:
                self.pcluster_config.error("Configuration parameter '{0}' must be an Integer".format(self.key))

        return self

    def get_value_from_string(self, string_value):
        """Return internal representation starting from CFN/user-input value."""
        param_value = self.get_default_value()
        try:
            if string_value is not None:
                string_value = str(string_value).strip()
                if string_value != "NONE":
                    param_value = int(string_value)
        except ValueError:
            self.pcluster_config.warn(
                "Unable to convert the value '{0}' to an Integer. "
                "Using default value for parameter '{1}'".format(string_value, self.key)
            )

        return param_value

class JsonCfnParam(CfnParam):
    """Class to manage json configuration parameters."""

    def from_file(self, config_parser):
        """
        Initialize parameter value from config_parser.

        :param config_parser: the configparser object from which get the parameter
        """
        section_name = get_file_section_name(self.section_key, self.section_label)
        if config_parser.has_option(section_name, self.key):
            config_value = config_parser.get(section_name, self.key)
            self.value = self.get_value_from_string(config_value)
            self._check_allowed_values()

        return self

    def get_value_from_string(self, string_value):
        """Return internal representation starting from CFN/user-input value."""
        param_value = self.get_default_value()
        try:
            # Do not convert empty string and use format and yaml.load in place of json.loads
            # for Python 2.7 compatibility because it returns unicode chars
            if string_value:
                string_value = str(string_value).strip()
                if string_value != "NONE":
                    param_value = yaml.safe_load(string_value)
        except Exception as e:
            self.pcluster_config.error("Error parsing JSON parameter '{0}'. {1}".format(self.key, e))

        return param_value

    def get_default_value(self):
        """Get default value from the Param definition, if there, {} otherwise."""
        return self.definition.get("default", {})

    def get_string_value(self):
        """
        Convert the internal representation into JSON.

        The keys in the Json are sorted alphabetically to produce a predictable and easy to test output.
        """
        return json.dumps(self.value, sort_keys=True)

    def get_cfn_value(self):
        """Convert parameter value into CFN value."""
        return self.get_string_value()

class ExtraJsonCfnParam(JsonCfnParam):
    """Class to manage extra_json configuration parameters."""

    def get_cfn_value(self):
        """
        Convert parameter value into CFN value.

        The extra_json configuration parameter can contain both "cfncluster" or "cluster" keys but cookbook
        recipes require "cfncluster" as key.
        """
        if self.value and "cluster" in self.value:
            self.value["cfncluster"] = self.value.pop("cluster")
        return self.get_string_value()

    def refresh(self):
        """
        Refresh the extra_jason.

        The extra_json configuration parameter can contain both "cfncluster" or "cluster" keys but
        we are using "cluster" in CLI to suggest the users the recommended syntax.
        """
        if self.value and "cfncluster" in self.value:
            self.value["cluster"] = self.value.pop("cfncluster")

class SharedDirCfnParam(CfnParam):
    """
    Class to manage the shared_dir configuration parameter.

    We need this class since the same CFN input parameter "SharedDir" is populated
    from the "shared" parameter of the cluster section (e.g. SharedDir = /shared)
    and the "shared" parameter of the ebs sections (e.g. SharedDir = /shared1,/shared2,NONE,NONE,NONE).
    """

    def to_cfn(self):
        """Convert parameter to CFN representation."""
        cfn_params = {}
        # if not using ebs_settings or
        # if only using 1 volume without specifying shared_dir in EBS section
        # use the shared_dir parameter in cluster section
        ebs_labels = self.pcluster_config.get_section("cluster").get_param_value("ebs_settings")
        if not ebs_labels or (
            len(ebs_labels.split(",")) == 1
            and not self.pcluster_config.get_section("ebs", ebs_labels).get_param_value("shared_dir")
        ):
            cfn_params[self.definition.get("cfn_param_mapping")] = self.get_cfn_value()
        # else: there are shared_dir specified in EBS sections
        # let the EBSSettings populate the SharedDir CFN parameter.
        return cfn_params

    def to_file(self, config_parser, write_defaults=False):
        """Set parameter in the config_parser only if the PclusterConfig object does not contains ebs sections."""
        # if not contains ebs_settings --> single SharedDir
        section_name = get_file_section_name(self.section_key, self.section_label)
        if not self.pcluster_config.get_section("ebs") and (write_defaults or self.value != self.get_default_value()):
            _ensure_section_existence(config_parser, section_name)
            config_parser.set(section_name, self.key, self.get_string_value())
        # else: there are ebs volumes, let the EBSSettings parse the SharedDir CFN parameter.

    def from_cfn_params(self, cfn_params):
        """
        Initialize param value by parsing CFN input only if the scheduler is a traditional one.

        When the SharedDir doesn't contain commas, it has been created from a single EBS volume
        specified through the shared_dir configuration parameter,
        if it contains commas, we need to create at least one ebs section.
        """
        if cfn_params:
            num_of_ebs = int(get_cfn_param(cfn_params, "NumberOfEBSVol"))
            if num_of_ebs == 1 and "," not in get_cfn_param(cfn_params, "SharedDir"):
                super(SharedDirCfnParam, self).from_cfn_params(cfn_params)

        return self

class SpotPriceCfnParam(FloatCfnParam):
    """
    Class to manage the spot_price configuration parameter.

    We need this class since the same CFN input parameter "SpotPrice" is populated
    from the "spot_bid_percentage" parameter when the scheduler is awsbatch and
    from "spot_price" when the scheduler is a traditional one.
    """

    def from_cfn_params(self, cfn_params):
        """Initialize param value by parsing CFN input only if the scheduler is a traditional one."""
        cfn_converter = self.definition.get("cfn_param_mapping", None)
        if cfn_converter and cfn_params:
            if get_cfn_param(cfn_params, "Scheduler") != "awsbatch":
                self.value = float(get_cfn_param(cfn_params, cfn_converter))

        return self

    def get_cfn_value(self):
        """
        Convert parameter value into CFN value.

        Insignificant trailing zeros removed to correctly match CloudFormation conditions using "0" as test value
        """
        return str("{0:g}".format(self.value) if self.value is not None else self.definition.get("default", "NONE"))

    def to_cfn(self):
        """Convert parameter to CFN representation."""
        cfn_params = {}

        cluster_config = self.pcluster_config.get_section(self.section_key)
        if cluster_config.get_param_value("scheduler") != "awsbatch":
            cfn_params[self.definition.get("cfn_param_mapping")] = self.get_cfn_value()

        return cfn_params

class SpotBidPercentageCfnParam(IntCfnParam):
    """
    Class to manage the spot_bid_percentage configuration parameter.

    We need this class since the same CFN input parameter "SpotPrice" is populated
    from the "spot_bid_percentage" parameter when the scheduler is awsbatch and
    from "spot_price" when the scheduler is a traditional one.
    """

    def from_cfn_params(self, cfn_params):
        """Initialize param value by parsing CFN input only if the scheduler is awsbatch."""
        cfn_converter = self.definition.get("cfn_param_mapping", None)
        if cfn_converter and cfn_params:
            if get_cfn_param(cfn_params, "Scheduler") == "awsbatch":
                # we have the same CFN input parameters for both spot_price and spot_bid_percentage
                # so the CFN input could be a float
                self.value = int(float(get_cfn_param(cfn_params, cfn_converter)))

        return self

    def to_cfn(self):
        """Convert parameter to CFN representation."""
        cfn_params = {}

        cluster_config = self.pcluster_config.get_section(self.section_key)
        if cluster_config.get_param_value("scheduler") == "awsbatch":
            cfn_params[self.definition.get("cfn_param_mapping")] = self.get_cfn_value()

        return cfn_params

class QueueSizeCfnParam(IntCfnParam):
    """
    Class to manage both the *_queue_size and *_vcpus configuration parameters.

    We need this class since the same CFN input parameter "*Size" is populated
    from the "*_vcpus" parameter when the scheduler is awsbatch and
    from "*_queue_size" when the scheduler is a traditional one.
    """

    def from_cfn_params(self, cfn_params):
        """Initialize param value by parsing the right CFN input according to the scheduler."""
        cfn_converter = self.definition.get("cfn_param_mapping", None)
        if cfn_converter and cfn_params:
            cfn_value = get_cfn_param(cfn_params, cfn_converter) if cfn_converter else "NONE"

            is_traditional_scheduler_param = get_cfn_param(cfn_params, "Scheduler") != "awsbatch" and (
                self.key == "initial_queue_size" or self.key == "max_queue_size"
            )
            is_awsbatch_param = get_cfn_param(cfn_params, "Scheduler") == "awsbatch" and (
                self.key == "desired_vcpus" or self.key == "max_vcpus" or self.key == "min_vcpus"
            )

            # initialize the value from cfn according to the scheduler
            if is_traditional_scheduler_param or is_awsbatch_param:
                self.value = self.get_value_from_string(cfn_value)

        return self

    def to_cfn(self):
        """Convert parameter to CFN representation."""
        cfn_params = {}

        cluster_config = self.pcluster_config.get_section(self.section_key)
        if (
            # traditional scheduler parameters
            cluster_config.get_param_value("scheduler") != "awsbatch"
            and (self.key == "initial_queue_size" or self.key == "max_queue_size")
        ) or (
            # awsbatch scheduler parameters
            cluster_config.get_param_value("scheduler") == "awsbatch"
            and (self.key == "desired_vcpus" or self.key == "max_vcpus" or self.key == "min_vcpus")
        ):
            cfn_value = cluster_config.get_param_value(self.key)
            cfn_params[self.definition.get("cfn_param_mapping")] = str(cfn_value)

        return cfn_params

class MaintainInitialSizeCfnParam(BoolCfnParam):
    """
    Class to manage the maintain_initial_size configuration parameters.

    We need this class since the same CFN input parameter "MinSize" is populated
    from the "min_vcpus" parameter when the scheduler is awsbatch and
    merging info from "initial_queue_size" and "maintain_initial_size" when the scheduler is a traditional one.
    """

    def from_cfn_params(self, cfn_params):
        """Initialize param value by parsing the right CFN input."""
        cfn_converter = self.definition.get("cfn_param_mapping", None)
        if cfn_converter and cfn_params:
            # initialize the value from cfn only if the scheduler is a traditional one
            if get_cfn_param(cfn_params, "Scheduler") != "awsbatch":
                # MinSize param > 0 means that maintain_initial_size was set to true at cluster creation
                min_size_cfn_value = get_cfn_param(cfn_params, cfn_converter) if cfn_converter else "0"
                min_size_value = int(min_size_cfn_value) if min_size_cfn_value != "NONE" else 0
                self.value = min_size_value > 0

        return self

    def to_cfn(self):
        """Convert parameter to CFN representation."""
        cfn_params = {}

        cluster_config = self.pcluster_config.get_section(self.section_key)
        if cluster_config.get_param_value("scheduler") != "awsbatch":
            cfn_value = cluster_config.get_param_value("maintain_initial_size")
            min_size_value = cluster_config.get_param_value("initial_queue_size") if cfn_value else "0"
            cfn_params.update({self.definition.get("cfn_param_mapping"): str(min_size_value)})

        return cfn_params

class AdditionalIamPoliciesCfnParam(CommaSeparatedCfnParam):
    """
    Class to manage the additional_iam_policies configuration parameters.

    We need this class for 2 reasons:
    * When the scheduler is awsbatch, we need to add/remove the AWSBatchFullAccess policy
      during CFN conversion.
    * When CloudWatch logging is enabled, we need to add/remove the CloudWatchAgentServerPolicy
      during CFN conversion.
    """

    policy_inclusion_rules = [CloudWatchAgentServerPolicyInclusionRule, AWSBatchFullAccessInclusionRule]

    def __init__(self, section_key, section_label, param_key, param_definition, pcluster_config, owner_section=None):
        super(AdditionalIamPoliciesCfnParam, self).__init__(
            section_key, section_label, param_key, param_definition, pcluster_config, owner_section
        )

    def get_string_value(self):
        """Convert internal representation into string. Conditionally enabled policies are not written."""
        non_conditional_iam_policies = self._non_conditional_iam_policies()
        return str(",".join(non_conditional_iam_policies)) if non_conditional_iam_policies else None

    def refresh(self):
        """Refresh the additional IAM policies by adding conditional policies, if needed."""
        additional_policies = set(self.value)
        for rule in AdditionalIamPoliciesCfnParam.policy_inclusion_rules:
            if rule.policy_is_required(self.pcluster_config) and rule.get_policy() not in additional_policies:
                additional_policies.add(rule.get_policy())
        self.value = sorted(additional_policies)

    def _non_conditional_iam_policies(self):
        """Given a list of IAM policies return a new list containing only the non conditional ones."""
        policies = set(self.value)  # List is cloned to avoid modifying self value
        for rule in self.policy_inclusion_rules:
            if rule.policy_is_required(self.pcluster_config):
                policies.discard(rule.get_policy())
        return sorted(policies)

class AvailabilityZoneCfnParam(CfnParam):
    """
    Base class to manage availability_zone internal attribute.

    This parameter is not exposed as configuration parameter in the file but it exists as CFN parameter
    and it is used for master_availability_zone and compute_availability_zone.
    """

    def _init_az(self, config_parser, subnet_parameter):
        section_name = get_file_section_name(self.section_key, self.section_label)
        if config_parser.has_option(section_name, subnet_parameter):
            subnet_id = config_parser.get(section_name, subnet_parameter)
            self.value = get_availability_zone_of_subnet(subnet_id)
            self._check_allowed_values()

    def to_file(self, config_parser, write_defaults=False):
        """Do nothing, because master_availability_zone it is an internal parameter, not exposed in the config file."""
        pass

class HeadNodeAvailabilityZoneCfnParam(AvailabilityZoneCfnParam):
    """
    Class to manage master_availability_zone internal attribute.

    This parameter is not exposed as configuration parameter in the file but it exists as CFN parameter
    and it is used during EFS conversion and validation.
    """

    def from_file(self, config_parser):
        """Initialize the Availability zone of the cluster by checking the head node Subnet."""
        self._init_az(config_parser, "master_subnet_id")

        return self

    def from_cfn_params(self, cfn_params):
        """Initialize the Availability zone by checking the head node subnet from cfn."""
        head_node_subnet_id = get_cfn_param(cfn_params, "MasterSubnetId")
        self.value = get_availability_zone_of_subnet(head_node_subnet_id)
        return self

class ComputeAvailabilityZoneCfnParam(AvailabilityZoneCfnParam):
    """
    Class to manage compute_availability_zone internal attribute.

    This parameter is not exposed as configuration parameter in the file but it exists as CFN parameter
    and it is used during EFS conversion and validation.
    """

    def from_file(self, config_parser):
        """Initialize the Availability zone of the cluster by checking the Compute Subnet."""
        self._init_az(config_parser, "compute_subnet_id")

        return self

    def from_cfn_params(self, cfn_params):
        """Initialize the Availability zone by checking the Compute Subnet from cfn."""
        compute_subnet_id = get_cfn_param(cfn_params, "ComputeSubnetId")
        self.value = get_availability_zone_of_subnet(compute_subnet_id)
        return self

class DisableHyperThreadingCfnParam(BoolCfnParam):
    """
    Class to manage the disable_hyperthreading configuration parameter.

    We need this class in order to convert the boolean disable_hyperthreading = [true/false] into Cores.
    """

    def from_cfn_params(self, cfn_params):
        """Initialize param value by parsing the right CFN input."""
        try:
            cfn_converter = self.definition.get("cfn_param_mapping", None)
            if cfn_converter and cfn_params:
                cores = get_cfn_param(cfn_params, cfn_converter)
                if cores and not cores.startswith("NONE,NONE"):
                    cores = int(cores.split(",")[0])
                    self.value = cores > 0
        except (ValueError, IndexError):
            self.pcluster_config.warn("Unable to parse Cfn Parameter Cores = {0}".format(cfn_params))

        return self

    @staticmethod
    def _get_cfn_params_for_instance_type(instance_type):
        """
        Return a pair describing whether or not to disable HT for the instance_type and how to do so.

        The first item in the pair is an integer representing a core count for the instance type when
        HT is disabled (or "NONE" if it shouldn't be disabled). The second item is a boolean expressing
        if HT should be disabled via CPU Options for the given instance type.
        """
        instance_type_info = InstanceTypeInfo.init_from_instance_type(instance_type)
        default_threads_per_core = instance_type_info.default_threads_per_core()
        if default_threads_per_core == 1:
            # no action is required to disable hyperthreading
            cores = "NONE"
        else:
            cores = instance_type_info.vcpus_count() // default_threads_per_core

        return cores, disable_ht_via_cpu_options(instance_type, default_threads_per_core)

    def to_cfn(self):
        """
        Define the Cores CFN parameter if disable_hyperthreading = true.

        :return: string (head_node_cores,compute_cores,head_node_supports_cpu_options,compute_supports_cpu_options)
        """
        cfn_params = {self.definition.get("cfn_param_mapping"): "NONE,NONE,NONE,NONE"}
        cluster_config = self.pcluster_config.get_section(self.section_key)
        if self.value:
            head_node_instance_type = cluster_config.get_param_value("master_instance_type")
            head_node_cores, disable_head_node_ht_via_cpu_options = self._get_cfn_params_for_instance_type(
                head_node_instance_type
            )

            if (
                self.pcluster_config.cluster_model.name == "SIT"
                and cluster_config.get_param_value("scheduler") != "awsbatch"
            ):
                # compute_instance_type parameter is valid only in SIT clusters
                compute_instance_type = cluster_config.get_param_value("compute_instance_type")
                compute_cores, disable_compute_ht_via_cpu_options = self._get_cfn_params_for_instance_type(
                    compute_instance_type
                )
            else:
                compute_instance_type = None
                compute_cores = 0
                disable_compute_ht_via_cpu_options = False

            for node_label, cores, instance_type in [
                ("master", head_node_cores, head_node_instance_type),
                ("compute", compute_cores, compute_instance_type),
            ]:
                if isinstance(cores, int) and cores < 0:
                    self.pcluster_config.error(
                        "For disable_hyperthreading, unable to get number of vcpus for {0} instance type {1}. "
                        "Please open an issue {2}".format(node_label, instance_type, PCLUSTER_ISSUES_LINK)
                    )
            cfn_params.update(
                {
                    self.definition.get("cfn_param_mapping"): "{0},{1},{2},{3}".format(
                        head_node_cores,
                        compute_cores,
                        str(disable_head_node_ht_via_cpu_options).lower(),
                        str(disable_compute_ht_via_cpu_options).lower(),
                    )
                }
            )

        return cfn_params

class ClusterConfigMetadataCfnParam(JsonCfnParam):
    """
    Class to manage configuration metadata.

    Uses an internal ResourceMap to manage section labels in order to ensure proper correspondence between the section
    labels and their corresponding CloudFormation resources.
    """

    def _from_definition(self):
        self.value = self.get_default_value()
        self.__section_resources = ResourceMap(self.value.get("sections"))

    def from_cfn_params(self, cfn_params):
        """Initialize parameter by parsing CFN parameters."""
        super(JsonCfnParam, self).from_cfn_params(cfn_params)
        self.__section_resources = ResourceMap(self.value.get("sections"))
        return self

    def __store_section_labels(self, section_key):
        """Store all the section labels corresponding to the provided section key into the internal ResourceMap."""
        section = self.pcluster_config.get_section(section_key)
        if section and section.has_metadata():
            labels = [section.label for section in self.pcluster_config.get_sections(section.key).values()]

            if not self.__section_resources.resources(section.key):
                # Allocating more resources than allowed is fine here. Integrity check is performed by validation.
                self.__section_resources.alloc(section.key, max(section.max_resources, len(labels)))

            self.__section_resources.store(section.key, labels)

    def get_cfn_value(self):
        """Get value to store in CloudFormation."""
        return self.get_string_value()

    def get_default_value(self):
        """Get default value from the Param definition."""
        return self.definition.get("default", {"sections": {}})

    def refresh(self):
        """
        Refresh the configuration metadata.

        The operation is done by storing all section labels of the parent PClusterConfig instance into the internal
        ResourceMap and then setting them back into the "sections" key of the parameter's Json value.
        This allows the original order of the labels to be maintained when the configuration has been loaded from cfn.
        """
        for section_key in self.pcluster_config.get_section_keys():
            self.__store_section_labels(section_key)
        self.value["sections"] = self.__section_resources.resources()

    def get_section_resources(self, section_key):
        """Get the resources linked to a specific section key in the configuration metadata."""
        return self.__section_resources.resources(section_key)

    def create_section_resources(self, section_key, num_labels, max_resources):
        """
        Create automatic section resource labels for the provided section key.

        :param section_key The section key
        :param num_labels The number of labels to create
        :param max_resources The max number of resources to allocate for this section key
        """
        self.__section_resources.alloc(section_key, max_resources)
        section_labels = ["{0}{1}".format(section_key, str(index + 1)) for index in range(num_labels)]
        self.__section_resources.store(section_key, section_labels)
        LOGGER.debug("Automatic labels generated: {0}".format(str(section_labels)))
        # Refresh param value
        self.__resources_to_value()
        return self.__section_resources.resources(section_key)

    def __resources_to_value(self):
        """Sync data from internal resources structure to param value."""
        self.value["sections"] = self.__section_resources.resources()

class BaseOSCfnParam(CfnParam):
    """
    Class to manage the base_os configuration parameter.

    We need this class in order to initialize the private architecture param.
    """

    @staticmethod
    def get_instance_type_architecture(instance_type):
        """Compute cluster's 'Architecture' CFN parameter based on its head node instance type."""
        if not instance_type:
            error("Cannot infer architecture without head node instance type")
        head_node_supported_architectures = get_supported_architectures_for_instance_type(instance_type)

        if not head_node_supported_architectures:
            error("Unable to get architectures supported by instance type {0}.".format(instance_type))
        # If the instance type supports multiple architectures, choose the first one.
        # TODO: this is currently not an issue because none of the instance types we support more than one of the
        #       architectures we support. If this were ever to change (e.g., we start supporting i386) then we would
        #       probably need to choose based on the subset of the architecutres supported by both the head node and
        #       compute instance types.
        return head_node_supported_architectures[0]

    def refresh(self):
        """Initialize the private architecture param."""
        if self.value:
            head_node_instance_type = self.owner_section.get_param_value("master_instance_type")
            architecture = self.get_instance_type_architecture(head_node_instance_type)
            self.owner_section.get_param("architecture").value = architecture

class ArgsCfnParam(CfnParam):
    """
    Class to manage the pre/post_install_args configuration parameter.

    We need this class in order to escape the args to json param.
    """

    def from_cfn_params(self, cfn_params):
        """
        Initialize parameter value by parsing CFN input parameters.

        :param cfn_params: list of all the CFN parameters, used if "cfn_param_mapping" is specified in the definition
        """
        cfn_converter = self.definition.get("cfn_param_mapping", None)
        if cfn_params:
            cfn_value = get_cfn_param(cfn_params, cfn_converter) if cfn_converter else "NONE"
            self.value = self.get_value_from_string(json.loads('"' + cfn_value + '"'))

        return self

    def to_cfn(self):
        """Convert param to CFN representation, if "cfn_param_mapping" attribute is present in the Param definition."""
        cfn_params = {}
        cfn_converter = self.definition.get("cfn_param_mapping", None)

        if cfn_converter:
            cfn_value = self.get_cfn_value()
            # use index to trim the redundant quotes, for example, config file "R wget" -> cfn_value = '"R wget"'
            # after json escape, cfn_value = '"/"R wget/""' -> we need '/"R wget/"' for pre/post install args
            cfn_params[cfn_converter] = json.dumps(cfn_value)[1:-1]

        return cfn_params

class ComputeInstanceTypeCfnParam(CfnParam):
    """
    Class to manage the compute instance type parameter.

    We need this class in order to set the default instance type from a boto3 call.
    """

    def refresh(self):
        """Get default value from a boto3 call for free tier instance type."""
        if not self.value:
            scheduler = self.pcluster_config.get_section("cluster").get_param_value("scheduler")
            if scheduler:
                self.value = "optimal" if scheduler == "awsbatch" else get_default_instance_type()

class HeadNodeInstanceTypeCfnParam(CfnParam):
    """
    Class to manage the head node instance type parameter.

    We need this class in order to set the default instance type from a boto3 call.
    """

    def refresh(self):
        """Get default value from a boto3 call for free tier instance type."""
        if not self.value:
            self.value = get_default_instance_type()

class TagsParam(JsonCfnParam):
    """
    Class to manage the tags json configuration parameter.

    Tags are stored in CFN as a separate field instead of inside parameters in CFN.
    Therefore, we need to overwrite the from_storage function
    """

    def from_storage(self, storage_params):
        """Load the param from the related storage data structure."""
        return self.from_cfn_tag(storage_params.cfn_tags)

    def from_cfn_tag(self, cfn_tags):
        """Initialize custom tags."""
        if cfn_tags:
            for tag in cfn_tags:
                key = tag.get("Key")
                if key == "Version":
                    # Skip default "Version" tag
                    continue
                self.value[key] = tag.get("Value")
        return self

# ---------------------- SettingsParams ---------------------- #
class SettingsCfnParam(SettingsParam):
    """Class to manage *_settings parameter on which the value is a single value (e.g. vpc_settings = default)."""

    def from_storage(self, storage_params):
        """Initialize section configuration parameters referred by the settings value by parsing CFN parameters."""
        self.value = self.get_default_value()
        section_labels = self.get_metadata_labels()
        label = section_labels[0] if section_labels else None

        # Section if rebuilt only if section label is found in cluster metadata
        if label:
            if storage_params:
                section = self.referred_section_type(
                    self.referred_section_definition, self.pcluster_config, section_label=label
                ).from_storage(storage_params)
                self._replace_default_section(section)

        return self

    def to_storage(self, storage_params):
        """Convert the referred section to CFN representation."""
        section_labels = self.get_metadata_labels()

        if self.referred_section_definition.get("max_resources", 1) > 1:
            # Multiple section
            for section_label in [section_label for section_label in section_labels if section_label is not None]:
                section = self.pcluster_config.get_section(self.referred_section_key, section_label.strip())
                section.to_storage(storage_params)
        else:
            # Single section
            section = self.pcluster_config.get_section(self.referred_section_key, self.value)
            if not section:
                # Create a default section to populate with default values (e.g. NONE)
                section = self.referred_section_type(self.referred_section_definition, self.pcluster_config)

            section.to_storage(storage_params)

    def get_metadata_labels(self, expected_num_labels=0, include_none_values=True):
        """
        Return the sections labels of this Settings param respecting their order in the cluster config metadata.

        If the config metadata has been restored from CloudFormation, for instance in the context of a pcluster update
        operation, this method ensures that the labels already stored in metadata will maintain their positions, so that
        their correspondence with the existing CloudFormation resources is respected.
        Important: calling this method is safe from all stages of configuration loading / writing  because the
        CloudFormationMetadata parameter is the first to be loaded.

        :param expected_num_labels: number of expected labels
        :param include_none_values: Include None values in the result
        :returns: The labels list as stored in config metadata
        """
        metadata = self.pcluster_config.get_section("cluster").get_param("cluster_config_metadata")
        section_labels = metadata.get_section_resources(self.referred_section_key)
        if not section_labels:
            section_labels = self.value.split(",") if self.value else None

        # Automatic section labels creation if no metadata information is found.
        # This condition is expected in two cases:
        #   1) when loading sections from cfn which where not present in the original config file (these sections will
        #      contain all default parameter values)
        #   2) in unit tests that build the configuration on the fly
        if not section_labels:
            max_resources = int(self.referred_section_definition.get("max_resources", "1"))
            section_labels = metadata.create_section_resources(
                self.referred_section_key, expected_num_labels, max_resources
            )

        if not include_none_values:
            section_labels = [label for label in section_labels if label is not None]

        return section_labels

class EBSSettingsCfnParam(SettingsCfnParam):
    """
    Class to manage ebs_settings parameter.

    We require a specific class for EBS settings because multiple parameters from multiple sections
    are merged together to create CFN parameters.
    Furthermore, as opposed to SettingsParam, the value can be a comma separated value (e.g. ebs_settings = ebs1,ebs2).
    """

    def from_storage(self, storage_params):
        """Init ebs section only if there are more than one ebs (the default one)."""
        labels = []
        if storage_params.cfn_params:
            cfn_params = storage_params.cfn_params
            # FIXME: remove NumberOfEBSVol Parameter and use EBS section params to manage EBS Volumes in cfn template
            # fix ebs substack - this code can be reused for back compatibility
            num_of_ebs = int(get_cfn_param(cfn_params, "NumberOfEBSVol"))
            if num_of_ebs >= 1 and "ebs" in get_cfn_param(cfn_params, "ClusterConfigMetadata"):
                # If "ebs" is in "ClusterConfigMetadata", there are at least one ebs section configured
                labels = self.get_metadata_labels(expected_num_labels=num_of_ebs, include_none_values=False)
                for index in range(len(labels)):
                    # create empty section
                    referred_section_type = self.referred_section_definition.get("type", CfnSection)
                    referred_section = referred_section_type(
                        self.referred_section_definition, self.pcluster_config, labels[index]
                    )

                    for param_key, param_definition in self.referred_section_definition.get("params").items():
                        cfn_converter = param_definition.get("cfn_param_mapping", None)
                        if cfn_converter:

                            param_type = param_definition.get("type", CfnParam)
                            cfn_value = get_cfn_param(cfn_params, cfn_converter).split(",")[index]
                            param = param_type(
                                referred_section.key,
                                referred_section.label,
                                param_key,
                                param_definition,
                                self.pcluster_config,
                            ).from_cfn_value(
                                None
                                if param_key == "shared_dir" and "," not in get_cfn_param(cfn_params, "SharedDir")
                                else cfn_value
                            )
                            # When the SharedDir doesn't contain commas, it has been created from a single EBS volume
                            # specified through the shared_dir configuration parameter in the cluster section
                            # If SharedDir contains comma, we need to create at least one ebs section
                            referred_section.add_param(param)

                    self.pcluster_config.add_section(referred_section)

        self.value = ",".join(labels) if labels else None

        return self

    def to_file(self, config_parser, write_defaults=False):
        """Convert the param value into a list of sections in the config_parser and initialize them."""
        sections = []
        if self.value:
            for section_label in self.value.split(","):
                sections.append(self.pcluster_config.get_section(self.referred_section_key, section_label.strip()))

        if sections:
            section_name = get_file_section_name(self.section_key, self.section_label)
            # add "*_settings = *" to the parent section
            _ensure_section_existence(config_parser, section_name)
            config_parser.set(section_name, self.key, self.get_string_value())

            # create sections
            for section in sections:
                section.to_file(config_parser)

    def to_storage(self, storage_params):
        """Convert a list of sections to multiple CFN params."""
        sections = OrderedDict({})

        # Section labels are retrieved from configuration metadata rather than directly from the ebs_settings param
        # to respect the original order of Cfn resources
        section_labels = self.get_metadata_labels()

        for section_label in [section_label for section_label in section_labels if section_label is not None]:
            section = self.pcluster_config.get_section(self.referred_section_key, section_label.strip())
            sections[section_label] = section

        cfn_params = storage_params.cfn_params
        number_of_ebs_sections = len(sections)
        for param_key, param_definition in self.referred_section_definition.get("params").items():
            if param_key == "shared_dir":
                # The same CFN parameter is used for both single and multiple EBS cases
                # if there are no EBS volumes, or if user does not specify shared_dir when using 1 EBS volume
                # let the SharedDirParam populate the "SharedDir" CFN parameter.
                if number_of_ebs_sections == 0 or (
                    number_of_ebs_sections == 1 and not next(iter(sections.values())).get_param_value("shared_dir")
                ):
                    continue

            cfn_converter = param_definition.get("cfn_param_mapping", None)
            if cfn_converter:

                cfn_value_list = []
                for section_label in section_labels:
                    if section_label:
                        section = sections[section_label]
                        param = section.get_param(param_key)
                    else:
                        # Create a default param
                        param_type = param_definition.get("type", CfnParam)
                        param = param_type(
                            self.referred_section_key, "default", param_key, param_definition, self.pcluster_config
                        )
                    cfn_value_list.append(param.to_cfn().get(cfn_converter))

                cfn_value = ",".join(cfn_value_list)
                cfn_params[cfn_converter] = cfn_value

        # We always have at least one EBS volume
        # FIXME: remove NumberOfEBSVol Parameter and use EBS section params to manage EBS Volumes in cfn template
        cfn_params["NumberOfEBSVol"] = str(max(number_of_ebs_sections, 1))

class NetworkInterfacesCountCfnParam(CommaSeparatedCfnParam):
    """
    Class to manage NetworkInterfacesCount Cfn param.

    The internal value is a list of two items, which respectively indicate the number of network interfaces to activate
    on head node and compute nodes.
    """

    def refresh(self):
        """Compute the number of network interfaces for head node and compute nodes."""
        cluster_section = self.pcluster_config.get_section("cluster")
        scheduler = cluster_section.get_param_value("scheduler")
        compute_instance_type = (
            cluster_section.get_param_value("compute_instance_type")
            if self.pcluster_config.cluster_model.name == "SIT" and scheduler != "awsbatch"
            else None
        )
        self.value = [
            str(
                InstanceTypeInfo.init_from_instance_type(
                    cluster_section.get_param_value("master_instance_type")
                ).max_network_interface_count()
            ),
            str(InstanceTypeInfo.init_from_instance_type(compute_instance_type).max_network_interface_count())
            if compute_instance_type
            else "1",
        ]

# ---------------------- Sections ---------------------- #
class CfnSection(Section):
    """Class to manage configuration sections with storage persistence in CloudFormation."""

    def from_storage(self, storage_params):
        """Initialize section configuration parameters by parsing CFN parameters."""
        cfn_converter = self.definition.get("cfn_param_mapping", None)
        if cfn_converter:
            # It is a section converted to a single CFN parameter
            cfn_values = get_cfn_param(storage_params.cfn_params, cfn_converter).split(",")

            cfn_param_index = 0
            for param_key, param_definition in self.definition.get("params").items():
                try:
                    cfn_value = cfn_values[cfn_param_index]
                except IndexError:
                    # This happen if the expected comma separated CFN param doesn't exist in the Stack,
                    # so it is set to a single NONE value
                    cfn_value = "NONE"

                param_type = param_definition.get("type", CfnParam)
                param = param_type(
                    self.key, self.label, param_key, param_definition, self.pcluster_config, owner_section=self
                ).from_cfn_value(cfn_value)

                self.add_param(param)
                cfn_param_index += 1
        else:
            for param_key, param_definition in self.definition.get("params").items():
                param_type = param_definition.get("type", CfnParam)
                param = param_type(
                    self.key, self.label, param_key, param_definition, self.pcluster_config, owner_section=self
                ).from_storage(storage_params)
                self.add_param(param)

        return self

    def to_storage(self, storage_params=None):
        """
        Convert section to CFN representation.

        The section is converted to a single CFN parameter if "cfn_param_mapping" is present in the Section definition,
        otherwise each parameter of the section will be converted to the respective CFN parameter.
        """
        if not storage_params:
            storage_params = StorageData({}, {})

        cfn_converter = self.definition.get("cfn_param_mapping", None)
        if cfn_converter:
            # it is a section converted to a single CFN parameter
            cfn_items = []
            for param_key, param_definition in self.definition.get("params").items():
                param = self.get_param(param_key)
                if param:
                    cfn_items.append(param.get_cfn_value())
                else:
                    param_type = param_definition.get("type", CfnParam)
                    param = param_type(self.key, self.label, param_key, param_definition, self.pcluster_config)
                    cfn_items.append(param.get_cfn_value())

            if cfn_items[0] == "NONE":
                # empty dict or first item is NONE --> set all values to NONE
                cfn_items = ["NONE"] * len(self.definition.get("params"))

            storage_params.cfn_params[cfn_converter] = ",".join(cfn_items)
        else:
            # get value from config object
            for param_key, param_definition in self.definition.get("params").items():
                param = self.get_param(param_key)
                if param:
                    param.to_storage(storage_params)
                else:
                    # set CFN value from a default param
                    param_type = param_definition.get("type", Param)
                    param = param_type(self.key, self.label, param_key, param_definition, self.pcluster_config)
                    param.to_storage(storage_params)

        return storage_params

    def get_default_param_type(self):
        """Get the default Param type managed by the Section type."""
        return CfnParam

class EFSCfnSection(CfnSection):
    """
    Class to manage [efs ...] section.

    We need to define this class because during the CFN conversion it is required to perform custom actions.
    """

    def to_storage(self, storage_params=None):
        """
        Convert section to CFN representation.

        In addition to the conversion of the parameter contained in the section definition,
        it also add a final value in the CFN param that identifies if exists or not
        a valid Mount Target for the given EFS FS Id.
        """
        if not storage_params:
            storage_params = StorageData({}, {})
        cfn_params = storage_params.cfn_params
        cfn_converter = self.definition.get("cfn_param_mapping", None)

        cfn_items = []
        for param_key, param_definition in self.definition.get("params").items():
            param = self.get_param(param_key)
            if param:
                cfn_items.append(param.get_cfn_value())
            else:
                param_type = param_definition.get("type", CfnParam)
                param = param_type(self.key, self.label, param_key, param_definition, self.pcluster_config)
                cfn_items.append(param.get_cfn_value())

        if cfn_items[0] == "NONE":
            head_node_mt_valid = False
            compute_mt_valid = False
            head_node_avail_zone = "fake_az1"
            compute_avail_zone = "fake_az2"
            # empty dict or first item is NONE --> set all values to NONE
            cfn_items = ["NONE"] * len(self.definition.get("params"))
        else:
            # add another CFN param that will identify if create or not a Mount Target for the given EFS FS Id
            head_node_avail_zone = self.pcluster_config.get_head_node_availability_zone()
            head_node_mount_target_id = get_efs_mount_target_id(
                efs_fs_id=self.get_param_value("efs_fs_id"), avail_zone=head_node_avail_zone
            )
            compute_avail_zone = self.pcluster_config.get_compute_availability_zone()
            compute_mount_target_id = get_efs_mount_target_id(
                efs_fs_id=self.get_param_value("efs_fs_id"), avail_zone=compute_avail_zone
            )
            head_node_mt_valid = bool(head_node_mount_target_id)
            compute_mt_valid = bool(compute_mount_target_id)

        cfn_items.append("Valid" if head_node_mt_valid else "NONE")
        # Do not create additional compute mount target if compute and head node subnet in the same AZ
        cfn_items.append("Valid" if compute_mt_valid or (head_node_avail_zone == compute_avail_zone) else "NONE")
        cfn_params[cfn_converter] = ",".join(cfn_items)

        return storage_params

class ClusterCfnSection(CfnSection):
    """
    Class to manage [cluster ...] section.

    We need to define this class because during the CFN conversion it is required to manage another CFN param
    that identifies the label in the template.
    """

    def from_storage(self, storage_params):
        """Initialize section configuration parameters by parsing CFN parameters."""
        if storage_params:
            super(ClusterCfnSection, self).from_storage(storage_params)

        # Update section label from metadata
        section_metadata = self.get_param_value("cluster_config_metadata").get("sections").get("cluster")
        self.label = section_metadata[0] if section_metadata else "default"

        return self

    def to_storage(self, storage_params=None):
        """Convert section to CFN representation."""
        if not storage_params:
            storage_params = StorageData({}, {})

        super(ClusterCfnSection, self).to_storage(storage_params)
        return storage_params

class VolumeSizeParam(IntCfnParam):
    """Class to manage ebs volume_size parameter."""

    def refresh(self):
        """
        We need this method to check whether the user have an input on ebs volume_size.

        If the volume_size is not specified by an user, we will create an EBS volume with default volume size. The
        default volume size would be 20 if it is not created from a specified snapshot, otherwise it will be equal to
        the size of the specified EBS snapshot.
        """
        section = self.pcluster_config.get_section(self.section_key, self.section_label)
        if section and section.get_param_value("volume_size") is None:
            if section.get_param_value("ebs_snapshot_id"):
                ebs_snapshot_id = section.get_param_value("ebs_snapshot_id")
                default_volume_size = get_ebs_snapshot_info(ebs_snapshot_id).get("VolumeSize")
            else:
                default_volume_size = 500 if section.get_param_value("volume_type") in ["st1", "sc1"] else 20
            self.value = default_volume_size

class VolumeIopsParam(IntCfnParam):
    """Class to manage ebs volume_iops parameter in the EBS section."""

    EBS_VOLUME_TYPE_IOPS_DEFAULT = {
        "io1": 100,
        "io2": 100,
        "gp3": 3000,
    }

    def refresh(self):
        """
        We need this method to set different default value for ebs IOPS for different volume.

        Check whether the user have an input on ebs volume_iops when specify volume type to be "gp3".
        For "gp3", the default iops is 3000. For other volumes, the default iops are 100. If the volume_iops is not
        specified by an user, we will create an EBS volume with default volume
        iops.
        """
        section = self.pcluster_config.get_section(self.section_key, self.section_label)

        if section and section.get_param_value("volume_iops") is None:
            volume_type = section.get_param_value("volume_type")
            if volume_type in VolumeIopsParam.EBS_VOLUME_TYPE_IOPS_DEFAULT:
                default_iops = VolumeIopsParam.EBS_VOLUME_TYPE_IOPS_DEFAULT.get(volume_type)
                self.value = default_iops


import unittest

from scoring.component_parameters import ComponentParameters
from scoring.score_components import TanimotoSimilarity
from unittest_reinvent.scoring_tests.scoring_components import ScoringTest
from utils.enums.scoring_function_component_enum import ScoringFunctionComponentNameEnum
import numpy.testing as npt

class Test_tanimoto_similarity(ScoringTest, unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        enum = ScoringFunctionComponentNameEnum()
        parameters = ComponentParameters(component_type=enum.TANIMOTO_SIMILARITY,
                                         name="tanimoto_similarity",
                                         weight=1.,
                                         smiles=["CCC", "O=S(=O)(c3ccc(n1nc(cc1c2ccc(cc2)C)C(F)(F)F)cc3)N"],
                                         model_path="",
                                         specific_parameters={})
        cls.component = TanimotoSimilarity(parameters)

    def test_similarity_1(self):
        npt.assert_almost_equal(self.score("CCC"), 1.0)

    def test_similarity_2(self):
        npt.assert_almost_equal(self.score("O=S(=O)(c3ccc(n1nc(cc1c2ccc(cc2)C)C(F)(F)F)cc3)N"), 1.0)

    def test_similarity_3(self):
        npt.assert_array_less(self.score("Cn1cc(c([NH])cc1=O)"), 0.5)

    def test_similarity_4(self):
        smiles = ["CCC", "O=S(=O)(c3ccc(n1nc(cc1c2ccc(cc2)C)C(F)(F)F)cc3)N"]
        scores = self.multiple_scores(smiles)
        npt.assert_almost_equal(scores, 1.0,)


from manimlib import *

"""
这段代码绝对的传奇：
在稿纸上画出草图，通过gpt4的识图功能，生成manim代码

x轴和f(x)之间有一些直线
当f(x)变成g(x)的时候，这些直线的高度也会变化

实现f(x)到g(x)的变化很简单
在此过程中，直线高度的变化给人直观上是因果关系
但是实现的时候，因果关系很难实现
所以，可以转向并行实现
一旦转向并行实现，就需要控制每一帧的直线的高度
这个时候，就需要使用UpdateFromAlphaFunc类
"""
class test(Scene):
    def construct(self):
        # Define axes
        axes = Axes(
            x_min=-3, x_max=3,
            y_min=-1, y_max=9,
            axis_config={"color": BLUE}
        )

        # Define functions
        def f(x):
            return x**2

        def g(x):
            return -1* x**2 + 1

        # Create curves for f(x) and g(x)
        graph_f = axes.get_graph(f, color=BLUE)
        graph_g = axes.get_graph(g, color=GREEN)

        # Create labels for f(x) and g(x)
        graph_label_f = axes.get_graph_label(graph_f, label='f(x)')
        graph_label_g = axes.get_graph_label(graph_g, label='g(x)')

        # Create vertical lines to the x-axis
        lines = VGroup(*[
            Line(
                start=axes.c2p(x, 0),
                end=axes.c2p(x, f(x)),
                color=RED
            ) for x in np.arange(-3, 3, 0.5)
        ])

        # Update function for the lines
        def update_lines(group, alpha):
            for line, x in zip(group, np.arange(-3, 3, 0.5)):
                y = interpolate(f(x), g(x), alpha)
                new_end = axes.c2p(x, y)
                line.put_start_and_end_on(line.get_start(), new_end)

        # Draw axes and initial plot with lines
        self.add(axes, graph_f, graph_label_f, lines)

        # Transform f(x) to g(x) and update lines
        self.play(
            Transform(graph_f, graph_g),
            Transform(graph_label_f, graph_label_g),
            UpdateFromAlphaFunc(lines, update_lines),
            run_time=4
        )
        self.wait()

# To run the scene, use the following command in your terminal:
# manimgl line_length.py test -ol


import utils.data as datautils

import re
from unidecode import unidecode
from keras import Input
from keras.callbacks import EarlyStopping, ReduceLROnPlateau, LambdaCallback
from keras.engine import Model
from keras.layers import Embedding, Bidirectional, LSTM, TimeDistributed, Dense, Dropout, Activation
from keras.models import load_model
from keras.optimizers import RMSprop
from keras.preprocessing.sequence import pad_sequences
from sklearn.model_selection import train_test_split

import constants as c
import numpy as np

from models.relation_classifier import LSTM_SIZE, EMBEDDING_SIZE, DROPOUT, BATCH_SIZE, MAX_EPOCHS
from utils.dependency_parser import get_spacy_parser
import pickle

class RelationClassifierRNNBased():
    def __init__(self,
                 lstm_size=LSTM_SIZE,
                 embedding_size=EMBEDDING_SIZE,
                 dropout=DROPOUT,
                 batch_size=BATCH_SIZE,
                 max_epochs=MAX_EPOCHS
                 ):
        self.w2idx = {c.PAD_TAG: 0, c.UNK_TAG: 1}
        self.idx2w = {0: c.PAD_TAG, 1: c.UNK_TAG}

        self.rel2idx = {c.NIL_TAG:0}
        self.idx2rel = {0:c.NIL_TAG}
        self.MAX_LENGTH = 35

        self.lstm_size = lstm_size
        self.embedding_size = embedding_size
        self.dropout = dropout
        self.batch_size = batch_size
        self.max_epochs = max_epochs

    def make_vocab(self, sents):
        parser = get_spacy_parser()
        for s in sents:
            cleaned_s = datautils.clean_question(s)
            doc = parser.tokenizer(cleaned_s)
            for t in doc:
                if not t.text in self.w2idx:
                    new_idx = len(self.w2idx)
                    self.w2idx[t.text] = new_idx
                    self.idx2w[new_idx] = t.text
                if not t.text.lower() in self.w2idx:
                    new_idx = len(self.w2idx)
                    self.w2idx[t.text.lower()] = new_idx
                    self.idx2w[new_idx] = t.text.lower()

    def to_word_list(self, vect):
        words = list(map(lambda i: self.idx2w[i], vect))
        return words

    def to_tags(self, vect):
        tags = c.entity_tags[vect.argmax()]
        return tags

    def fit(self, X,Y, dev=None):
        # early stopping callback for stop the training when needed
        earlyStopping = EarlyStopping(monitor='val_loss', patience=2, verbose=1, mode='auto')

        # reduce learning rate callback for optimize on plateau
        reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=0, verbose=1, mode='auto',
                                      epsilon=0.0001, cooldown=0, min_lr=0)

        def printer(x):
            print(x)

        print_callback = LambdaCallback(
            on_epoch_end=lambda epoch, logs: printer(logs),
            on_train_end=lambda logs: printer(logs)
        )

        self.model.fit([X], Y, epochs=self.max_epochs, batch_size=self.batch_size,
                       shuffle=True,
                       validation_data=dev,
                       verbose=1,
                       callbacks=[print_callback, reduce_lr, earlyStopping],
                       )

    def train(self, sents, concepts, dev=None):
        X, Y = self.make_XY(sents, concepts)
        if dev:
            X_dev, Y_dev = self.make_XY(dev[0], dev[1])
        else:
            (X_dev, Y_dev) = (None, None)
        self.model = self.get_model()
        self.fit(X,Y,dev=(X_dev, Y_dev))

    def test(self, sents, relations):
        X,Y = self.make_XY(sents, relations)
        Y_pred = self.model.predict(X)

        total = len(X)
        correct = 0
        prediction_per_relation = {k:[0,0] for k in c.RELATIONS}

        for x,yp,yt, sent, r in zip(X, Y_pred, Y, sents, relations):
            no_pads = [t for t in x if t!=0]
            true_len = len(no_pads)
            pred_tags = self.idx2rel[yp.argmax()]
            true_tags = self.idx2rel[yt.argmax()]

            prediction_per_relation[true_tags][1] += 1
            if true_tags==pred_tags:
                correct+=1
                prediction_per_relation[true_tags][0] += 1
            else:
                print("="*50)
                print(sent)
                print(r)
                print(self.to_word_list(x)[:true_len])
                print(pred_tags)
                print(true_tags)
                print("=" * 50)

        print(total, correct)
        print(correct/total)
        print("accuracy per relation: ")

        print("Relation\tSupport\tCorrect\tAcc.")
        for k, (cor, tot) in prediction_per_relation.items():
            print("\t".join([k, str(tot), str(cor), str(cor/tot)]))

        print("\t".join(["Total", str(total), str(correct), str(correct/total)]))

    def get_model(self):
        main_input = Input(shape=(self.MAX_LENGTH,), name="main_input")

        embedded = Embedding(len(self.w2idx),self.embedding_size,mask_zero=True)(main_input)

        blstm_l1 = Bidirectional(LSTM(
            self.lstm_size,
            return_sequences=True)
        )(embedded)

        blstm_l2 = Bidirectional(LSTM(
            self.lstm_size,
            return_sequences=False)
        )(blstm_l1)

        output_l = Dense(len(self.rel2idx), use_bias=True)(blstm_l2)
        dropout_out_l = Dropout(0.1)(output_l)
        activation = Activation(activation="softmax")(dropout_out_l)

        model = Model(inputs=[main_input], outputs=[activation])

        # learning rate = 0.001
        opt = RMSprop()
        model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])
        model.summary()
        return model

    def make_x(self, sent):
        parser = get_spacy_parser()
        question = datautils.clean_question(sent)

        # tokenize the question string
        q_parsed = parser(question)
        q_vect = list(map(lambda t: self.w2idx[t.text] if t.text in self.w2idx else self.w2idx[c.UNK_TAG], q_parsed))
        pad_length = self.MAX_LENGTH - len(q_vect)
        nil_X = 0
        x = (q_vect + (pad_length) * [nil_X])
        return np.array(x)

    def make_XY(self, sents, relations):
        """

        :param sents: a list of strings
        :param relations: a list of relation strings
        :return: X, Y, where:
            - X is a list of one-hot encoded sentences (eventually padded), but not vectorized
                i.e. a np.Array of shape (len(sents), MAX_LENGTH)
            - Y is the list of one-hot encoded relations.
        """
        X, Y = [], []
        for sent, rel in zip(sents, relations):

            x = self.make_x(sent)

            if not rel in self.rel2idx:
                new_idx = len(self.rel2idx)
                self.rel2idx[rel]=new_idx
                self.idx2rel[new_idx]=rel

            y = datautils.to_ohenc(self.rel2idx[rel], 17)

            X.append(np.array(x))
            Y.append(np.array(y))
        return np.array(X),np.array(Y)

    def predict(self, query, candidate_relations=[]):
        x = self.make_x(query)

        Y_pred = self.model.predict(np.array([x]))

        Y_pred = enumerate(Y_pred[0])
        Y_pred = sorted(Y_pred, key=lambda x: -x[1])

        relation = self.idx2rel[Y_pred[0][0]]

        # Actually, the mapping relation 2 domain are not so good.
        # For example, for "Geography and places" there missing the "PLACE" relation, which I think it is necessary.
        # However, the implementation is below.

        # if candidate_relations and relation not in candidate_relations:
        #     Y_pred = list(filter(lambda x: x[0]!=self.rel2idx[c.NIL_TAG]  and self.idx2rel[x[0]] in candidate_relations, Y_pred))
        #     Y_pred = sorted(Y_pred, key=lambda x: -x[1])
        #     if len(Y_pred)==0:
        #         relation =""
        #     else:
        #         relation = self.idx2rel[Y_pred[0][0]]

        return relation

    def save(self, dirpath):
        import os
        os.system("rm -rf " + dirpath)
        os.mkdir(dirpath)
        temp = self.get_model()
        self.model.save(dirpath+"/model")
        self.model = None
        import pickle
        pickle.dump(self, open(dirpath+"/obj", "wb"))
        self.model = temp

    @staticmethod
    def load(dirpath):
        obj = pickle.load(open(dirpath+"/obj", "rb"))
        model = load_model(dirpath+"/model")
        obj.model = model
        return obj


from simpy import Environment

# from src.drawer import Drawer
from sim.simulation import Simulation
from sim.status_warehouse.Simulate_Events.action import Action
from sim.warehouse import Warehouse

class Buffer(Action):
    def __init__(self, env: Environment, warehouse: Warehouse, simulation: Simulation):
        super().__init__(env, warehouse, simulation)

    # override
    def simulate_action(self):
        # try to take buffer resource
        with self.get_simulation().get_res_buffer().request() as req_buf:
            yield req_buf
            # try to take deposit resource
            with self.get_simulation().get_res_deposit().request() as req_dep:
                yield req_dep
                # check if the deposit and the buffer are empty and full iff the resources are taken
                if self.get_warehouse().get_carousel().is_buffer_full() and \
                        not self.get_warehouse().get_carousel().is_deposit_full():
                    print(f"Time {self.env.now:5.2f} - Start loading buffer drawer inside the deposit")
                    yield self.env.process(self.get_warehouse().loading_buffer_and_remove())
                    print(f"Time {self.env.now:5.2f} - Finish loading buffer drawer inside the deposit")


import os
import cv2
import numpy as np

def vid2frm(infile, out_dir):
    cap = cv2.VideoCapture(infile)
    W = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    H = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    fps = cap.get(cv2.CAP_PROP_FPS)
    print('[INFO] total frame: {}, fps: {}, width: {}, height: {}'.format(count, fps, W, H))
    count = 1

    while(cap.isOpened()):
        ret, frame = cap.read()
        if count % 100 == 0:
            print('count: {}'.format(count))
        if ret:
            # cv2.imshow('frame', frame)
            # video.append(frame)
            # if count >= 1020 and count <= 1170:
            cv2.imwrite(os.path.join(out_dir, '{}.png'.format(count)), frame)
            # if cv2.waitKey(1) & 0xFF == ord('q'):
            #     break
        else:
            break
        count += 1
    cap.release()

# outfile = '{}ex_{}'.format(infile_dir, infile)
# fps = 20.0
# codecs = 'H264'

# print(np.array([video]).shape)
# ret, frames, height, width, ch = np.array([video]).shape
#
# fourcc = cv2.VideoWriter_fourcc(*codecs)
# writer = cv2.VideoWriter(outfile, fourcc, fps, (width, height))
# for i in range(frames):
#     writer.write(video[i])
# writer.release()

if __name__ == '__main__':
    infile = '../../data/videos/half_noon/out_off_half_inter10_noon.avi'
    out_dir = '../../data/videos/half_noon/off'
    vid2frm(infile, out_dir)


#!/usr/bin/env python3

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

import socket
import argparse
import sys
import time
import struct
import threading
import json
#from pynput import keyboard
# global end_t
# end_t = 0

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

# Read in the config.py file to set various addresses and ports.
#from config import *
CMD_FIELD_LEN = 1
FILE_SIZE_FIELD_LEN = 8

CMD = {
    "getdir":1,
    "makeroom":2,
    "deleteroom":3,
    "bye":4,
}

########################################################################
# Broadcast Server class
########################################################################

class Sender:

    # HOSTNAME = socket.gethostbyname('')
    HOSTNAME = 'localhost'
    CDP_PORT = 50000
    BACKLOG = 5

    TIMEOUT = 2
    RECV_SIZE = 256

    MSG_ENCODING = "utf-8"
    # MESSAGE =  HOSTNAME + "multicast beacon: "
    MESSAGE = "Hello from "
    MESSAGE_ENCODED = MESSAGE.encode('utf-8')

    # TTL = 1 # Hops
    # TTL_SIZE = 1 # Bytes
    # TTL_BYTE = TTL.to_bytes(TTL_SIZE, byteorder='big')
    # OR: TTL_BYTE = struct.pack('B', TTL)

    #chatroomDict = {}

    def __init__(self):
        self.connected = 0
        self.chatroomDict = {}
        self.thread_list = []
        self.create_listen_socket()
        self.process_connections_forever()
        #self.send_messages_forever()

    # def getDict(self,dic):
    #     print(dic.items())
    #     return dic.items()

    def create_listen_socket(self):
        try:
            #create TCP server listen socket

            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.socket.bind((Sender.HOSTNAME, Sender.CDP_PORT))
            # tcp_thread = thread.Thread(target=self.socket.listen(Server.BACKLOG))
            self.socket.listen(Sender.BACKLOG)
            print("WELCOME TO THE CRDS \nListening for TCP connections on CDP Port {} ...".format(Sender.CDP_PORT))
            print('-'*72)

        except Exception as msg:
            print(msg)
            sys.exit(1)

    def connection_handler(self,client):
        connection,address = client
        while True:
            #read the command sent from client to server
             cmd = int.from_bytes(connection.recv(CMD_FIELD_LEN), byteorder='big')
             #print(cmd)
             if (cmd not in CMD.values()):
                 print('Incorrect command received')
                 break

             if (cmd==CMD['bye']):
                 print('Closing {} client connection...'.format(address))
                 print('-'*72)
                 connection.close()
                 break

             elif(cmd == CMD['getdir']):
                 try:
                     #print("in the server")
                     dicttoSend = json.dumps(self.chatroomDict)
                     chatroomDict = dicttoSend.encode(Sender.MSG_ENCODING)
                     connection.sendall(chatroomDict)
                     print("Sending dictionary")
                 except socket.error:
                     print("Closing client connection...")
                     connection.close()
                     return

             elif(cmd == CMD['makeroom']):
                #decode message from client
                 #print("decoding message")
                 room_bytes = connection.recv(Sender.RECV_SIZE)
                 room_str = room_bytes.decode(Sender.MSG_ENCODING)
                 #print(room_str)

                 #check address/port combination is unique

                 #print(list(self.chatroomDict.values()))
                 #print("this ist the length",len(self.chatroomDict))
                 if len(self.chatroomDict)>0:
                     for value in list(self.chatroomDict.values()):
                         #print("inside for loop")
                         if value!= (room_str.split(",")[1],room_str.split(",")[2]):
                             self.chatroomDict[room_str.split(",")[0]] = (room_str.split(",")[1],room_str.split(",")[2])
                         else:
                             print("address/port already exits")
                 else:
                     self.chatroomDict[room_str.split(",")[0]] = (room_str.split(",")[1],room_str.split(",")[2])

             elif(cmd==CMD['deleteroom']):
                 #print("Inside delete function")
                 deleteroom_bytes = connection.recv(Sender.RECV_SIZE)
                 deleteroom_str = deleteroom_bytes.decode(Sender.MSG_ENCODING)
                 print(deleteroom_str)
                 print(self.chatroomDict)
                 self.chatroomDict.pop(deleteroom_str)
                 #print("POPPED OFF")
                 print(self.chatroomDict)

             elif (cmd==CMD['bye']):
                 print("CLOSING CONNECTION")
                 #closing client connection to server but not server socket as its still listening
                 connection.close()
                 break

    def process_connections_forever(self):
        try:
            while True:
                new_client = self.socket.accept()
                new_thread = threading.Thread(target=self.connection_handler,args=(new_client,))
                self.thread_list.append(new_thread)
                #print("Starting serving thread:", new_thread.name)
                print("Connected to the client")

                new_thread.daemon = True
                new_thread.start()

        except Exception as msg:
            print(msg)
        except KeyboardInterrupt:
            print()
        finally:
            print("Closing server socket...")
            self.socket.close()
            sys.exit(1)

########################################################################
# Echo Receiver class
########################################################################

class Receiver:

    RECV_SIZE = 256
    MSG_ENCODING = 'utf-8'
    TTL = 1 # Hops
    TTL_SIZE = 1 # Bytes
    TTL_BYTE = TTL.to_bytes(TTL_SIZE, byteorder='big')
    TIMEOUT = 2

    def __init__(self):
        self.userName = ""
        self.chatroomName = ""
        self.flag = 0
        self.chatroomInfo = {}
        self.thread_list = []
        self.get_console_input()

    def tcp_connection(self):
        if self.connected ==1:
            self.get_socket()
            self.connect_to_server()
            #self.send_console_input_forever()

    def get_socket(self):
        try:
            #create TCP socket
            self.socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        except Exception as msg:
            print(msg)
            exit()

    def connect_to_server(self):
        try:
            #create TCP connection
            self.socket.connect((Sender.HOSTNAME, Sender.CDP_PORT))
        except Exception as msg:
            print(msg)
            exit()

    def getdir(self):
        #print("inside getdir")
        #create packet and send to server
        getdir_field = CMD["getdir"].to_bytes(CMD_FIELD_LEN,byteorder='big')
        self.socket.sendall(getdir_field)
        try:
            recvd_bytes = self.socket.recv(Receiver.RECV_SIZE)
            json_str = recvd_bytes.decode(Receiver.MSG_ENCODING)
            #print(json.loads(json_str))
            #print(type(json.loads(json_str)))
            test_dict = json.loads(json_str)
            self.chatroomInfo = dict(list(test_dict.items()))
            print(self.chatroomInfo)

        except Exception as msg:
            print(msg)
            sys.exit(1)

    def makeroom(self,roomName,address,port):
        makeroom_field = CMD["makeroom"].to_bytes(CMD_FIELD_LEN,byteorder='big')
        roomName = roomName + ","
        roomNameField = roomName.encode(Receiver.MSG_ENCODING)
        address = address + ","
        addressField = address.encode(Receiver.MSG_ENCODING)
        portField = port.encode(Receiver.MSG_ENCODING)
        pkt = makeroom_field + roomNameField + addressField + portField
        #print("sending packet")
        self.socket.sendall(pkt)

    def send_messages_forever(self,MULTICAST_ADDRESS,MULTICAST_PORT):
        try:
            while True:
                self.chat_text = input()
                MULTICAST_PORT = int(MULTICAST_PORT)
                MULTICAST_ADDRESS_PORT = (MULTICAST_ADDRESS, MULTICAST_PORT)
                if self.chat_text == "~]" :
                    #send message to receive socket to signal it to finish
                    killMsg = "Sender thread closing".encode(Receiver.MSG_ENCODING)
                    self.socket_udp.sendto(killMsg,MULTICAST_ADDRESS_PORT)
                    return
                nameField = self.userName.encode(Receiver.MSG_ENCODING)
                dashField = ":".encode(Receiver.MSG_ENCODING)
                textField = self.chat_text.encode(Receiver.MSG_ENCODING)
                pkt = nameField + dashField + textField
                self.socket_udp.sendto(pkt,MULTICAST_ADDRESS_PORT)
                time.sleep(Sender.TIMEOUT)
        except KeyboardInterrupt:
            #print("There has been an error",msg)
            print("exiting")
            return

    def receive_forever(self):
        while True:
            try:
                data, address_port = self.socket_r.recvfrom(Receiver.RECV_SIZE)
                address, port = address_port
                if data.decode('utf-8') == "Sender thread closing":
                    #print("Closing receive thread")
                    return
                print("\n")
                print(data.decode('utf-8'))
            # except Exception as msg:
            #     print("There has been an error",msg)
            #     #sys.exit(1)
            except KeyboardInterrupt:
                print("exiting")
                return

    def deleteroomFunc(self,room):
        delete_field = CMD['deleteroom'].to_bytes(CMD_FIELD_LEN,byteorder='big')
        room_field = room.encode(Receiver.MSG_ENCODING)
        pkt = delete_field + room_field
        self.socket.sendall(pkt)

    def bye(self):
        bye_field = CMD['bye'].to_bytes(CMD_FIELD_LEN,byteorder='big')
        self.socket.sendall(bye_field)
        print("Closing connection to the CRDS")
        self.connected = 0
        #closing TCP socket and cannot do self.socket functions
        self.socket.close()
        #sys.exit(1)

    def chat(self):
        #print("Inside chat function")
        #find multicast ip address and port from local dictionary
        MULTICAST_ADDRESS,MULTICAST_PORT = self.chatroomInfo[self.chatroomName]
        #create udp send socket
        try:
            self.socket_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self.socket_udp.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, Receiver.TTL_BYTE)
            #print("Create the first socket")
        except Exception as msg:
            print(msg)

    #create udp receive socket and multicast group request
        try:
            self.socket_r = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self.socket_r.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
            MULTICAST_PORT = int(MULTICAST_PORT)
            self.socket_r.bind((MULTICAST_ADDRESS,MULTICAST_PORT))
            multicast_group_bytes = socket.inet_aton(MULTICAST_ADDRESS)
            RX_IFACE_ADDRESS = "0.0.0.0"
            multicast_if_bytes = socket.inet_aton(RX_IFACE_ADDRESS)
            print("before multicast request")
            multicast_request = multicast_group_bytes + multicast_if_bytes
            self.socket_r.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, multicast_request)
            print("created the second socket")
        except Exception as msg:
            print (msg)

        #change command prompt
        print('\n'+'*'*50)
        print("Entering chatroom")

        # #Create listener thread to listen for ctrl key
        # def pass_to_thread(self,key):
        #     print(key.char)

        # def dummyFunction(self):
        #     return False

        # h = keyboard.Listener(on_press = pass_to_thread, on_release = dummyFunction)
        # #h.daemon = True
        # self.thread_list.append(h)
        # h.start()

        #Create UDP thread
        udp_thread_send = threading.Thread(target=self.send_messages_forever,args=(MULTICAST_ADDRESS,MULTICAST_PORT))
        self.thread_list.append(udp_thread_send)
        #udp_thread_send.daemon = True
        udp_thread_send.start()

        #create UDP thread
        udp_thread_receive = threading.Thread(target=self.receive_forever)
        self.thread_list.append(udp_thread_receive)
        #udp_thread_receive.daemon = True
        udp_thread_receive.start()

        #set flag back to zero and go back to console_input
        udp_thread_send.join()
        udp_thread_receive.join()
        self.socket_udp.close()
        self.socket_r.close()
        self.flag = 0
        self.get_console_input()

    def get_console_input(self):
        while self.flag!=1:
            print('\n'+'*'*50)
            self.input_text = input("--(connect,getdir, makeroom, deleteroom, name , chat)--\n Enter Command: ")
            try:
                if (self.input_text == "connect"):
                    self.connected = 1
                    self.tcp_connection()
                    print("Connected to the CRDS")

            except Exception as msg:
                print(msg)

            try:
                if(self.input_text == "getdir"):
                    #print("detected")
                    self.getdir()
            except Exception as msg:
                print(msg)

            try:
                if(self.input_text.split()[0] == "makeroom"):
                    if(len(self.input_text.split())<2):
                        print('ENTER FORMAT: makeroom <chat room name> <address> <port>')
                        break

                    chatRoomName = self.input_text.split()[1]
                    address = self.input_text.split()[2]
                    port = self.input_text.split()[3]

                    self.makeroom(chatRoomName,address,port)

            except Exception as msg:
                print(msg)

            try:
                if(self.input_text.split()[0] == "deleteroom"):
                    deleteroomName = self.input_text.split()[1]
                    self.deleteroomFunc(deleteroomName)
            except Exception as msg:
                print(msg)

            try:
                if(self.input_text.split()[0] == "chat"):
                    # chatRoomName = self.input_text.split()[1]
                    # print("This si the name of the chatroom",chatRoomName)
                    # self.chat(chatRoomName)
                    self.chatroomName = self.input_text.split()[1]
                    self.flag = 1
            except Exception as msg:
                print(msg)

            try:
                if(self.input_text.split()[0] == "name"):
                    self.userName = self.input_text.split()[1]
                    print("This is the name entered",self.userName)
            except Exception as msg:
                print(msg)

            try:
                if(self.input_text.split()[0] == "bye"):
                    self.bye()
            except Exception as msg:
                print(msg)

        print("proceeding to chat function")
        self.chat()

########################################################################
# Process command line arguments if run directly.
########################################################################

if __name__ == '__main__':
    roles = {'receiver': Receiver,'sender': Sender}
    parser = argparse.ArgumentParser()

    parser.add_argument('-r', '--role',
                        choices=roles,
                        help='sender or receiver role',
                        required=True, type=str)

    args = parser.parse_args()
    roles[args.role]()

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


import random

def main():
    # 1. Understand how to create a list and add values
    # A list is an ordered collection of values
    names = ['Julie', 'Mehran', 'Simba', 'Ayesha']
    names.append('Karel')

    # 2. Understand how to loop over a list
    # Prints the list to the screen one value at a time
    for value in names:
        print(value)

    # 3. Understand how to look up the length of a list
    # Use randint to select a valid "index"
    max_index = len(names) - 1
    index = random.randint(0, max_index)

    # 4. Understand how to get a value by its index
    # Get the item at the chosen index
    correct_answer = names[index]

    # This is just like in Khansole Academy...
    # Prompt user for an answer and check whether correct or not
    prompt = 'Who is in index...' + str(index) + '? '
    answer = input(prompt)
    if answer == correct_answer:
        print('Good job')
    else:
        print('Correct answer was', correct_answer)

if __name__ == '__main__':
    main()


from utils.dataset import AscDatasets
import statsmodels.api as sm
import numpy as np
from sklearn.metrics import mean_squared_error
from multiprocessing import Pool
from functools import partial

def save_prediction(prediction):
	dataPath = 'data/medianmodel_acc_pred_arima_'
	with open(dataPath+'.asc', 'wb') as f:
		np.save(f, prediction, allow_pickle=False)

def rmse(y_actual, y_predicted):
    return sqrt(mean_squared_error(y_actual, y_predicted) + 1e-6)

def run_arma(data, train_data, i):
	if (-999.250 in train_data):
		preds = [-999.250 for i in range(181)]
		return preds
	else:
		print(i)
		print(train_data.shape)
		in_x = np.copy(train_data)
		preds = []
		for j in range(181):
			start_index = len(train_data) + j
			end_index = start_index + 6
			arma = sm.tsa.statespace.SARIMAX(in_x, order=(7,0,1), enforce_stationarity=False)
			results = arma.fit(disp=False, maxiter=100)
			pred_sequence = results.predict(start=start_index, end=end_index, dynamic=False)
			preds.append(pred_sequence[-1])
			in_x = np.append(in_x, data[start_index, i])
		return preds

if __name__ == '__main__':
	dataset = AscDatasets('data', '/medianmodel_acc', 3, 1, False, 0.2, 0.2, 7, 7)
	data = dataset.load_data_arma()

	train_data = data[:-188]
	test_data = data[-181:]

	#Second half of the data
	train = train_data.T[int(train_data.shape[1]/2):]
	idxs = [x+int(train_data.shape[1]/2) for x in idxs]

	#First half of the data
	#train = train_data.T[: int(train_data.shape[1]/2)]
	#idxs = list(range(int(train_data.shape[1]/2)))
	with Pool() as pool:
		func = partial(run_arma, data)
		pred_matrix = pool.starmap(func, zip(train, idxs))
		preds = np.array(pred_matrix)
		#shape: 181 x time series
		preds = preds.T
		print(preds.shape)
		pool.close()
		pool.join()
	save_prediction(preds)


# -*- coding: utf-8 -*-
"""
    Anotherdark Colorscheme
    ~~~~~~~~~~~~~~~~~~~~~~~

    Converted by Vim Colorscheme Converter
"""
from pygments.style import Style
from pygments.token import Token, Keyword, String, Generic, Number, Name, Comment, Operator

class AnotherdarkStyle(Style):

    background_color = '#333333'
    styles = {
        Token:              '#ffffff bg:#333333',
        Name.Tag:           '#f0e68c',
        Comment.Preproc:    '#cd5c5c',
        Name.Entity:        '#ffdead',
        Name.Constant:      '#ffa0a0',
        Keyword:            '#f0e68c',
        Generic.Deleted:    '#0000c0 bg:#008080 bold',
        Generic.Heading:    '#cd5c5c',
        Comment:            '#ff4500',
        Keyword.Type:       '#bdb76b',
        Generic.Emph:       '#c000c0 underline',
        Name.Variable:      '#98fb98',
        Generic.Error:      '#c0c0c0 bg:#c00000 bold',
        Generic.Subheading: '#cd5c5c',
        Generic.Output:     '#add8e6 bg:#4d4d4d bold',
        Generic.Traceback:  '#c0c0c0 bg:#c00000 bold',
        Generic.Inserted:   'bg:#0000c0',
    }


'''âœ¨ Python: Convert A Given Temperature Value. âœ¨

Points:
    - Convert Celsius to Fahrenheit and Vice-Versa.
    - Validate input for strings or any text-mix values.
    - Validate Input for absolute zero(Temperature above absolute zero only).
'''

class Temperature:
    def __init__(self, value=0, unit='C'):
        self.value, self.unit = self._validate_input(value, unit)

    def _validate_input(self, value, unit):
        try:
            value = float(value)
            if unit == 'C' and value < -273.15:
                raise ValueError(
                    "Temperature can't go below absolute zero in Celsius")
            elif unit == 'F' and value < -459.67:
                raise ValueError(
                    "Temperature can't go below absolute zero in Fahrenheit")
            return value, unit
        except ValueError:
            return None, None

    def to_celsius(self):
        if self.unit == 'F':
            self.value = (self.value - 32) * 5/9
            self.unit = 'C'
        return self.value

    def to_fahrenheit(self):
        if self.unit == 'C':
            self.value = (self.value * 9/5) + 32
            self.unit = 'F'
        return self.value

    def __sub__(self, other):
        if isinstance(other, Temperature):
            if self.unit == 'F':
                self.value = (self.value - 32) * 5/9
                self.unit = 'C'

            if other.unit == 'F':
                other.value = (other.value - 32) * 5/9
                other.unit = 'C'

            return Temperature(self.value - other.value, 'C')
        else:
            raise ValueError("Can only subtract two Temperature objects")

# Manual feed
# value = 50
# unit = 'F'

# Take user input from console.
value = input("Enter temperature value: ")
unit = input("Enter temperature unit (C or F): ")
temp1 = Temperature(value, unit)
temp2 = Temperature(50, "C")
temp_diff = temp1 - temp2
if temp1.value is not None:
    print(f"Temperature in Celsius: {temp1.to_celsius():.2f}")
    print(f"Temperature in Fahrenheit: {temp1.to_fahrenheit():.2f}")
    print(f"Temperature difference is {temp_diff.value} degrees Celsius.")
else:
    print("Please enter a valid temperature value and unit.")

# ðŸ”¥ -40 Fahrenheit is equal to -40 Celsius. Check it yourself.ðŸ”¥
# ðŸ‘©â€�ðŸ’» See you soon, Till then Keep Improving!!!

# For more on Python follow: https://twitter.com/CodingMantras


from random import randint
import pygame

cell_size = 20
cell_number = 35

# Definindo cores
PRETO = (0, 0, 0)
BRANCO = (255, 255, 255)
VERMELHO = (255, 0, 0)
VERDE = (0, 255, 0)
AZUL = (0, 0, 255)
AMARELO = (244, 233, 51)

DONE = 0
PLAYING = 1
TELA = 2
DEAD = 3

RUNNING = 0
PAUSED = 1

# Nome do jogo'

# Frames por segundo (FPS)
FPS = 10


from typing import List
from ants_ai.training.game_state.game_turn import GameTurn
from ants_ai.training.game_state.game_map import GameMap
from ants_ai.engine.bot import BotName

class GameState:
    def __init__(self,
                 game_id: str,
                 game_turns: List[GameTurn],
                 game_map: GameMap,
                 view_radius_squared: int,
                 winning_bot: BotName,
                 ranking_turn: int):
        self.game_id = game_id
        self.winning_bot = winning_bot
        self.game_turns = game_turns
        self.game_map = game_map
        self.view_radius_squared = view_radius_squared
        self.ranking_turn = ranking_turn


from keras.callbacks import ModelCheckpoint

from data import data_generator, save_result
from unet import unet

train_generator = data_generator('training_set', 1, 200, flip=True)
test_generator = data_generator('training_set', 205, 5, flip=True)

weight_name = 'first_generation_4'

model = unet()

model_checkpoint = ModelCheckpoint('{}.hdf5'.format(weight_name), monitor='loss', verbose=1, save_best_only=True)

model.fit_generator(train_generator, steps_per_epoch=200, epochs=200, callbacks=[model_checkpoint])
result = model.predict_generator(test_generator, steps=5, verbose=1)

save_result(result, start=205, path='./{}'.format(weight_name))