# -*- coding: utf-8 -*-
import json
import logging
from copy import deepcopy
from datetime import datetime
from enum import IntEnum

from addict import Dict
from console.exception.main import (EnvAlreadyExist, ErrDepVolumeNotFound, ErrInvalidVolume, InnerPortNotFound, InvalidEnvName,
                                    ServiceHandleException, ServiceRelationAlreadyExist)
from console.repositories.app import service_repo, service_source_repo
from console.repositories.app_config import port_repo, volume_repo
from console.repositories.probe_repo import probe_repo
from console.repositories.service_backup_repo import service_backup_repo
from console.services.app_actions import app_manage_service
from console.services.app_actions.app_restore import AppRestore
from console.services.app_actions.exception import ErrBackupNotFound
from console.services.app_actions.properties_changes import (PropertiesChanges, get_template_component,
                                                             get_upgrade_app_template)
from console.services.app_config import (AppPortService, env_var_service, mnt_service)
from console.services.app_config.app_relation_service import \
    AppServiceRelationService
from console.services.app_config.component_graph import component_graph_service
from console.services.app_config.service_monitor import service_monitor_repo
from console.services.backup_service import \
    groupapp_backup_service as backup_service
from console.services.exception import ErrDepServiceNotFound
from console.services.plugin import app_plugin_service
from django.db import transaction
from www.apiclient.regionapi import RegionInvokeApi
from www.apiclient.regionapibaseclient import RegionApiBaseHttpClient
from www.tenantservice.baseservice import BaseTenantService
from www.utils.crypt import make_uuid

logger = logging.getLogger("default")
region_api = RegionInvokeApi()
app_port_service = AppPortService()
app_relation_service = AppServiceRelationService()
baseService = BaseTenantService()

class AsyncAction(IntEnum):
    BUILD = 0
    UPDATE = 1
    NOTHING = 2

# priority: build > update > nothing
priority = {AsyncAction.BUILD.value: 0, AsyncAction.UPDATE.value: 1, AsyncAction.NOTHING.value: 2}

class PropertyType(IntEnum):
    """type of property
    ALL: do not distinguish, use all properties
    ORDINARY: ordinary properties
    DEPENDENT: properties that need to handle dependencies
    """
    ALL = 0
    ORDINARY = 1
    DEPENDENT = 2

class AppDeployService(object):
    def __init__(self):
        self.impl = OtherService()

    def set_impl(self, impl):
        self.impl = impl

    def pre_deploy_action(self, tenant, service, version=None):
        """perform pre-deployment actions"""
        if service.service_source == "market":
            # TODO: set app template init MarketService
            self.impl = MarketService(tenant, service, version)

        self.impl.pre_action()

    def get_async_action(self):
        return self.impl.get_async_action()

    def execute(self, tenant, service, user, is_upgrade, version, committer_name=None, oauth_instance=None):
        async_action = self.get_async_action()
        logger.info("service id: {}; async action is '{}'".format(service.service_id, async_action))
        if async_action == AsyncAction.BUILD.value:
            code, msg, event_id = app_manage_service.deploy(tenant, service, user, oauth_instance=oauth_instance)
        elif async_action == AsyncAction.UPDATE.value:
            code, msg, event_id = app_manage_service.upgrade(
                tenant, service, user, committer_name, oauth_instance=oauth_instance)
        else:
            return 200, "", ""
        return code, msg, event_id

    def deploy(self, tenant, service, user, version, committer_name=None, oauth_instance=None):
        """
        After the preparation is completed, emit a deployment task to the data center.
        """
        self.pre_deploy_action(tenant, service, version)

        return self.execute(tenant, service, user, version, committer_name, oauth_instance=oauth_instance)

class OtherService(object):
    """
    Services outside the market service
    """

    def pre_action(self):
        logger.info("type: other; pre-deployment action.")

    def get_async_action(self):
        return AsyncAction.BUILD.value

class MarketService(object):
    """
    Define some methods for upgrading market services.
    """

    def __init__(self, tenant, service, version, all_component_one_model=None, component_change_info=None, app_version=None):
        self.tenant = tenant
        self.service = service
        self.market_name = None
        # tenant service models
        self.all_component_one_model = all_component_one_model
        self.service_source = service_source_repo.get_service_source(tenant.tenant_id, service.service_id)
        self.install_from_cloud = self.service_source.is_install_from_cloud()
        self.market_name = self.service_source.get_market_name()
        # If no version is specified, the default version is used.
        self.async_action = None
        if not version:
            version = self.service_source.version
            self.async_action = AsyncAction.BUILD.value
        self.version = version
        self.group_key = self.service_source.group_key
        self.changes = component_change_info
        if app_version:
            self.template = json.loads(app_version.app_template)
            self.template_update_time = app_version.update_time
        else:
            self.template = None
            self.template_update_time = None
        self.update_source = False
        # data that has been successfully changed
        self.changed = {}
        self.backup = None

        self.update_funcs = self._create_update_funcs()
        self.sync_funcs = self._create_sync_funcs()
        self.restore_func = self._create_restore_funcs()
        self.async_build, self.async_update = self._create_async_action_tbl()

        self.app_restore = AppRestore(tenant, service)
        self.auto_restore = True

    def dummy_func(self, changes):
        logger.debug("dummy_func")

    def set_properties(self, typ3=PropertyType.ALL.value):
        """
        Types of service properties are ordinary and dependent. when updating an application,
        you need to process the ordinary properties first and then the dependent ones.
        In addition, you don't need to distinguish properties when you roll back.
        """
        all_update_funcs = self._create_update_funcs()
        all_sync_funcs = self._create_sync_funcs()
        m = {
            PropertyType.ORDINARY.value: [
                "deploy_version", "app_version", "image", "slug_path", "envs", "connect_infos", "ports", "volumes", "probe",
                "component_graphs", "component_monitors"
            ],
            PropertyType.DEPENDENT.value: ["dep_services", "dep_volumes", "plugins"]
        }
        keys = m.get(typ3, None)
        if keys is None:
            self.update_funcs = all_update_funcs
            self.sync_funcs = all_sync_funcs
            return
        self.update_funcs = {key: all_update_funcs[key] for key in keys if key in all_update_funcs}
        self.sync_funcs = {key: all_sync_funcs[key] for key in keys if key in all_sync_funcs}

    def _create_update_funcs(self):
        return {
            "deploy_version": self._update_deploy_version,
            "app_version": self._update_version,
            "image": self.dummy_func,
            "slug_path": self.dummy_func,
            "envs": self._update_inner_envs,
            "connect_infos": self._update_outer_envs,
            "ports": self._update_ports,
            "volumes": self._update_volumes,
            "probe": self._update_probe,
            "dep_services": self._update_dep_services,
            "dep_volumes": self._update_dep_volumes,
            "plugins": self._update_plugins,
            "component_graphs": self._update_component_graphs,
            "component_monitors": self._update_component_monitors,
        }

    def _create_sync_funcs(self):
        return {
            "deploy_version": self.dummy_func,
            "app_version": self.dummy_func,
            "image": self.dummy_func,
            "slug_path": self.dummy_func,
            "envs": self._sync_inner_envs,
            "connect_infos": self._sync_outer_envs,
            "ports": self._sync_ports,
            "volumes": self._sync_volumes,
            "probe": self._sync_probe,
            "dep_services": self._sync_dep_services,
            "dep_volumes": self._sync_dep_volumes,
            "plugins": self._sync_plugins,
            "component_monitors": self._sync_component_monitors,
        }

    def _create_restore_funcs(self):
        return {
            "deploy_version": self.dummy_func,
            "app_version": self.dummy_func,
            "image": self.dummy_func,
            "slug_path": self.dummy_func,
            "envs": self._restore_inner_envs,
            "connect_infos": self._restore_outer_envs,
            "ports": self._restore_ports,
            "volumes": self._restore_volumes,
            "probe": self._restore_probe,
            "dep_services": self._restore_dep_services,
            "dep_volumes": self._restore_dep_volumes,
            "plugins": self._restore_plugins,
            "service": self._restore_service,
            "service_source": self._restore_service_source,
        }

    @staticmethod
    def _create_async_action_tbl():
        """
        create an asynchronous action corresponding to the modification of each property
        asynchronous action: build, update or nothing
        """
        async_build = ["deploy_version", "image", "slug_path"]
        async_update = ["envs", "connect_infos", "ports", "volumes", "probe", "dep_services", "dep_volumes", "plugins"]
        return async_build, async_update

    def pre_action(self):
        """
        raise RbdAppNotFound
        raise RecordNotFound
        raise ErrServiceSourceNotFound
        """
        logger.info("type: market; service id: {}; pre-deployment action.".format(self.service.service_id))
        backup = self.create_backup()
        logger.info("service id: {}; backup id: {}; backup successfully.".format(self.service.service_id, backup.backup_id))
        self.set_changes()

        try:
            with transaction.atomic():
                self.modify_property()
                self.sync_region_property()
        except RegionApiBaseHttpClient.CallApiError as e:
            logger.exception(e)
            logger.error("service id: {}; failed to change properties for market service: {}".format(
                self.service.service_id, e))
            self.restore_backup(backup)
            # when a single service is upgraded, if a restore occurs,
            # there is no need to emit an asynchronous action.
            self.async_action = AsyncAction.NOTHING.value

    def set_changes(self):
        pc = None
        if not self.template:
            pc = PropertiesChanges(
                self.service,
                self.tenant,
                all_component_one_model=self.all_component_one_model,
                install_from_cloud=self.install_from_cloud)
            template = get_upgrade_app_template(self.tenant, self.version, pc)
            self.template = template
            self.template_update_time = pc.template_updatetime
        if not self.changes and self.changes != {} and pc:
            _, changes = pc.get_property_changes(template=template)
            logger.debug("service id: {}; dest version: {}; changes: {}".format(self.service.service_id, self.version, changes))
            self.changes = changes

    def create_backup(self):
        """create_backup
        Create a pre-service backup to prepare for deployment failure
        """
        backup_data = backup_service.get_service_details(self.tenant, self.service)
        backup = {
            "region_name": self.service.service_region,
            "tenant_id": self.tenant.tenant_id,
            "service_id": self.service.service_id,
            "backup_id": make_uuid(),
            "backup_data": json.dumps(backup_data),
            "create_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            "update_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
        }
        return service_backup_repo.create(**backup)

    def modify_property(self):
        """
        Perform modifications to the given properties. must be called after `set_changes`.
        """
        component = get_template_component(self.template, self.service_source)
        # if component is null, maybe new app not have this component
        if component:
            if not self.update_source:
                self._update_service(component)
                self._update_service_source(component, self.version, self.template_update_time)
                self.update_source = True
            if not self.changes:
                return
            changes = deepcopy(self.changes)
            if changes:
                for k, v in list(changes.items()):
                    func = self.update_funcs.get(k, None)
                    if func is None:
                        continue
                    func(v)
        else:
            raise ServiceHandleException(msg="component is not exist", msg_show="该版本模版不存在该组件，无法进行升级")

    @staticmethod
    def _compare_async_action(a, b):
        """
        compare a, b, two asynchronous actions, returning the ones with higher priority
        """
        if priority[a] < priority[b]:
            return a
        return b

    def get_async_action(self):
        """ get asynchronous action
        must be called after `set_changes`.
        """
        if self.install_from_cloud:
            return AsyncAction.BUILD.value
        if self.async_action is not None:
            return self.async_action
        changes = deepcopy(self.changes)
        async_action = AsyncAction.NOTHING.value
        if changes:
            for key in changes:
                async_action = self._compare_async_action(async_action, self._key_action(key))
        return async_action

    def _key_action(self, key):
        if key in self.async_build:
            return AsyncAction.BUILD.value
        if key in self.async_update:
            return AsyncAction.UPDATE.value
        return AsyncAction.NOTHING.value

    def sync_region_property(self):
        """
        After modifying the properties on the console side, you need to
        synchronize with the region side. must be called after `set_changes`.
        raise: RegionApiBaseHttpClient.CallApiError
        """
        if not self.changes:
            return
        changes = deepcopy(self.changes)
        if changes:
            for k, v in list(changes.items()):
                func = self.sync_funcs.get(k, None)
                if func is None:
                    continue
                func(v)
                self.changed[k] = v

    def restore_backup(self, backup=None):
        """
        Restore data in the region based on backup information
        when an error occurs during deployment.
        """
        logger.info("service id: {}; changed properties: {}; restore service from backup".format(
            self.service.service_id, json.dumps(self.changed)))
        if backup is None:
            # use the latest backup
            backup = service_backup_repo.get_newest_by_sid(self.tenant.tenant_id, self.service.service_id)
        if backup is None:
            raise ErrBackupNotFound(self.service.service_id)

        self._update_changed()

        async_action = AsyncAction.NOTHING.value
        for k, v in list(self.changed.items()):
            func = self.restore_func.get(k, None)
            if func is None:
                continue
            try:
                func(backup)
            except RegionApiBaseHttpClient.CallApiError as e:
                # ignore restore error
                logger.error("service id: {}; failed to restore {}; {}".format(self.service.service_id, k, e))
            async_action = self._compare_async_action(async_action, self._key_action(k))
        self.async_action = async_action

    def _update_changed(self):
        if not self.changed:
            logger.info("service id: {}; no specified changed, will restore \
                all properties".format(self.service.service_id))
            self.changed = self._create_restore_funcs()
        logger.debug("changed to be restored: {}".format(self.changed))

    def _update_service(self, app):
        share_image = app.get("share_image", None)
        if share_image:
            self.service.image = share_image
        self.service.cmd = app.get("cmd", "")
        self.service.version = app["version"]
        self.service.save()

    def _update_service_source(self, app, version, template_updatetime):
        new_extend_info = {}
        share_image = app.get("share_image", None)
        share_slug_path = app.get("share_slug_path", None)
        if share_image and app.get("service_image", None):
            new_extend_info = app["service_image"]
        if share_slug_path:
            slug_info = app.get("service_slug")
            slug_info["slug_path"] = share_slug_path
            new_extend_info = slug_info
        new_extend_info["source_deploy_version"] = app.get("deploy_version")

        if app.get("service_share_uuid", None):
            service_share_uuid = app.get("service_share_uuid")
        else:
            service_share_uuid = app.get("service_key", "")
        new_extend_info["source_service_share_uuid"] = service_share_uuid
        if template_updatetime:
            if type(template_updatetime) == datetime:
                new_extend_info["update_time"] = template_updatetime.strftime('%Y-%m-%d %H:%M:%S')
            elif type(template_updatetime) == str:
                new_extend_info["update_time"] = template_updatetime
        if self.install_from_cloud:
            new_extend_info["install_from_cloud"] = True
            new_extend_info["market"] = "default"
            new_extend_info["market_name"] = self.market_name
        data = {
            "extend_info": json.dumps(new_extend_info),
            "version": version,
        }
        logger.debug("service id: {}; data: {}; update service source.".format(self.service.service_id, data))
        service_source_repo.update_service_source(self.tenant.tenant_id, self.service.service_id, **data)

    def _update_deploy_version(self, dv):
        if not dv["is_change"]:
            return
        self.service.deploy_version = dv["new"]
        self.service.save()

    def _update_version(self, v):
        if not v["is_change"]:
            return
        self.service.version = v["new"]
        self.service.save()

    def _update_inner_envs(self, envs):
        self._update_envs(envs, "inner")

    def _update_outer_envs(self, envs):
        self._update_envs(envs, "outer")

    def _update_envs(self, envs, scope):
        if envs is None:
            return
        logger.debug("service id: {}; update envs; data: {}".format(self.service.service_id, envs))
        # create envs
        add = envs.get("add", [])
        for env in add:
            container_port = env.get("container_port", 0)
            value = env.get("attr_value", "")
            name = env.get("name", "")
            attr_name = env.get("attr_name", "")
            is_change = env.get("is_change", True)
            if not attr_name:
                continue
            if container_port == 0 and value == "**None**":
                value = self.service.service_id[:8]
            try:
                env_var_service.create_env_var(self.service, container_port, name, attr_name, value, is_change, scope)
            except (EnvAlreadyExist, InvalidEnvName) as e:
                logger.warning("failed to create env: {}; will ignore this env".format(e))

    def _update_component_graphs(self, component_graphs):
        if not component_graphs:
            return
        add = component_graphs.get("add", [])
        component_graph_service.bulk_create(self.service.service_id, add)

    def _update_component_monitors(self, component_monitors):
        if not component_monitors:
            return
        add = component_monitors.get("add", [])
        service_monitor_repo.bulk_create_component_service_monitors(self.tenant, self.service, add)

    def _sync_inner_envs(self, envs):
        self._sync_envs(envs, "inner")

    def _sync_outer_envs(self, envs):
        self._sync_envs(envs, "outer")

    def _sync_envs(self, envs, scope):
        """
        raise RegionApiBaseHttpClient.CallApiError
        """
        if envs is None:
            return
        logger.debug("service id: {}; sync envs; data: {}".format(self.service.service_id, envs))
        add = envs.get("add", [])
        for env in add:
            body = self._create_env_body(env, scope)
            if not body:
                continue
            try:
                region_api.add_service_env(self.service.service_region, self.tenant.tenant_name, self.service.service_alias,
                                           body)
            except region_api.CallApiError as e:
                if e.status == 400:
                    logger.warning("env name: {}; failed to create env: {}".format(env["attr_name"], e))
                    continue
                res = Dict({"status": e.status})
                raise region_api.CallApiError(e.apitype, e.url, e.method, res, e.body)

    def _restore_inner_envs(self, backup):
        self._restore_envs(backup, "inner")

    def _restore_outer_envs(self, backup):
        self._restore_envs(backup, "outer")

    def _restore_envs(self, backup, scope):
        backup_data = json.loads(backup.backup_data)
        if isinstance(backup_data, list):
            service_env_vars = backup_data[0].get("service_env_vars", [])
        else:
            service_env_vars = backup_data.get("service_env_vars", [])

        if not self.auto_restore:
            self.app_restore.envs(service_env_vars)

        body = {"scope": scope, "envs": []}
        for env in service_env_vars:
            if scope != env["scope"]:
                continue
            body["envs"].append(self._create_env_body(env, scope))
        try:
            region_api.restore_properties(self.service.service_region, self.tenant.tenant_name, self.service.service_alias,
                                          "/app-restore/envs", body)
        except RegionApiBaseHttpClient.CallApiError as e:
            # ignore restore envs error:
            logger.error("backup id: {}; failed to restore envs: {}".format(backup.backup_id, e))

    def _create_env_body(self, env, scope):
        """
        convert env to the body needed to add environment variables to the region
        """
        container_port = env.get("container_port", 0)
        if 'attr_name' not in env:
            return
        if container_port == 0 and env.get("attr_value") == "**None**":
            env["attr_value"] = self.service.service_id[:8]
        result = {
            "container_port": container_port,
            "tenant_id": self.service.tenant_id,
            "service_id": self.service.service_id,
            "name": env.get("name"),
            "attr_name": env["attr_name"],
            "attr_value": str(env.get("attr_value")),
            "is_change": True,
            "scope": scope,
            "env_name": env["attr_name"],
            "env_value": str(env.get("attr_value")),
            "enterprise_id": self.tenant.enterprise_id
        }
        return result

    def _create_envs_4_ports(self, port):
        container_port = int(port["container_port"])
        port_alias = self.service.service_alias.upper()
        host_env = {
            "name": "连接地址",
            "attr_name": port_alias + str(port["container_port"]) + "_HOST",
            "attr_value": "127.0.0.1",
            "is_change": False,
        }
        port_env = {
            "name": "端口",
            "attr_name": port_alias + str(port["container_port"]) + "_PORT",
            "attr_value": container_port,
            "is_change": False,
        }
        return [host_env, port_env]

    def update_port_data(self, port):
        container_port = int(port["container_port"])
        port_alias = self.service.service_alias.upper()
        k8s_service_name = port.get("k8s_service_name", self.service.service_alias)
        if k8s_service_name:
            filter_port = port_repo.get_by_k8s_service_name(self.tenant.tenant_id, k8s_service_name)
            if filter_port and self.service.service_id:
                k8s_service_name = k8s_service_name + "-" + make_uuid(
                )[-4:] if self.service.service_id != filter_port.service_id else k8s_service_name
            port["k8s_service_name"] = k8s_service_name
        port["tenant_id"] = self.tenant.tenant_id
        port["service_id"] = self.service.service_id
        port["mapping_port"] = container_port
        port["port_alias"] = port_alias

    def _update_ports(self, ports):
        if ports is None:
            return

        add = ports.get("add", [])
        envs = {"add": []}
        for port in add:
            self.update_port_data(port)
            port_repo.add_service_port(**port)
            if not port["is_inner_service"]:
                continue
            envs["add"].extend(self._create_envs_4_ports(port))
        upd = ports.get("upd", [])
        for port in upd:
            self.update_port_data(port)
            port_repo.update(**port)
        if not envs["add"]:
            return
        self._update_envs(envs, "outer")

    def _sync_ports(self, ports):
        """
        raise RegionApiBaseHttpClient.CallApiError
        """
        if ports is None:
            return
        add = ports.get("add", [])
        envs = {"add": []}
        for port in add:
            self.update_port_data(port)
            if not port["is_inner_service"]:
                continue
            envs["add"].extend(self._create_envs_4_ports(port))
        upd = ports.get("upd", [])
        for port in upd:
            self.update_port_data(port)
        add_body = {"port": add, "enterprise_id": self.tenant.enterprise_id}
        region_api.add_service_port(self.service.service_region, self.tenant.tenant_name, self.service.service_alias, add_body)
        upd_body = {"port": upd, "enterprise_id": self.tenant.enterprise_id}
        region_api.update_service_port(self.service.service_region, self.tenant.tenant_name, self.service.service_alias,
                                       upd_body)

        if envs:
            self._sync_outer_envs(envs)

    def _restore_ports(self, backup):
        backup_data = json.loads(backup.backup_data)
        if isinstance(backup_data, list):
            service_ports = backup_data[0].get("service_ports", [])
        else:
            service_ports = backup_data.get("service_ports", [])

        if not self.auto_restore:
            self.app_restore.ports(service_ports)

        try:
            body = {"ports": service_ports}
            region_api.restore_properties(self.service.service_region, self.tenant.tenant_name, self.service.service_alias,
                                          "/app-restore/ports", body)
        except RegionApiBaseHttpClient.CallApiError as e:
            # ignore restore ports error:
            logger.error("backup id: {}; failed to restore ports: {}".format(backup.backup_id, e))

    def _update_volumes(self, volumes):
        for volume in volumes.get("add"):
            volume["service_id"] = self.service.service_id
            host_path = "/grdata/tenant/{0}/service/{1}{2}".format(self.tenant.tenant_id, self.service.service_id,
                                                                   volume["volume_path"])
            volume["host_path"] = host_path
            file_content = volume.get("file_content", None)
            if file_content is not None:
                volume.pop("file_content")
            logger.debug("add volume {} for component {}".format(volume["volume_name"], self.service.service_id))
            v = volume_repo.add_service_volume(**volume)
            if not file_content and volume["volume_type"] != "config-file":
                continue
            file_data = {"service_id": self.service.service_id, "volume_id": v.ID, "file_content": file_content}
            _ = volume_repo.add_service_config_file(**file_data)
        for volume in volumes.get("upd"):
            # only volume of type config-file can be updated,
            # and only the contents of the configuration file can be updated.
            file_content = volume.get("file_content", None)
            if not file_content and volume["volume_type"] != "config-file":
                continue
            v = volume_repo.get_service_volume_by_name(self.service.service_id, volume["volume_name"])
            if not v:
                logger.warning("service id: {}; volume name: {}; failed to update volume: \
                    volume not found.".format(self.service.service_id, volume["volume_name"]))
            logger.debug("update volume {} for component {}".format(v.volume_name, self.service.service_id))
            cfg = volume_repo.get_service_config_file(v)
            cfg.file_content = file_content
            cfg.save()

    def _sync_volumes(self, volumes):
        """
        raise RegionApiBaseHttpClient.CallApiError
        """
        for volume in volumes.get("add"):
            volume["enterprise_id"] = self.tenant.enterprise_id
            try:
                region_api.add_service_volumes(self.service.service_region, self.tenant.tenant_name, self.service.service_alias,
                                               volume)
            except RegionApiBaseHttpClient.CallApiError as e:
                if not e.body or "is exist" not in e.body.msg:
                    logger.exception(e)
                    raise e

    def _restore_volumes(self, backup):
        backup_data = json.loads(backup.backup_data)
        if isinstance(backup_data, list):
            service_config_file = backup_data[0].get("service_config_file", [])
            service_volumes = backup_data[0].get("service_volumes", [])
        else:
            service_config_file = backup_data.get("service_config_file", [])
            service_volumes = backup_data.get("service_volumes", [])
        cfgfs = {item["volume_id"]: item["file_content"] for item in service_config_file}

        if not self.auto_restore:
            self.app_restore.volumes(service_volumes, service_config_file)

        body = {"volumes": []}
        for item in service_volumes:
            item_id = item.get("ID")
            if not item_id:
                item["file_content"] = ""
            else:
                item["file_content"] = cfgfs.get(item_id, "")
            body["volumes"].append(item)
        try:
            region_api.restore_properties(self.service.service_region, self.tenant.tenant_name, self.service.service_alias,
                                          "/app-restore/volumes", body)
        except RegionApiBaseHttpClient.CallApiError as e:
            # ignore restore volumes error:
            logger.error("backup id: {}; failed to restore volumes: {}".format(backup.backup_id, e))

    def _update_probe(self, probe):
        logger.debug("probe: {}".format(probe))
        add = probe.get("add")
        if add:
            add["probe_id"] = make_uuid()
            probe_repo.update_or_create(self.service.service_id, add)
        upd = probe.get("upd", None)
        if upd:
            probe_repo.update_or_create(self.service.service_id, upd)

    def _sync_probe(self, probe):
        """
        raise RegionApiBaseHttpClient.CallApiError
        """
        p = probe_repo.get_probe(self.service.service_id)
        data = p.to_dict()
        data["is_used"] = 1 if data["is_used"] else 0
        add = probe.get("add", None)
        if add:
            region_api.add_service_probe(self.service.service_region, self.tenant.tenant_name, self.service.service_alias, data)
        upd = probe.get("upd", None)
        if upd:
            region_api.update_service_probec(self.service.service_region, self.tenant.tenant_name, self.service.service_alias,
                                             data)

    def _restore_probe(self, backup):
        backup_data = json.loads(backup.backup_data)
        if isinstance(backup_data, list):
            pd = backup_data[0].get("service_probes", [])
        else:
            pd = backup_data.get("service_probes", [])
        if pd:
            probe = pd[0]
            probe["is_used"] = 1 if probe["is_used"] else 0
        else:
            probe = ""

        if not self.auto_restore:
            self.app_restore.probe(probe)

        try:
            region_api.restore_properties(self.service.service_region, self.tenant.tenant_name, self.service.service_alias,
                                          "/app-restore/probe", probe)
        except RegionApiBaseHttpClient.CallApiError as e:
            # ignore restore probe error:
            logger.error("backup id: {}; failed to restore probe: {}".format(backup.backup_id, e))

    def _update_dep_services(self, dep_services):
        def create_dep_service(dep_service_id):
            try:
                app_relation_service.create_service_relation(self.tenant, self.service, dep_service_id)
            except (ErrDepServiceNotFound, ServiceRelationAlreadyExist, InnerPortNotFound) as e:
                logger.warning("failed to create service relation: {}".format(e))

        add = dep_services.get("add", [])
        for dep_service in add:
            create_dep_service(dep_service["service_id"])

    def _sync_dep_services(self, dep_services):
        def sync_dep_service(dep_service_id):
            """
            raise RegionApiBaseHttpClient.CallApiError
            """
            dep_service = service_repo.get_service_by_service_id(dep_service_id)
            body = dict()
            body["dep_service_id"] = dep_service.service_id
            body["tenant_id"] = self.tenant.tenant_id
            body["dep_service_type"] = dep_service.service_type
            body["enterprise_id"] = self.tenant.enterprise_id
            region_api.add_service_dependency(self.service.service_region, self.tenant.tenant_name, self.service.service_alias,
                                              body)

        add = dep_services.get("add", [])
        for dep_service in add:
            sync_dep_service(dep_service["service_id"])

    def _restore_dep_services(self, backup):
        backup_data = json.loads(backup.backup_data)
        if isinstance(backup_data, list):
            service_relation = backup_data[0].get("service_relation", [])
        else:
            service_relation = backup_data.get("service_relation", [])

        if not self.auto_restore:
            self.app_restore.dep_services(service_relation)

        body = {"deps": []}
        for item in service_relation:
            body["deps"].append({
                "dep_service_id": item["dep_service_id"],
                "dep_service_type": item["dep_service_type"],
            })
        try:
            region_api.restore_properties(self.service.service_region, self.tenant.tenant_name, self.service.service_alias,
                                          "/app-restore/deps", body)
        except RegionApiBaseHttpClient.CallApiError as e:
            # ignore restore service relations error:
            logger.error("backup id: {}; failed to restore service relations: {}".format(backup.backup_id, e))

    def _update_dep_volumes(self, dep_volumes):
        def create_dep_vol(dep_volume):
            data = {
                "service_id": dep_volume["service_id"],
                "volume_name": dep_volume["mnt_name"],
                "path": dep_volume["mnt_dir"]
            }
            try:
                mnt_service.create_service_volume(self.tenant, self.service, data)
            except (ErrInvalidVolume, ErrDepVolumeNotFound) as e:
                logger.warning("failed to create dep volume: {}".format(e))

        add = dep_volumes.get("add", [])
        for dep_volume in add:
            create_dep_vol(dep_volume)

    def _sync_dep_volumes(self, dep_volumes):
        def sync_dep_vol(dep_vol_info):
            """
            raise RegionApiBaseHttpClient.CallApiError
            """
            dep_vol = volume_repo.get_service_volume_by_name(dep_vol_info["service_id"], dep_vol_info["mnt_name"])
            if dep_vol is None:
                logger.warning("dep service id: {}; volume name: {}; fail to \
                    sync dep volume: dep volume not found".format(dep_vol_info["service_id"], dep_vol_info["mnt_name"]))
                return
            data = {
                "depend_service_id": dep_vol.service_id,
                "volume_name": dep_vol.volume_name,
                "volume_path": dep_vol_info['mnt_dir'].strip(),
                "enterprise_id": self.tenant.enterprise_id,
                "volume_type": dep_vol.volume_type
            }
            if dep_vol.volume_type == "config-file":
                config_file = volume_repo.get_service_config_file(dep_vol)
                data["file_content"] = config_file.file_content
            region_api.add_service_dep_volumes(self.service.service_region, self.tenant.tenant_name, self.service.service_alias,
                                               data)

        add = dep_volumes.get("add", [])
        for dep_vol in add:
            sync_dep_vol(dep_vol)

    def _restore_dep_volumes(self, backup):
        backup_data = json.loads(backup.backup_data)
        if isinstance(backup_data, list):
            dep_vols = backup_data[0].get("service_mnts", [])
        else:
            dep_vols = backup_data.get("service_mnts", [])

        if not self.auto_restore:
            self.app_restore.dep_volumes(dep_vols)

        body = {"dep_vols": []}
        for dv in dep_vols:
            body["dep_vols"].append({
                "dep_service_id": dv["dep_service_id"],
                "volume_path": dv["mnt_dir"],
                "volume_name": dv["mnt_name"]
            })
        try:
            region_api.restore_properties(self.service.service_region, self.tenant.tenant_name, self.service.service_alias,
                                          "/app-restore/depvols", body)
        except RegionApiBaseHttpClient.CallApiError as e:
            # ignore restore service dependent volumes error:
            logger.error("backup id: {}; failed to restore service dependent volumes: {}".format(backup.backup_id, e))

    def _update_plugins(self, plugins):
        logger.debug("start updating plugins; plugin datas: {}".format(plugins))
        add = plugins.get("add", [])
        try:
            app_plugin_service.create_plugin_4marketsvc(self.service.service_region, self.tenant, self.service,
                                                        self.template["apps"], self.version, add)
        except ServiceHandleException as e:
            logger.exception(e)

        delete = plugins.get("delete", [])
        for plugin in delete:
            app_plugin_service.delete_service_plugin_relation(self.service, plugin["plugin_id"])
            app_plugin_service.delete_service_plugin_config(self.service, plugin["plugin_id"])

    def _sync_plugins(self, plugins):
        """
        raise RegionApiBaseHttpClient.CallApiError
        """
        logger.debug("start syncing plugins; plugin datas: {}".format(plugins))
        add = plugins.get("add", [])
        for plugin in add:
            data = app_plugin_service.build_plugin_data_4marketsvc(self.tenant, self.service, plugin)
            if data:
                region_api.install_service_plugin(self.service.service_region, self.tenant.tenant_name,
                                                  self.service.service_alias, data)

        delete = plugins.get("delete", [])
        for plugin in delete:
            region_api.uninstall_service_plugin(self.service.service_region, self.tenant.tenant_name, plugin["plugin_id"],
                                                self.service.service_alias)

    def _sync_component_monitors(self, component_monitors):
        logger.debug("start syncing component_monitors; component_monitors datas: {}".format(component_monitors))
        monitors = service_monitor_repo.list_by_service_ids(self.tenant.tenant_id, [self.service.service_id])
        for monitor in monitors:
            req = {
                "name": monitor.name,
                "path": monitor.path,
                "port": monitor.port,
                "service_show_name": monitor.service_show_name,
                "interval": monitor.interval,
            }
            try:
                region_api.create_service_monitor(self.tenant.enterprise_id, self.service.service_region,
                                                  self.tenant.tenant_name, self.service.service_alias, req)
            except RegionApiBaseHttpClient.CallApiError as e:
                logger.error("failed to create_component_monitor: {}".format(e))

    def _restore_plugins(self, backup):
        backup_data = json.loads(backup.backup_data)
        if isinstance(backup_data, list):
            relations = backup_data[0].get("service_plugin_relation", [])
        else:
            relations = backup_data.get("service_plugin_relation", [])

        if not self.auto_restore:
            self.app_restore.plugins(relations)

        body = {"plugins": []}
        for r in relations:
            body["plugins"].append({
                "plugin_id": r["plugin_id"],
                "version_id": r["build_version"],
                "switch": r["plugin_status"],
            })
        try:
            region_api.restore_properties(self.service.service_region, self.tenant.tenant_name, self.service.service_alias,
                                          "/app-restore/plugins", body)
        except RegionApiBaseHttpClient.CallApiError as e:
            # ignore restore service plugins error:
            logger.error("backup id: {}; failed to restore service plugins: {}".format(backup.backup_id, e))

    def _restore_service(self, backup):
        backup_data = json.loads(backup.backup_data)
        if isinstance(backup_data, list):
            service_base = backup_data[0].get("service_base", None)
        else:
            service_base = backup_data.get("service_base", None)
        if not self.app_restore:
            self.app_restore.svc(service_base)

    def _restore_service_source(self, backup):
        backup_data = json.loads(backup.backup_data)
        if isinstance(backup_data, list):
            service_source = backup_data[0].get("service_source", None)
        else:
            service_source = backup_data.get("service_source", None)
        if not self.auto_restore:
            self.app_restore.svc_source(service_source)


from urllib.request import urlopen
from bs4 import BeautifulSoup
import re

def ngrams(input, n):
    input = re.sub('\n+', ' ', input)  # 去掉连续转义字符\n
    input = re.sub(' +', ' ', input)   # 去掉连续空格
    input = bytes(input,'UTF-8')
    input = input.decode('ascii', 'ignore')
    input = input.split(' ')
    output = []
    for i in range(len(input)-n+1):
        output.append(input[i:i+n])
    return output

html = urlopen("http://en.wikipedia.org/wiki/Python_(programming_language)")
soup = BeautifulSoup(html,'html.parser')
content = soup.find('div',{'id':'mw-content-text'}).get_text()
test = ngrams(content,2)
print(test)
print('2-grams count is: '+str(len(test)))

# 消除转义字符的是下一行代码的ignore参数，转义字符因为不能被ascii decode，如果不加ignore就会报错，加了ignore就会被忽略。
# contents是一个str，由一系列不可改变的Unicode字符组成，本身不能被decode，用utf8 encode之后转为bytes才可以被decode。
# encode是转为计算机能理解的二进制数，bytes就是一系列不可改变的介于0-255之间的数字。decode就是转为我们可以理解的字符。
# ascii字符集远小于unicode字符集，用ascii decode的时候那些不在ascii字符集的字符就会导致报错，设了ignore参数后就会被忽略。


import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

from scipy.stats import spearmanr, gaussian_kde, binned_statistic, binned_statistic_2d
from scipy.interpolate import interpn

from .utils import fix_aspect_ratio, bin_stat

def corner_scatter(x,y,ax,**kwargs):
    '''a simple scatter plot'''

    ax.scatter(x,y,**kwargs)

    return 0

def corner_binned_stat(x,y,ax,bins=10,color='black',**kwargs):
    '''a scatter plot with binned trends in y'''

    xlim = ax.get_xlim()
    #x,mean,std = bin_stat(x,y,bins=bins,range=xlim,statistic='median')

    # just ignore nan values
    x, y = x[np.isfinite(x) & np.isfinite(y)], y[np.isfinite(x) & np.isfinite(y)]
    mean, edges, _ = binned_statistic(x,y,statistic='median',bins=bins,range=xlim)
    x = (edges[1:]+edges[:-1])/2

    ax.errorbar(x,mean,yerr=None,fmt='-',color=color,**kwargs)

    return 0

def corner_density_scatter(x,y,ax,nbins=10,**kwargs):
    '''scatter plot with point density from histogram'''

    xlim=ax.get_xlim()
    ylim=ax.get_ylim()

    hist, x_e, y_e = np.histogram2d(x,y,bins=nbins,range=[xlim,ylim],density=True)
    z = interpn((0.5*(x_e[1:] + x_e[:-1]) , 0.5*(y_e[1:]+y_e[:-1]) ),hist,np.vstack([x,y]).T,method="linear",bounds_error=False)
    vmin,vmax=np.nanpercentile(z,[15,95])
    sc=ax.scatter(x,y,c=z,vmin=vmin,vmax=vmax,**kwargs)

    return 0

def corner_density_histogram(x,y,ax,nbins=10,**kwargs):
    '''2d histogram of point density

    https://stackoverflow.com/a/53865762
    '''

    xlim=ax.get_xlim()
    ylim=ax.get_ylim()

    hist, x_e, y_e = np.histogram2d(x,y,bins=nbins,range=[xlim,ylim],density=True)
    hist[hist==0]  = np.nan
    im = ax.imshow(hist.T,origin='lower',extent=[*xlim,*ylim],**kwargs)

    return 0

def corner_gaussian_kde_scatter(x,y,ax,**kwargs):
    '''use a gaussian kernel density estimate for the point density'''

    xy = np.vstack([x,y])
    z = gaussian_kde(xy)(xy)
    idx = z.argsort()
    x, y, z = x[idx], y[idx], z[idx]
    sc=ax.scatter(x,y,c=z,**kwargs)

    return 0

def corner_binned_stat2d(x,y,ax,z,nbins=10,**kwargs):
    '''make a scatter plot of x and y with the color based on binned stats of z'''

    xlim=ax.get_xlim()
    ylim=ax.get_ylim()

    x,y,z = x[~np.isnan(x) & ~np.isnan(y) & ~np.isnan(z)], y[~np.isnan(x) & ~np.isnan(y) & ~np.isnan(z)], z[~np.isnan(x) & ~np.isnan(y) & ~np.isnan(z)]

    stat ,x_e, y_e, _ = binned_statistic_2d(x,y,z,bins=nbins,range=[xlim,ylim])
    z = interpn((0.5*(x_e[1:] + x_e[:-1]) , 0.5*(y_e[1:]+y_e[:-1]) ),stat,np.vstack([x,y]).T,method="nearest",bounds_error=False)
    sc=ax.scatter(x,y,c=z,**kwargs)

    return 0

def corner_binned_stat2d_histogram(x,y,ax,z,nbins=10,**kwargs):
    '''show z binned based on x and y'''

    xlim=ax.get_xlim()
    ylim=ax.get_ylim()

    x,y,z = x[~np.isnan(x) & ~np.isnan(y) & ~np.isnan(z)], y[~np.isnan(x) & ~np.isnan(y) & ~np.isnan(z)], z[~np.isnan(x) & ~np.isnan(y) & ~np.isnan(z)]

    stat ,x_e, y_e, _ = binned_statistic_2d(x,y,z,bins=nbins,range=[xlim,ylim])
    im = ax.imshow(stat.T,origin='lower',extent=[*xlim,*ylim],**kwargs)

    return 0

def corner_binned_percentile(x,y,ax,nbins=10,range=None,n=68,**kwargs):
    '''plot the n percentile of y along the xaxis'''

    x,y = x[~np.isnan(x) & ~np.isnan(y)], y[~np.isnan(x) & ~np.isnan(y)]

    pl, edges, _ = binned_statistic(x,y,statistic=lambda data: np.percentile(data,50+n/2),bins=nbins,range=range)
    pu, edges, _ = binned_statistic(x,y,statistic=lambda data: np.percentile(data,50-n/2),bins=nbins,range=range)
    xp = (edges[1:]+edges[:-1])/2
    ax.fill_between(xp,y1=pl,y2=pu,**kwargs)

def corner_violin(x,y,ax,positions,**kwargs):
    '''create violin plots for the data'''

    #x,y = x[~np.isnan(y)],y[~np.isnan(y)]

    bins = (positions[1:]+positions[:-1])/2
    binned_data = [y[(bins[i]<x) & (x<bins[i+1])] for i in range(len(bins)-1)]

    ax.violinplot(binned_data,positions=positions[1:-1],**kwargs)

    return binned_data

def corner_spearmanr(x,y,ax,position=(0.93,0.93),pvalue=False,**kwargs):
    '''calculate Spearman correlation coefficient'''

    not_nan = ~np.isnan(x) & ~np.isnan(y)
    r,p = spearmanr(x[not_nan],y[not_nan])

    label = r'$\rho'+f'={r:.2f}$'
    if pvalue: label+=f', p={100*p:.3f}\%'
    t = ax.text(*position,label,transform=ax.transAxes,ha='right',va='top',**kwargs)
    t.set_bbox(dict(facecolor='white', alpha=1, ec='white'))

def corner(table,columns,function=None,histogram=False,limits={},labels={},scale={},filename=None,figsize=10,aspect_ratio=1,**kwargs):
    '''Create a pairwise plot for all names in columns

    Parameters
    ----------

    table : table with the data

    columns : list of the columns that should be used

    limits : dictionary with the limits (tuple) for the columns

    labels : dictionary with the axis labels

    filename : name of the file to save to
    '''

    # create a figure with the correct proportions
    nrows, ncols = len(columns)-1, len(columns)-1

    if histogram:
        nrows+=1
        ncols+=1

    fig, axes = plt.subplots(nrows=nrows,ncols=ncols,figsize=(figsize,figsize*aspect_ratio*ncols/nrows))
    for i,row in enumerate(columns[1:]):
        for j,col in enumerate(columns[:-1]):
            ax=axes[i,j]
            if j>i:
                ax.remove()
            else:
                # we need to set the scale before
                ax.set(xscale=scale.get(col,'linear'),yscale=scale.get(row,'linear'))
                if j==0:
                    ax.set_ylabel(labels.get(row,row.replace("_","")))
                else:
                    ax.set_yticklabels([])
                if i==len(columns)-2:
                    ax.set_xlabel(labels.get(col,col.replace("_","")))
                else:
                    ax.set_xticklabels([])

                # we set the axis limit before we plot the data (some functions might need the limits)
                xlim = limits.get(col,[np.min(table[col]),np.max(table[col])])
                ylim = limits.get(row,[np.min(table[row]),np.max(table[row])])
                ax.set(xlim=xlim,ylim=ylim)

                # make sure there are no NaN in the data points
                x,y = table[col],table[row]
                x,y = x[np.isfinite(x) & np.isfinite(y)], y[np.isfinite(x) & np.isfinite(y)]
                #x,y = x[(xlim[0]<=x) & (x<=xlim[1]) & (ylim[0]<=y) & (y<=ylim[1])],y[(xlim[0]<=x) & (x<=xlim[1]) & (ylim[0]<=y) & (y<=ylim[1])]

                # either use a special function for the plot or just use a scatter plot
                if function:
                    function(x,y,ax,xlim=xlim)
                else:
                    ax.scatter(x,y,**kwargs)
            fix_aspect_ratio(ax,aspect_ratio=aspect_ratio)

    plt.subplots_adjust(wspace=0.12, hspace=0.12)

    if filename:
        plt.savefig(filename,dpi=600)

    plt.show()

def corner_by_galaxy(table,sample_table,columns,limits={},labels={},nbins=7,filename=None,figsize=10,aspect_ratio=1,**kwargs):
    '''Create a pairwise plot for all names in columns

    Parameters
    ----------

    table : table with the data

    columns : list of the columns that should be used

    limits : dictionary with the limits (tuple) for the columns

    labels : dictionary with the axis labels

    filename : name of the file to save to
    '''

    cmap = mpl.cm.get_cmap('plasma')
    norm = mpl.colors.Normalize(vmin=9.4,vmax=11)

    # create a figure with the correct proportions
    nrows, ncols = len(columns)-1, len(columns)-1

    fig, axes = plt.subplots(nrows=nrows,ncols=ncols,figsize=(figsize,figsize*aspect_ratio*ncols/nrows))
    for i,row in enumerate(columns[1:]):
        for j,col in enumerate(columns[:-1]):
            ax=axes[i,j]
            if j>i:
                ax.remove()
            else:
                if j==0:
                    ax.set_ylabel(labels.get(row,row.replace("_","")))
                else:
                    ax.set_yticklabels([])
                if i==len(columns)-2:
                    ax.set_xlabel(labels.get(col,col.replace("_","")))
                else:
                    ax.set_xticklabels([])

                # we set the axis limit before we plot the data (some functions might need the limits)
                xlim = limits.get(col,[np.min(table[col]),np.max(table[col])])
                ylim = limits.get(row,[np.min(table[row]),np.max(table[row])])
                ax.set(xlim=xlim,ylim=ylim)

                for k,gal_name in enumerate(np.unique(table['gal_name'])):

                    tmp = table[table['gal_name']==gal_name]

                    # make sure there are no NaN in the data points
                    x,y = tmp[col],tmp[row]
                    x,y = x[(xlim[0]<=x) & (x<=xlim[1]) & (ylim[0]<=y) & (y<=ylim[1])],y[(xlim[0]<=x) & (x<=xlim[1]) & (ylim[0]<=y) & (y<=ylim[1])]
                    x, y = x[~np.isnan(y) & np.isfinite(y)], y[~np.isnan(y) & np.isfinite(y)]

                    if len(x)>0:
                        mean, edges, _ = binned_statistic(x,y,statistic='median',bins=nbins,range=xlim)
                        std, _, _ = binned_statistic(x,y,statistic='std',bins=nbins,range=xlim)
                        x,mean,std = (edges[1:]+edges[:-1])/2,mean,std
                        ax.errorbar(x,mean,fmt='o-',ms=1.5,color=cmap(norm(sample_table.loc[gal_name]['mass'])),label=gal_name)

                x,y = table[col],table[row]
                x,y = x[(xlim[0]<=x) & (x<=xlim[1]) & (ylim[0]<=y) & (y<=ylim[1])],y[(xlim[0]<=x) & (x<=xlim[1]) & (ylim[0]<=y) & (y<=ylim[1])]
                x, y = x[~np.isnan(y) & np.isfinite(y)], y[~np.isnan(y) & np.isfinite(y)]

                mean, edges, _ = binned_statistic(x,y,statistic='median',bins=7,range=xlim)
                std, _, _ = binned_statistic(x,y,statistic='std',bins=7,range=xlim)
                x,mean,std = (edges[1:]+edges[:-1])/2,mean,std
                ax.errorbar(x,mean,fmt='o-',ms=1.5,color='black',label=gal_name)

            fix_aspect_ratio(ax,aspect_ratio=aspect_ratio)

    plt.subplots_adjust(wspace=0.12, hspace=0.12)

    if filename:
        plt.savefig(filename,dpi=600)

    plt.show()


import pygame
import math
'''
def show_splash_screen():
    splash_font = pygame.font.SysFont('Sans-Serif', 50)
    splash_text = splash_font.render('Welcome to Platon!', True, (255, 255, 255))

    # Initial position of the splash text
    text_x = 350
    text_y = 250

    start_time = pygame.time.get_ticks()  # Get the start time

    while pygame.time.get_ticks() - start_time < 2000:  # Display for 2 seconds
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()

        screen.fill((0, 0, 0))
        screen.blit(splash_text, (text_x, text_y))
        pygame.display.flip()

        # Update the position of the text to create the floating effect
        text_y += math.sin(pygame.time.get_ticks() * 0.01) * 2  # Adjust the values for desired movement

        pygame.time.Clock().tick(60)
'''
pygame.init()
pos_x, pos_y = 200, 1000
enemy_x, enemy_y = 800, 420
heal_x, heal_y = 2000, 450
round_heal_x, round_heal_y = 2200, 450

distance_threshold = 100
playerHP = 100
enemyHP = 100
attack_cooldown = 0  # Timer in milliseconds
attack_enemy_cooldown = 0  # Timer for enemy attack in milliseconds
roundHP = 0
kills = 0

screen = pygame.display.set_mode((1920, 1080))
pygame.display.set_caption('Hello World')

font = pygame.font.SysFont('Sans-Serif', 40)

clock = pygame.time.Clock()

# Load the images
player_stand = pygame.image.load('data/images/player_stand.png').convert_alpha()
player_stand = pygame.transform.scale(player_stand, (95, 90))

enemy_stand = pygame.image.load('data/images/enemy_stand.png').convert_alpha()
enemy_stand = pygame.transform.scale(enemy_stand, (95, 90))

sword_image = pygame.image.load('data/images/knife.png').convert_alpha()
sword_image = pygame.transform.scale(sword_image, (60, 50))  # Adjust size as needed

bg_image = pygame.image.load('data/images/bg.png').convert_alpha()
bg_image = pygame.transform.scale(bg_image, (1920, 1080))

sword_swing_angle = 60

# The speed of swinging (adjust this value to control the swinging speed)
sword_swing_speed = 1

# Position the sword relative to the player's position
sword_offset_x = 61
sword_offset_y = 30

# The initial swing angle
current_swing_angle = 0
sword_swing_direction = 0
#distance
def distance_between_points(x1, y1, x2, y2):
    return math.sqrt((x2 - x1)**2 + (y2 - y1)**2)

sword_swing_angle = 60

# The speed of swinging (adjust this value to control the swinging speed)
sword_swing_speed = 0.1

# Position the sword relative to the player's position
sword_offset_x = 61
sword_offset_y = 30

# The initial swing angle
current_swing_angle = 0

def draw_sword():
    global current_swing_angle

    # Calculate the new swing angle using the sine function
    current_swing_angle = (current_swing_angle + sword_swing_speed) % 360

    # Calculate the rotation of the sword image based on the current swing angle
    rotated_sword = pygame.transform.rotate(sword_image, current_swing_angle - sword_swing_angle)

    # Position the rotated sword relative to the player's position
    sword_x = pos_x + sword_offset_x
    sword_y = pos_y + sword_offset_y

    # Draw the sword
    screen.blit(rotated_sword, (sword_x, sword_y))

# playerz
def player():
    global moving_rect, pos_x, pos_y
    #pygame.draw.rect(screen, (51, 51, 255), moving_rect)
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        pos_x -= 10
    if keys[pygame.K_RIGHT]:
        pos_x += 10
    if keys[pygame.K_UP]:
        pos_y -= 10
    if keys[pygame.K_DOWN]:
        pos_y += 10
    if keys[pygame.K_a]:
        pos_x -= 10
    if keys[pygame.K_d]:
        pos_x += 10
    if keys[pygame.K_w]:
        pos_y -= 10
    if keys[pygame.K_s]:
        pos_y += 10
    if pos_x < -15:
        pos_x = -15
    if pos_x > 1840:
        pos_x = 1840
    if pos_y < -15:
        pos_y = -15
    if pos_y > 1000:
        pos_y = 1000

    current_image = player_stand
    # Draw the player
    screen.blit(current_image, (pos_x, pos_y))
    draw_sword()
# enemy
def enemy():
    global enemy_x, enemy_y
    #pygame.draw.rect(screen, (255, 51, 51), enemy_rect)

    # Move the enemy towards the player at a constant speed
    if enemy_x > pos_x:
        enemy_x -= 1
    elif enemy_x < pos_x:
        enemy_x += 1

    current_enemy_image = enemy_stand
    # Draw the player
    screen.blit(current_enemy_image, (enemy_x, enemy_y))

def heal_player():
    global heal_x, heal_y
    pygame.draw.rect(screen, (255, 255, 255), heal_rect)

def round_heal():
    global round_heal_x, round_heal_y
    pygame.draw.rect(screen, (0, 156, 73), round_rect)
# attacking the player
def attack_player():
    global playerHP, attack_cooldown
    dist = distance_between_points(pos_x, pos_y, enemy_x, enemy_y)

    if dist <= distance_threshold and attack_cooldown <= 0:
        #print("Attacking the player!")
        playerHP -= 7
        attack_cooldown = 2000
# attacking the enemy
def attack_enemy():
    global enemyHP, attack_enemy_cooldown
    keys = pygame.key.get_pressed()
    dist = distance_between_points(pos_x, pos_y, enemy_x, enemy_y)

    if dist <= distance_threshold and attack_enemy_cooldown <= 0:
        # Check for mouse events
        #if keys[pygame.K_1]:
        mouse_buttons = pygame.mouse.get_pressed()
        if mouse_buttons[0]:
                #print('Attacking the enemy!')
                enemyHP -= 15
                attack_enemy_cooldown = 1500
        if keys[pygame.K_1]:
            #print('Attacking the enemy!')
            enemyHP -= 15
            attack_enemy_cooldown = 1500
# drawings
def draw_player_hp():
    player_hp_text = font.render(f'Numbis HP: {playerHP}', False, (255, 255, 255))
    screen.blit(player_hp_text, (10, 10))

def draw_enemy_hp():
    enemy_hp_text = font.render(f'Prixie HP: {enemyHP}', False, (255, 255, 255))
    screen.blit(enemy_hp_text, (10, 40))

def draw_round_hp():
    round_text = font.render(f'Rounds: {roundHP}', False, (255, 255, 255))
    screen.blit(round_text, (10, 70))

def draw_kills():
    kills_text = font.render(f'Kills: {kills}', False, (255, 255, 255))
    screen.blit(kills_text, (10, 100))

#show_splash_screen()
running = True
while running:
    screen.blit(bg_image, (0, 0))
    #rects
    moving_rect = pygame.Rect(pos_x, pos_y, 55, 50)
    enemy_rect = pygame.Rect(enemy_x, enemy_y, 55, 50)
    heal_rect = pygame.Rect(heal_x, heal_y, 35, 30)
    round_rect = pygame.Rect(round_heal_x, round_heal_y, 35, 30)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # Updating attack cooldown
    if attack_cooldown > 0:
        attack_cooldown -= clock.get_time()

    if attack_enemy_cooldown > 0:
        attack_enemy_cooldown -= clock.get_time()

    # If player is dead, pos_x and pos_y are reset to pos_x = 200 and pos_y = 440
    if playerHP < 0:
        pos_x = 200
        pos_y = 420
        enemy_x = 800
        enemy_y = 420
        playerHP = 100
    if enemyHP <= 0:
        enemy_x = 800
        enemy_y = 420
        enemyHP = 100
        heal_x = 200
        roundHP += 1
        kills += 1
    if moving_rect.colliderect(heal_rect):
        playerHP += 30
        heal_x = 2000
    if roundHP == 3:
        round_heal_x = 200
        heal_x = 1200
    if moving_rect.colliderect(round_rect):
        playerHP += 50
        round_heal_x = 2200
        roundHP = 0
    attack_player()
    attack_enemy()

    #screen.fill((52, 52, 52))
    draw_player_hp()
    draw_enemy_hp()
    draw_round_hp()
    draw_kills()
    '''
    playername = font.render('Numbis', False, (255, 255, 255))
    screen.blit(playername, (180, 390))

    enemyname = font.render('Prixie', False, (255, 255, 255))
    screen.blit(enemyname, (790, 390))
    '''

    player()
    enemy()
    heal_player()
    round_heal()
    #print(clock.get_fps())
    pygame.display.flip()
    clock.tick(60)

pygame.quit()


"""
file_management
Contains the main functionality for managing the files
Most functions have complete functionality and don't
return any value, printing the process output
Authors:
    Ana Calzada
    Martin Sanchez
"""

import re
import os
import sys
import requests
import encryption
from Cryptodome.PublicKey import RSA

token = 'IMPORT TOKEN HERE'

def upload(file, dest_id):
    """ Upload a file.
    Given the file (path) and the id of the destinatary, it
    encrypts and signs the file and then uploads it.
    """
    # Get file and private key
    data, filename = read_from_file(file)
    if data is None:
        return

    key_pr = encryption.get_key()
    if key_pr is None:
        print('\nError: No private key found, create an id first with --create_id', file=sys.stderr)
        return

    # Sign file
    print('Solicitando envío de fichero a SecureBox')
    print('-> Firmando fichero...', end='', flush=True)
    try:
        signature, data = encryption.sign_data(data, key_pr)
    except:
        print('\nError: Could not sign file, error with private key', file=sys.stderr)
        return
    print('OK', flush=True)

    # Retrieve public key from dest
    print('-> Recuperando clave pública de ID {0}...'.format(dest_id), end='', flush=True)
    r = encryption.request_public_key(dest_id)
    if r.status_code != 200:
        print_error_server(r)
        return
    key_pu = RSA.import_key(r.json()['publicKey'])
    print('OK', flush=True)

    # Encrypt with public key
    print('-> Cifrando fichero...', end='', flush=True)
    try:
        iv, enc_session_key, enc_data = encryption.encrypt_data(signature+data, key_pu)
    except:
        print('\nError: Could not encrypt file, error with public key', file=sys.stderr)
        return
    to_send = iv + enc_session_key + enc_data
    print('OK', flush=True)

    # Upload
    print('-> Subiendo fichero al servidor...', end='', flush=True)
    url = 'https://vega.ii.uam.es:8080/api/files/upload'
    file = {'ufile': (filename, to_send)}
    headers = {'Authorization': 'Bearer '+token}
    r = requests.post(url, files=file, headers=headers)

    if r.status_code != 200:
        print_error_server(r)
        return
    print('OK', flush=True)
    print('Subida realizada correctamente, ID del fichero: {0}'.format(r.json()['file_id']))
    return

def download_file(file_id, src_id):
    """ Downloads a file.
    Given a file id (created on upload), it fetches it from
    the server and then tries to decrypt it.
    """
    # Get private key to decrypt
    key_pr = encryption.get_key()
    if key_pr is None:
        print('\nError: No private key found, create an id first with --create_id', file=sys.stderr)
        return

    # Fetch file
    print('Descargando fichero de SecureBox...', end='',flush=True)
    url = 'https://vega.ii.uam.es:8080/api/files/download'
    args = {'file_id': file_id}
    headers = {'Authorization': 'Bearer '+token}
    r = requests.post(url, json=args, headers=headers)

    if r.status_code != 200:
        print_error_server(r)
        return
    content = r.content

    # Retrieve original filename with regex
    try:
        regex = r"(?<=filename=\")(?:[^\"\\]|\\.)*(?=\")"
        matches = re.findall(regex, r.headers['Content-Disposition'])
        if len(matches) > 0:
            filename = matches[0]
        else:
            filename = 'download'
    except:
        filename = 'download'

    print('OK',flush=True)
    print('-> {0} bytes descargados correctamente'.format(len(content)),flush=True)

    # Decrypt content and verify
    print('-> Descifrando fichero...',end='',flush=True)
    try:
        iv, enc_session_key, enc_data = encryption.get_encrypted_components(content)
        data = encryption.decrypt_data(iv, enc_session_key, enc_data, key_pr)
    except:
        print('\nError: Could not decrypt file, invalid private key', file=sys.stderr)
        return
    print('OK', flush=True)

    # Get public key to verify
    print('-> Recuperando clave pública de ID {0}...',end='',flush=True)
    r = encryption.request_public_key(src_id)
    if r.status_code != 200:
        print_error_server(r)
        return
    key_pu = RSA.import_key(r.json()['publicKey'])
    print('OK',flush=True)

    print('-> Verificando firma...',end='',flush=True)
    signature, data = encryption.get_signed_components(data)
    if encryption.verify_data(signature, data, key_pu):
        print('OK',flush=True)
    else:
        print('\nError: Sign verification failed', file=sys.stderr)

    if write_in_file(filename, data):
        print('Fichero \'{0}\' descargado y verificado correctamente'.format(filename))
    return

def list_files():
    """ Prints a list of files.
    Accesses the server to retrieve the list of files under the
    current user.
    """
    # Make request
    url = 'https://vega.ii.uam.es:8080/api/files/list'
    headers = {'Authorization': 'Bearer '+token}

    print('Recuperando lista de ficheros subidos al servidor...',end='',flush=True)
    r = requests.post(url, headers=headers)

    if r.status_code != 200:
        print_error_server(r)
        return
    print('OK',flush=True)

    # Print results
    json = r.json()
    num = json['num_files']
    files = json['files_list']
    print('Se han encontrado {0} ficheros:'.format(num))
    for i in range(num):
        print('[{index}] {name}, ID: {id}'.format(index=i, name=files[i]['fileName'], id=files[i]['fileID']))

    return

def delete_file(id):
    """ Deletes a files.
    Given a valid file id (of a file owned by the user), it
    tries to delete it from the server.
    """
    url = 'https://vega.ii.uam.es:8080/api/files/delete'
    args = {'file_id': id}
    headers = {'Authorization': 'Bearer '+token}

    print('Solicitando borrado del fichero #{0}...'.format(id),end='',flush=True)
    r = requests.post(url, json=args, headers=headers)

    if r.status_code != 200:
        print_error_server(r)
        return
    print('OK',flush=True)
    print('Fichero con fileID#{0} borrado correctamente'.format(id))

    return

def get_abs_path(rel_path):
    script_dir  = os.path.dirname(__file__)
    abs_path    = os.path.join(script_dir, rel_path)
    return abs_path

def write_in_file(file, data):
    """ Write data in file and print in case of error.
    Returns True if success,
            False if error
    """
    # Save file
    try:
        f = open(file, 'wb')
        f.write(data)
        f.close()
    except:
        print('\nError: Could not save output in {0}'.format(file), file=sys.stderr)
        return False
    return True

def read_from_file(file):
    """ Read data and name from file and print in case of error.
    Returns data and filename if success,
            None if error
    """
    try:
        f = open(file, 'rb')
        data = f.read()
        name = os.path.basename(file)
        f.close()
    except:
        print('\nError: Could not find file {0}'.format(file), file=sys.stderr)
        return None
    return data, name

def print_error_server(response):
    """ Print error message from server response
    """

    if response.status_code > 499:
        print('\nError: Server fault responded with code {0}'.format(response.status_code), file=sys.stderr)
        return

    if response.status_code == 401 or response.status_code == 403:
        print('\nError: Server responded with code {0}'.format(response.status_code), end='', file=sys.stderr)
        try:
            print(': {0}'.format(response.json()['description']),file=sys.stderr)
        except:
            print(': No description',file=sys.stderr)
        return

    print('\nError: Causa desconocida', file=sys.stderr)
    return


#!/usr/bin/python3
#Poshmark Packing Slip Generator
#uses Poshmark order notification email as input
#Requires Python 3.7 and FPDF library

import argparse
import email
import quopri
import urllib.request

from html.parser import HTMLParser
from fpdf import FPDF

class Order():

  section = -1
  sections = []
  contents = []
  images = []

  def __init__(self, shipfrom, seller):
    self.add_section("Shipping From")
    for line in shipfrom:
      self.add_content(line)
    self.add_section("Seller")
    self.add_content(seller)
    self.add_section("header")

  def add_section(self, section):
    self.section += 1
    self.sections.append(section)
    self.contents.append([])
    self.images.append("")

  def add_content(self, content):
    if self.section > -1:
      self.contents[self.section].append(content)

  def add_image(self, url):
    if self.section > -1:
      self.images[self.section] = url

  def get_content(self, section, multiple=False):
    if multiple:
      result = []
      for index in range(0,len(self.sections)):
        if self.sections[index] == section:
          tuple = self.contents[index], self.images[index]
          result.append(tuple)
    elif section in self.sections:
      index = self.sections.index(section)
      result = self.contents[index]
    else:
      result = None
    return result

class PackSlip():

  def __init__(self, order):
    self.order = order

  def generate(self, pdf_name):
    self.left_margin = .75
    self.top_margin = 1
    self.pdf = FPDF("P", "in", "Letter")
    self.pdf.set_margins(self.left_margin, self.top_margin, self.left_margin)
    self.pdf.add_page()
    self.header()
    self.y = 3
    self.items()
    self.pdf.output(pdf_name)

  def header(self):
    self.pdf.set_font("Arial", "B", 24)
    self.pdf.cell(7, .5, "Packing Slip", 0, 1, 'C')
    self.heading("Shipping From", 0, .75, 3.5, 12)
    self.heading("Shipping To", 3.5, .75, 3.5, 12)
    self.heading("Seller", 0, 2, 1.5)
    self.heading("Buyer", 1.5, 2, 1.5)
    self.heading("Order Date", 3, 2, 1.5)
    self.heading("Order ID", 4.5, 2, 2.5)

  def heading(self, section, x=0, y=0, w=7, p=11):
    if y: self.pdf.set_y(y + self.top_margin)
    if x: self.pdf.set_x(x + self.left_margin)
    self.pdf.set_font("Arial", "B", p)
    self.pdf.cell(w, .25, section, 0, 2, 'L')
    contents = self.order.get_content(section)
    self.block(contents, w, .2, p)

  def block(self, lines, w=7, h=.2, p=11):
    self.pdf.set_font("Arial", "", p)
    for line in lines:
      self.pdf.cell(w, h, line, 0, 2, 'L')

  def items(self):
    items = self.order.get_content("Item", True)
    for item in items:
      self.item(item)
      self.y = self.y + 1

  def item(self, item):
    content, image = item
    x = self.left_margin
    y = self.y + self.top_margin
    self.pdf.image(image, x, y, .75, .75)
    self.pdf.set_xy(x+1, y)
    self.block(content[1:4], 6, .25)

  def print_items(self):
    items = self.order.get_content("Item", True)
    for item in items:
      self.print_item(item)

  def print_item(self, item):
    content, image = item
    print("Item")
    print(" ", image)
    for index in range(1,4):
      print(" ", content[index])

class Tag():

  name = ""
  attrs = {}
  sections = ["Shipping To", "Order Date", "Order ID", "Buyer", "Item"]

  def __init__(self, order):
    self.order = order

  def attr(self, name):
    for (key, value) in self.attrs:
      if key == name: return value
    return None

  def start(self):
    if self.name == "img":
      #if self.attr("alt") == "Poshmark":
      #  print("Logo URL:", self.attr("src"))
      if self.attr("width") == "75":
        self.order.add_image(self.attr("src"))
        #print("Item URL:", self.attr("src"))

  def data(self, data):
    if self.name == "td":
      section = data.strip()
      if section in self.sections:
        self.order.add_section(section)
        #print("Section: ", data)
      else:
        if data.strip() != "":
          self.order.add_content(data)
          #print("   Data: ", data)

class Parser(HTMLParser):
  def __init__(self, order):
    self.order = order
    self.tag = None
    self.tags = []
    super().__init__()

  def handle_starttag(self, tag, attrs):
    self.tags.append(self.tag)
    self.tag = Tag(self.order)
    self.tag.name = tag
    self.tag.attrs = attrs
    self.tag.start()

  def handle_endtag(self, tag):
    if self.tag != None:
      self.tag = self.tags.pop()

  def handle_data(self, data):
    if self.tag != None:
      self.tag.data(data);

def email_html(file):
  infile = open(file, "rb")
  message = email.message_from_binary_file(infile)
  if message.is_multipart():
    for part in message.get_payload():
      body = part.get_payload()
      break
  else:
    body = message.get_payload(decode=True)
  return quopri.decodestring(body).decode()

def packslip_from_email(email, pdf):
  shipfrom = ["Curtis F Kaylor", "159 Hunter Ave", "Munroe Falls OH 44262"]
  seller = "@revcurtisp"
  order = Order(shipfrom, seller)
  parser = Parser(order)
  html = email_html(email)
  parser.feed(html)
  packslip = PackSlip(order)
  packslip.generate(pdf)

def main():
  parser = argparse.ArgumentParser()
  parser.add_argument("emlfile", help="Input File Name (with .eml extension)")
  parser.add_argument("pdffile", help="Output File Name (with .pdf extension)")
  args = parser.parse_args()

  packslip_from_email(args.emlfile, args.pdffile)

if __name__ == "__main__":
    main()


import logging
import sys
import json

from sonrai_api import token

# globals
logger = logging.getLogger("sonrai_api")
api_token = token.verify_token()

# Create the Logger for use with all modules
# https://docs.python.org/3/library/logging.html#logrecord-objects
logging.basicConfig(
    format='[%(asctime)s] [%(levelname)s] -- %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',
    stream=sys.stdout
)

# Set ERROR for initial loading of information
level = logging.getLevelName('ERROR')
logger.setLevel(level)

# Constants from config
try:
    with open('sonrai_api/config.json') as json_file:
        config = json.load(json_file)
except json.decoder.JSONDecodeError:
    logging.error("Unable to parse the config.json file")
    exit(255)
except FileNotFoundError:
    logging.error("Unable to find the config.json file")
    exit(254)

# define Python user-defined exceptions
class SonraiAPIException(Exception):
    """Base class for other exceptions"""
    pass

# Set level according to the config file.
level = logging.getLevelName(config['sonrai-token-log-level'])
logger.setLevel(level)