# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import enum

from collections import OrderedDict
from urllib.parse import urlparse

import packaging.utils

from citext import CIText
from pyramid.authorization import Allow
from pyramid.threadlocal import get_current_request
from sqlalchemy import (
    BigInteger,
    Boolean,
    CheckConstraint,
    Column,
    DateTime,
    Enum,
    Float,
    ForeignKey,
    Index,
    Integer,
    String,
    Table,
    Text,
    UniqueConstraint,
    func,
    orm,
    sql,
)
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declared_attr  # type: ignore
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import validates
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
from sqlalchemy.sql import expression
from trove_classifiers import sorted_classifiers

from warehouse import db
from warehouse.accounts.models import User
from warehouse.classifiers.models import Classifier
from warehouse.integrations.vulnerabilities.models import VulnerabilityRecord
from warehouse.sitemap.models import SitemapMixin
from warehouse.utils import dotted_navigator
from warehouse.utils.attrs import make_repr

class Role(db.Model):

    __tablename__ = "roles"
    __table_args__ = (
        Index("roles_user_id_idx", "user_id"),
        Index("roles_project_id_idx", "project_id"),
        UniqueConstraint("user_id", "project_id", name="_roles_user_project_uc"),
    )

    __repr__ = make_repr("role_name")

    role_name = Column(Text, nullable=False)
    user_id = Column(
        ForeignKey("users.id", onupdate="CASCADE", ondelete="CASCADE"), nullable=False
    )
    project_id = Column(
        ForeignKey("projects.id", onupdate="CASCADE", ondelete="CASCADE"),
        nullable=False,
    )

    user = orm.relationship(User, lazy=False)
    project = orm.relationship("Project", lazy=False)

class RoleInvitationStatus(enum.Enum):

    Pending = "pending"
    Expired = "expired"

class RoleInvitation(db.Model):

    __tablename__ = "role_invitations"
    __table_args__ = (
        Index("role_invitations_user_id_idx", "user_id"),
        UniqueConstraint(
            "user_id", "project_id", name="_role_invitations_user_project_uc"
        ),
    )

    __repr__ = make_repr("invite_status", "user", "project")

    invite_status = Column(
        Enum(RoleInvitationStatus, values_callable=lambda x: [e.value for e in x]),
        nullable=False,
    )
    token = Column(Text, nullable=False)
    user_id = Column(
        ForeignKey("users.id", onupdate="CASCADE", ondelete="CASCADE"),
        nullable=False,
        index=True,
    )
    project_id = Column(
        ForeignKey("projects.id", onupdate="CASCADE", ondelete="CASCADE"),
        nullable=False,
        index=True,
    )

    user = orm.relationship(User, lazy=False)
    project = orm.relationship("Project", lazy=False)

class ProjectFactory:
    def __init__(self, request):
        self.request = request

    def __getitem__(self, project):
        try:
            return (
                self.request.db.query(Project)
                .filter(Project.normalized_name == func.normalize_pep426_name(project))
                .one()
            )
        except NoResultFound:
            raise KeyError from None

class TwoFactorRequireable:
    # Project owner requires 2FA for this project
    owners_require_2fa = Column(Boolean, nullable=False, server_default=sql.false())
    # PyPI requires 2FA for this project
    pypi_mandates_2fa = Column(Boolean, nullable=False, server_default=sql.false())

    @hybrid_property
    def two_factor_required(self):
        return self.owners_require_2fa | self.pypi_mandates_2fa

class Project(SitemapMixin, TwoFactorRequireable, db.Model):

    __tablename__ = "projects"
    __table_args__ = (
        CheckConstraint(
            "name ~* '^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$'::text",
            name="projects_valid_name",
        ),
    )

    __repr__ = make_repr("name")

    name = Column(Text, nullable=False)
    normalized_name = orm.column_property(func.normalize_pep426_name(name))
    created = Column(
        DateTime(timezone=False),
        nullable=False,
        server_default=sql.func.now(),
        index=True,
    )
    has_docs = Column(Boolean)
    upload_limit = Column(Integer, nullable=True)
    total_size_limit = Column(BigInteger, nullable=True)
    last_serial = Column(Integer, nullable=False, server_default=sql.text("0"))
    zscore = Column(Float, nullable=True)

    total_size = Column(BigInteger, server_default=sql.text("0"))

    users = orm.relationship(User, secondary=Role.__table__, backref="projects")  # type: ignore # noqa

    releases = orm.relationship(
        "Release",
        backref="project",
        cascade="all, delete-orphan",
        order_by=lambda: Release._pypi_ordering.desc(),  # type: ignore
        passive_deletes=True,
    )

    events = orm.relationship(
        "ProjectEvent", backref="project", cascade="all, delete-orphan", lazy=True
    )

    def __getitem__(self, version):
        session = orm.object_session(self)
        canonical_version = packaging.utils.canonicalize_version(version)

        try:
            return (
                session.query(Release)
                .filter(
                    Release.project == self,
                    Release.canonical_version == canonical_version,
                )
                .one()
            )
        except MultipleResultsFound:
            # There are multiple releases of this project which have the same
            # canonical version that were uploaded before we checked for
            # canonical version equivalence, so return the exact match instead
            try:
                return (
                    session.query(Release)
                    .filter(Release.project == self, Release.version == version)
                    .one()
                )
            except NoResultFound:
                # There are multiple releases of this project which have the
                # same canonical version, but none that have the exact version
                # specified, so just 404
                raise KeyError from None
        except NoResultFound:
            raise KeyError from None

    def __acl__(self):
        session = orm.object_session(self)
        acls = [
            (Allow, "group:admins", "admin"),
            (Allow, "group:moderators", "moderator"),
        ]

        # Get all of the users for this project.
        query = session.query(Role).filter(Role.project == self)
        query = query.options(orm.lazyload("project"))
        query = query.options(orm.joinedload("user").lazyload("emails"))
        query = query.join(User).order_by(User.id.asc())
        for role in sorted(
            query.all(), key=lambda x: ["Owner", "Maintainer"].index(x.role_name)
        ):
            if role.role_name == "Owner":
                acls.append((Allow, str(role.user.id), ["manage:project", "upload"]))
            else:
                acls.append((Allow, str(role.user.id), ["upload"]))
        return acls

    def record_event(self, *, tag, ip_address, additional=None):
        session = orm.object_session(self)
        event = ProjectEvent(
            project=self, tag=tag, ip_address=ip_address, additional=additional
        )
        session.add(event)
        session.flush()

        return event

    @property
    def documentation_url(self):
        # TODO: Move this into the database and eliminate the use of the
        #       threadlocal here.
        request = get_current_request()

        # If the project doesn't have docs, then we'll just return a None here.
        if not self.has_docs:
            return

        return request.route_url("legacy.docs", project=self.name)

    @property
    def all_versions(self):
        return (
            orm.object_session(self)
            .query(
                Release.version, Release.created, Release.is_prerelease, Release.yanked
            )
            .filter(Release.project == self)
            .order_by(Release._pypi_ordering.desc())
            .all()
        )

    @property
    def latest_version(self):
        return (
            orm.object_session(self)
            .query(Release.version, Release.created, Release.is_prerelease)
            .filter(Release.project == self, Release.yanked.is_(False))
            .order_by(Release.is_prerelease.nullslast(), Release._pypi_ordering.desc())
            .first()
        )

class ProjectEvent(db.Model):
    __tablename__ = "project_events"

    project_id = Column(
        UUID(as_uuid=True),
        ForeignKey(
            "projects.id", deferrable=True, initially="DEFERRED", ondelete="CASCADE"
        ),
        nullable=False,
        index=True,
    )
    tag = Column(String, nullable=False)
    time = Column(DateTime, nullable=False, server_default=sql.func.now())
    ip_address = Column(String, nullable=False)
    additional = Column(JSONB, nullable=True)

class DependencyKind(enum.IntEnum):

    requires = 1
    provides = 2
    obsoletes = 3
    requires_dist = 4
    provides_dist = 5
    obsoletes_dist = 6
    requires_external = 7

    # TODO: Move project URLs into their own table, since they are not actually
    #       a "dependency".
    project_url = 8

class Dependency(db.Model):

    __tablename__ = "release_dependencies"
    __table_args__ = (
        Index("release_dependencies_release_kind_idx", "release_id", "kind"),
    )
    __repr__ = make_repr("name", "version", "kind", "specifier")

    release_id = Column(
        ForeignKey("releases.id", onupdate="CASCADE", ondelete="CASCADE"),
        nullable=False,
    )
    kind = Column(Integer)
    specifier = Column(Text)

def _dependency_relation(kind):
    return orm.relationship(
        "Dependency",
        primaryjoin=lambda: sql.and_(
            Release.id == Dependency.release_id, Dependency.kind == kind.value
        ),
        viewonly=True,
    )

class Description(db.Model):

    __tablename__ = "release_descriptions"

    content_type = Column(Text)
    raw = Column(Text, nullable=False)
    html = Column(Text, nullable=False)
    rendered_by = Column(Text, nullable=False)

class Release(db.Model):

    __tablename__ = "releases"

    @declared_attr
    def __table_args__(cls):  # noqa
        return (
            Index("release_created_idx", cls.created.desc()),
            Index("release_project_created_idx", cls.project_id, cls.created.desc()),
            Index("release_version_idx", cls.version),
            UniqueConstraint("project_id", "version"),
        )

    __repr__ = make_repr("project", "version")
    __parent__ = dotted_navigator("project")
    __name__ = dotted_navigator("version")

    project_id = Column(
        ForeignKey("projects.id", onupdate="CASCADE", ondelete="CASCADE"),
        nullable=False,
    )
    version = Column(Text, nullable=False)
    canonical_version = Column(Text, nullable=False)
    is_prerelease = orm.column_property(func.pep440_is_prerelease(version))
    author = Column(Text)
    author_email = Column(Text)
    maintainer = Column(Text)
    maintainer_email = Column(Text)
    home_page = Column(Text)
    license = Column(Text)
    summary = Column(Text)
    keywords = Column(Text)
    platform = Column(Text)
    download_url = Column(Text)
    _pypi_ordering = Column(Integer)
    requires_python = Column(Text)
    created = Column(
        DateTime(timezone=False), nullable=False, server_default=sql.func.now()
    )

    description_id = Column(
        ForeignKey("release_descriptions.id", onupdate="CASCADE", ondelete="CASCADE"),
        nullable=False,
        index=True,
    )
    description = orm.relationship(
        "Description",
        backref=orm.backref(
            "release",
            cascade="all, delete-orphan",
            passive_deletes=True,
            passive_updates=True,
            single_parent=True,
            uselist=False,
        ),
    )

    yanked = Column(Boolean, nullable=False, server_default=sql.false())

    yanked_reason = Column(Text, nullable=False, server_default="")

    _classifiers = orm.relationship(
        Classifier,
        backref="project_releases",
        secondary=lambda: release_classifiers,  # type: ignore
        order_by=expression.case(
            {c: i for i, c in enumerate(sorted_classifiers)},
            value=Classifier.classifier,
        ),
        passive_deletes=True,
    )
    classifiers = association_proxy("_classifiers", "classifier")

    files = orm.relationship(
        "File",
        backref="release",
        cascade="all, delete-orphan",
        lazy="dynamic",
        order_by=lambda: File.filename,  # type: ignore
        passive_deletes=True,
    )

    dependencies = orm.relationship(
        "Dependency",
        backref="release",
        cascade="all, delete-orphan",
        passive_deletes=True,
    )

    vulnerabilities = orm.relationship(
        VulnerabilityRecord,
        back_populates="releases",
        secondary="release_vulnerabilities",
        passive_deletes=True,
    )

    _requires = _dependency_relation(DependencyKind.requires)
    requires = association_proxy("_requires", "specifier")

    _provides = _dependency_relation(DependencyKind.provides)
    provides = association_proxy("_provides", "specifier")

    _obsoletes = _dependency_relation(DependencyKind.obsoletes)
    obsoletes = association_proxy("_obsoletes", "specifier")

    _requires_dist = _dependency_relation(DependencyKind.requires_dist)
    requires_dist = association_proxy("_requires_dist", "specifier")

    _provides_dist = _dependency_relation(DependencyKind.provides_dist)
    provides_dist = association_proxy("_provides_dist", "specifier")

    _obsoletes_dist = _dependency_relation(DependencyKind.obsoletes_dist)
    obsoletes_dist = association_proxy("_obsoletes_dist", "specifier")

    _requires_external = _dependency_relation(DependencyKind.requires_external)
    requires_external = association_proxy("_requires_external", "specifier")

    _project_urls = _dependency_relation(DependencyKind.project_url)
    project_urls = association_proxy("_project_urls", "specifier")

    uploader_id = Column(
        ForeignKey("users.id", onupdate="CASCADE", ondelete="SET NULL"),
        nullable=True,
        index=True,
    )
    uploader = orm.relationship(User)
    uploaded_via = Column(Text)

    @property
    def urls(self):
        _urls = OrderedDict()

        if self.home_page:
            _urls["Homepage"] = self.home_page
        if self.download_url:
            _urls["Download"] = self.download_url

        for urlspec in self.project_urls:
            name, _, url = urlspec.partition(",")
            name = name.strip()
            url = url.strip()
            if name and url:
                _urls[name] = url

        return _urls

    @property
    def github_repo_info_url(self):
        for url in self.urls.values():
            parsed = urlparse(url)
            segments = parsed.path.strip("/").split("/")
            if parsed.netloc in {"github.com", "www.github.com"} and len(segments) >= 2:
                user_name, repo_name = segments[:2]
                return f"https://api.github.com/repos/{user_name}/{repo_name}"

    @property
    def has_meta(self):
        return any(
            [
                self.license,
                self.keywords,
                self.author,
                self.author_email,
                self.maintainer,
                self.maintainer_email,
                self.requires_python,
            ]
        )

class File(db.Model):

    __tablename__ = "release_files"

    @declared_attr
    def __table_args__(cls):  # noqa
        return (
            CheckConstraint("sha256_digest ~* '^[A-F0-9]{64}$'"),
            CheckConstraint("blake2_256_digest ~* '^[A-F0-9]{64}$'"),
            Index(
                "release_files_single_sdist",
                "release_id",
                "packagetype",
                unique=True,
                postgresql_where=(
                    (cls.packagetype == "sdist")
                    & (cls.allow_multiple_sdist == False)  # noqa
                ),
            ),
            Index("release_files_release_id_idx", "release_id"),
        )

    release_id = Column(
        ForeignKey("releases.id", onupdate="CASCADE", ondelete="CASCADE"),
        nullable=False,
    )
    python_version = Column(Text)
    requires_python = Column(Text)
    packagetype = Column(
        Enum(
            "bdist_dmg",
            "bdist_dumb",
            "bdist_egg",
            "bdist_msi",
            "bdist_rpm",
            "bdist_wheel",
            "bdist_wininst",
            "sdist",
        )
    )
    comment_text = Column(Text)
    filename = Column(Text, unique=True)
    path = Column(Text, unique=True, nullable=False)
    size = Column(Integer)
    has_signature = Column(Boolean)
    md5_digest = Column(Text, unique=True, nullable=False)
    sha256_digest = Column(CIText, unique=True, nullable=False)
    blake2_256_digest = Column(CIText, unique=True, nullable=False)
    upload_time = Column(DateTime(timezone=False), server_default=func.now())
    uploaded_via = Column(Text)

    # We need this column to allow us to handle the currently existing "double"
    # sdists that exist in our database. Eventually we should try to get rid
    # of all of them and then remove this column.
    allow_multiple_sdist = Column(Boolean, nullable=False, server_default=sql.false())

    @hybrid_property
    def pgp_path(self):
        return self.path + ".asc"

    @pgp_path.expression  # type: ignore
    def pgp_path(self):
        return func.concat(self.path, ".asc")

    @validates("requires_python")
    def validates_requires_python(self, *args, **kwargs):
        raise RuntimeError("Cannot set File.requires_python")

class Filename(db.ModelBase):

    __tablename__ = "file_registry"

    id = Column(Integer, primary_key=True, nullable=False)
    filename = Column(Text, unique=True, nullable=False)

release_classifiers = Table(
    "release_classifiers",
    db.metadata,
    Column(
        "release_id",
        ForeignKey("releases.id", onupdate="CASCADE", ondelete="CASCADE"),
        nullable=False,
    ),
    Column("trove_id", Integer(), ForeignKey("trove_classifiers.id")),
    Index("rel_class_trove_id_idx", "trove_id"),
    Index("rel_class_release_id_idx", "release_id"),
)

class JournalEntry(db.ModelBase):

    __tablename__ = "journals"

    @declared_attr
    def __table_args__(cls):  # noqa
        return (
            Index("journals_changelog", "submitted_date", "name", "version", "action"),
            Index("journals_name_idx", "name"),
            Index("journals_version_idx", "version"),
            Index("journals_submitted_by_idx", "submitted_by"),
            Index("journals_submitted_date_id_idx", cls.submitted_date, cls.id),
        )

    id = Column(Integer, primary_key=True, nullable=False)
    name = Column(Text)
    version = Column(Text)
    action = Column(Text)
    submitted_date = Column(
        DateTime(timezone=False), nullable=False, server_default=sql.func.now()
    )
    _submitted_by = Column(
        "submitted_by", CIText, ForeignKey("users.username", onupdate="CASCADE")
    )
    submitted_by = orm.relationship(User, lazy="raise_on_sql")
    submitted_from = Column(Text)

class ProhibitedProjectName(db.Model):

    __tablename__ = "prohibited_project_names"
    __table_args__ = (
        CheckConstraint(
            "name ~* '^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$'::text",
            name="prohibited_project_valid_name",
        ),
    )

    __repr__ = make_repr("name")

    created = Column(
        DateTime(timezone=False), nullable=False, server_default=sql.func.now()
    )
    name = Column(Text, unique=True, nullable=False)
    _prohibited_by = Column(
        "prohibited_by", UUID(as_uuid=True), ForeignKey("users.id"), index=True
    )
    prohibited_by = orm.relationship(User)
    comment = Column(Text, nullable=False, server_default="")


from unittest.mock import patch

import pytest
from urllib3.response import HTTPResponse

from stravabot.clients import mapbox

@patch("stravabot.clients.mapbox.MAPBOX_ACCESS_TOKEN", "mapbox-access-token")
def test_static_image(requests_mock):
    expected_response = HTTPResponse(body="the-raw-response", status=200)
    requests_mock.get(
        "https://api.mapbox.com/styles/v1/mapbox/light-v10/static/the-overlay/auto/123x456",
        raw=expected_response,
    )
    response = mapbox.static_image("the-overlay", 123, 456)

    assert requests_mock.call_count == 1
    assert requests_mock.last_request.qs == {"access_token": ["mapbox-access-token"]}
    assert response == expected_response

@patch("stravabot.clients.mapbox.MAPBOX_ACCESS_TOKEN", "mapbox-access-token")
def test_static_image_raises_on_non_200(requests_mock):
    expected_response = HTTPResponse(body="the-raw-response", status=500)
    requests_mock.get(
        "https://api.mapbox.com/styles/v1/mapbox/light-v10/static/the-overlay/auto/123x456",
        raw=expected_response,
    )
    with pytest.raises(mapbox.MapboxError):
        mapbox.static_image("the-overlay", 123, 456)


import torch.nn as nn

class Discriminator(nn.Module):
    def __init__(self, img_dim):
        super().__init__()
        self.layers = nn.Sequential(
            nn.Linear(in_features=img_dim, out_features=128),
            nn.LeakyReLU(0.01),
            nn.Linear(128, 1),
            nn.Sigmoid(),
        )

    def forward(self, x):
        return self.layers.forward(x)

class Generator(nn.Module):
    def __init__(self, noise_dim, img_dim):
        super().__init__()
        self.layers = nn.Sequential(
            nn.Linear(noise_dim, 256),
            nn.LeakyReLU(0.01),
            nn.Linear(256, img_dim),
            nn.Tanh(),
        )

    def forward(self, x):
        return self.layers.forward(x)


# [ 분석 시 고려사항 5. 최적의 매개변수 조합 (grid search) : train/val/test ]
# - 변수의 최적의 조합을 찾는 과정
# - 중첩 for문으로 구현 가능, grid search 기법으로 간단히 구현 가능
# - train/validation/test set으로 분리
# - 매개변수의 선택은 validation set으로 평가

# 예제) grid search - knn iris data
# 1) data loading
# 2) data split
trainval_x, test_x, trainval_y, test_y = train_test_split(df_iris.data,
                                                          df_iris.target,
                                                          random_state=0)

train_x, val_x, train_y, val_y = train_test_split(trainval_x,
                                                  trainval_y,
                                                  random_state=0)

# 3) 모델 학습 및 매개변수 튜닝
best_score = 0
for i in range(1,11) :
    m_knn = knn_c(i)
    m_knn.fit(train_x, train_y)
    vscore = m_knn.score(val_x, val_y)

    if vscore > best_score :
        best_score = vscore    # best_score의 갱신
        best_params = i        # best parameter의 저장

# 4) 매개변수 고정 후 재학습 및 평가
m_knn = knn_c(best_params)
m_knn.fit(trainval_x, trainval_y)
m_knn.score(test_x, test_y)    # 0.973, best parameter = 5
# --------------------------------------------------------------------------- #

# -------------------------------- 연 습 문 제 -------------------------------- #
# 연습문제 6. grid search - random forest cancer data set
# 1) data loading
from sklearn.datasets import load_breast_cancer
df_cancer = load_breast_cancer()
df_cancer.data.shape    # (569, 30)

# 2) data split
trainval_x, test_x, trainval_y, test_y = train_test_split(df_cancer.data,
                                                          df_cancer.target,
                                                          random_state = 0)

train_x, val_x, train_y, val_y = train_test_split(trainval_x,
                                                  trainval_y,
                                                  random_state = 0)

# 3) 모델 학습 및 매개변수 튜닝 (min_samples_split, max_features)
# min_samples_split : 2 ~ 10
# max_features : 1 ~ 30
# 3-1) 교차 검증 수행 x => 9 * 30
best_score = 0
for i in range(2, 11) :
    for j in range(1, 31) :
        m_rf = rf_c(min_samples_split = i, max_features = j, random_state = 0)
        m_rf.fit(train_x, train_y)
        vscore = m_rf.score(val_x, val_y)

        if vscore > best_score :
            best_score = vscore
            best_params = {'min_samples_split' : i,
                           'max_features' : j}

m_rf = rf_c(**best_params, random_state = 0)    # ** 사용하면 키워드 인자 넣을 수 있음
m_rf.fit(trainval_x, trainval_y)
m_rf.score(test_x, test_y)    # 0.972

# 3-2) 교차 검증 수행 (매개변수 튜닝 시) => 9 * 30 * 5
best_score = 0
for i in range(2, 11) :
    for j in range(1, 31) :
        m_rf = rf_c(min_samples_split = i, max_features = j, random_state = 0)
        m_rf.fit(train_x, train_y)
        cr_vscore = cross_val_score(m_rf, trainval_x, trainval_y, cv = 5)

        vscore = cr_vscore.mean()
        if vscore > best_score :
            best_score = vscore
            best_params = {'min_samples_split' : i,
                           'max_features' : j}

m_rf = rf_c(**best_params, random_state = 0)    # ** 사용하면 키워드 인자 넣을 수 있음
m_rf.fit(trainval_x, trainval_y)
m_rf.score(test_x, test_y)    # 0.944

# grid search 기법
# - 위의 중첩 for문을 사용한 매개변수의 조합을 찾는 과정을 함수화
# - CV 기법을 포함시켜 validation data set에 대한 반복 test를 수행

from sklearn.model_selection import GridSearchCV

# 1) 모델 생성
m_rf = rf_c()

# 2) 그리드 서치 기법을 통한 매개변수 조합 찾기
# 2-1) 매개변수 조합 생성
v_params = {'min_samples_split' : np.arange(2, 11),
            'max_features' : np.arange(1, 31)}

# 2-2) 그리드 서치 모델 생성
m_grid = GridSearchCV(m_rf,           # 적용 모델
                      v_params,       # 매개변수 조합 (딕셔너리)
                      cv = 5)

# 2-3) 그리드 서치에 의한 모델 학습
m_grid.fit(trainval_x, trainval_y)

# 2-4) 결과 확인
dir(m_grid)
m_grid.best_score_    # 베스트 매개변수 값을 갖는 평가 점수
m_grid.best_params_   # {'max_features': 1, 'min_samples_split': 2}

df_result = DataFrame(m_grid.cv_results_)
df_result.T    # 여기서 test_score들은 우리가 아는 test score가 아니고, validation score라고 생각하면 됨

df_result.T.iloc[:, 0]    # 첫 번째 매개변수 셋 결과

# cv = 5이기 때문에 아래처럼 5회 진행
# split0_test_score                                              0.94186
# split1_test_score                                             0.952941
# split2_test_score                                             0.929412
# split3_test_score                                             0.988235
# split4_test_score                                             0.988235

# 2-5) 최종 평가
m_grid.score(test_x, test_y)    # 0.951 => 모델 자체에 best params가 저장되어 있음

# 2-6) 그리드 서치 결과 시각화
df_result.mean_test_score    # 교차 검증의 결과 (5개의 점수에 대한 평균)
arr_score = np.array(df_result.mean_test_score).reshape(30, 9)
# reshape(max_features, min_samples) => reshape은 행우선 순위로 배치 되기 때문에
df_result.loc[:, ['params', 'mean_test_score']]

# =============================================================================
# # [ 참고 : df_result의 결과 reshape 시 배치 순서 ]
# #                             min_samples_split
# #                         2          3          4          5      ...     10
# # 'max_features': 1   0.960137   0.953133   0.960109   0.955486
# # 'max_features': 2
# #                     ....
# # 'max_features': 30
# =============================================================================

import mglearn
plt.rc('figure', figsize = (10, 10))
plt.rc('font', size = 7)

mglearn.tools.heatmap(value,        # 숫자 배열
                      xlabel,       # x축 이름
                      ylabel,       # y축 이름
                      xticklabels,  # x축 눈금
                      yticklabels)  # y축 눈금

mglearn.tools.heatmap(arr_score,
                      'min_samples_split',
                      'max_features',
                      v_params['min_samples_split'],
                      v_params['max_features'],
                      cmap = 'viridis')
# --------------------------------------------------------------------------- #


import argparse
import socket
import time

parser = argparse.ArgumentParser(description="UDP Client")
parser.add_argument(
    "-l",
    "--listen",
    type=str,
    default="127.0.0.1",
    help="IP to check connectivity",
)
parser.add_argument(
    "-p",
    "--port",
    type=int,
    default=1234,
    help="Port to check connectivity",
)

args = parser.parse_args()
ip = args.listen
port = args.port

print(f"Check udp on {ip}:{port}")

while True:
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    client_socket.settimeout(1.0)
    message = b"PING"
    addr = (ip, port)

    start = time.time()
    client_socket.sendto(message, addr)
    try:
        data, server = client_socket.recvfrom(1024)
        end = time.time()
        elapsed = end - start
        print(f"{data} {elapsed:.6f}s")
    except socket.timeout:
        print("REQUEST TIMED OUT")
    time.sleep(1)


# ---Dependencies---
import torch
import torch.nn as nn

# ---Custom---
from utils.ops import extract_kpt_vectors

Default = {'margin': 0.5, 'alpha': None, 'beta': None, 'n_neg': 10}

# noinspection PyArgumentList
class PixelwiseContrastiveLoss(nn.Module):
    """
    Implementation of "pixel-wise" contrastive loss. Contrastive loss typically compares two whole images.
            L = (Y) * (1/2 * d**2) + (1 - Y) * (1/2 * max(0, margin - d)**2)

    In this instance, we instead compare pairs of features within those images.
    Positive matches are given by ground truth correspondences between images.
    Negative matches are generated on-the-fly based on provided parameters.

    Attributes:
        margin (float): Target margin distance between positives and negatives
        alpha (int): Minimum distance from original positive KeyPoint
        beta (int): Maximum distance from original positive KeyPoint
        n-neg (int): Number of negative samples to generate

    Methods:
        forward: Compute pixel-wise contrastive loss
        forward_eval: Detailed forward pass for logging
    """
    def __init__(self, margin=0.5, alpha=None, beta=None, n_neg=10):
        super().__init__()
        self.margin = margin
        self.alpha = alpha
        self.beta = beta
        self.n_neg = n_neg

        self._dist = nn.PairwiseDistance()

    def __repr__(self):
        return f'{self.__class__.__qualname__}({self.margin}, {self.alpha}, {self.beta}, {self.n_neg})'

    def __str__(self):
        return f'Min{self.alpha or 0}_Max{self.beta or "Inf"}'

    @staticmethod
    def create_parser(parser):
        parser.add_argument('--margin', default=0.5, help='Target distance between negative feature embeddings.')
        parser.add_argument('--alpha', default=None, type=float, help='Minimum distance from positive KeyPoint')
        parser.add_argument('--beta', default=None, type=float, help='Maximum distance from positive KeyPoint')
        parser.add_argument('--n_neg', default=10, help='Number of negative samples to generate')

    def forward(self, features, labels):
        """ Compute pixel-wise contrastive loss.
        :param features: Vertically stacked feature maps (b, n-dim, h*2, w)
        :param labels: Horizontally stacked correspondence KeyPoints (b, n-kpts, 4) -> (x1, y1, x2, y2)
        :return: Loss
        """
        source, target = torch.chunk(features, 2, dim=-2)
        source_kpts, target_kpts = torch.chunk(labels, 2, dim=-1)

        loss = self._positive_loss(source, target, source_kpts, target_kpts)[0]
        loss += self._negative_loss(source, target, source_kpts, target_kpts)[0]
        return loss

    def forward_eval(self, features, labels):
        source, target = torch.chunk(features, 2, dim=-2)
        source_kpts, target_kpts = torch.chunk(labels, 2, dim=-1)

        pos_loss, pos_dist = self._positive_loss(source, target, source_kpts, target_kpts)
        neg_loss, neg_dist = self._negative_loss(source, target, source_kpts, target_kpts)

        loss = (pos_loss+neg_loss).item()
        output = {
            'scalars': {
                'loss': loss,
                'positive': pos_dist.mean().item(),
                'negative': neg_dist.mean().item(),
            },
            'histograms': {
                'hist_positive': pos_dist.detach().cpu(),
                'hist_negative': neg_dist.detach().cpu(),
            }
        }
        return loss, output

    def _calc_distance(self, source, target, source_kpts, target_kpts):
        source_descriptors = extract_kpt_vectors(source, source_kpts).permute([0, 2, 1])
        target_descriptors = extract_kpt_vectors(target, target_kpts).permute([0, 2, 1])
        return self._dist(source_descriptors, target_descriptors)

    def _positive_loss(self, source, target, source_kpts, target_kpts):
        dist = self._calc_distance(source, target, source_kpts, target_kpts)
        loss = (dist**2).mean() / 2
        return loss, dist

    def _negative_loss(self, source, target, source_kpts, target_kpts):
        dsource_kpts, dtarget_kpts = self._generate_negative_like(source, source_kpts, target_kpts)

        dist = self._calc_distance(source, target, dsource_kpts, dtarget_kpts)
        margin_dist = (self.margin - dist).clamp(min=0.0)
        loss = (margin_dist ** 2).mean() / 2
        return loss, dist

    def _generate_negative_like(self, other, source_kpts, target_kpts):
        # Source points remain the same
        source_kpts = source_kpts.repeat([1, self.n_neg, 1])

        # Target points + offset according to method
        target_kpts = target_kpts.repeat([1, self.n_neg, 1])
        target_kpts = self._permute_negatives(target_kpts, other.shape)
        return source_kpts, target_kpts

    def _permute_negatives(self, kpts, shape):
        h, w = shape[-2:]
        # (max(h, w) - low) means that even after getting the remainder points will be further away than low
        low = self.alpha if self.alpha else 0
        high = self.beta if self.beta else (max(h, w) - low)

        # Generate random shift for each KeyPoint
        shift = torch.randint_like(kpts, low=low, high=high)
        shift *= torch.sign(torch.rand_like(shift, dtype=torch.float)-0.5).short()  # Random + or - shift

        # Initial shift to satisfy max distance
        new_kpts = kpts + shift
        new_kpts %= torch.tensor((w, h), dtype=torch.short, device=new_kpts.device)

        # Shift to satisfy min distance
        diffs = new_kpts - kpts
        diff_clamp = torch.clamp(diffs, min=-high, max=high)
        new_kpts += (diff_clamp - diffs)

        return new_kpts


import threading
import socket
import time
import os
from datetime import datetime
import json

CONST_MAX_PORTS = 65534
CONST_START_THREAD_TIMEOUT = 0.7
CONST_TCP_WAIT_TIMEOUT = 1
CONST_NETWORKS_FILE = "networks.txt"
CONST_LOG_FILE = "port_scan_log.txt"
CONST_RESULT_FILE = "result.json"
#TG_STR_1 = "wget -O /dev/null \"https://api.telegram.org/bot0000000000:KEY/sendMessage?chat_id=-11111111111111&disable_notification=true&text="
TG_STR_1 = "wget -O /dev/null \"https://api.telegram.org/bot000000000:KEY/sendMessage?chat_id=-111111111111&disable_notification=true&text="
TG_STR_2 = "\""

def log(log_string):
    file_name = CONST_LOG_FILE
    if log_string == "_INIT_":
        newfile = open(file_name, 'w')
        newfile.truncate()
        newfile.close()
    else:
        file_log = open(file_name, 'a')
        month_names = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]
        dt = datetime.now()
        if dt.hour < 10:
            f_hour = f"0{dt.hour}"
        else:
            f_hour = f"{dt.hour}"
        if dt.minute < 10:
            f_minute = f"0{dt.minute}"
        else:
            f_minute = f"{dt.minute}"
        if dt.second < 10:
            f_second = f"0{dt.second}"
        else:
            f_second = f"{dt.second}"
        file_str = f"{f_hour}:{f_minute}:{f_second} {dt.day}-{month_names[dt.month - 1]}-{dt.year} ... {log_string} \n"
        file_log.write(file_str)
        file_log.close()

def cidr_to_addrlist(act_network):
    addresses = list()
    act_network.strip()
    slash_position = act_network.find("/")
    network_name = act_network[:slash_position]
    network_mask = int(act_network[slash_position + 1:])
    if network_mask == 32:
        addresses.append(network_name)
    else:
        host_num = 2 ** (32 - network_mask) - 2
        net_ip_arr_str = network_name.split(".")
        net_ip_arr_int = [int(net_ip_arr_str[0]), int(net_ip_arr_str[1]), int(net_ip_arr_str[2]), int(net_ip_arr_str[3])]
        net_binary = (net_ip_arr_int[0] << 24) + (net_ip_arr_int[1] << 16) + (net_ip_arr_int[2] << 8) + net_ip_arr_int[3]
        for i in range(0, host_num, 1):
            net_binary += 1
            oct1 = net_binary >> 24
            oct2 = (net_binary % 16777216) >> 16
            oct3 = (net_binary % 65536) >> 8
            oct4 = net_binary % 256
            addresses.append(f"{oct1}.{oct2}.{oct3}.{oct4}")
    return addresses

#def worker_pinger(ip_address):
#    r = os.popen(f"ping -c2 -W1 {ip_address}").read()
#    if (" 0% packet loss," in r) or (" 50% packet loss," in r) or (" 25% packet loss," in r) or (" 75% packet loss," in r):
#        log(f"{ip_address} is alive")
#        alive_hosts.add(ip_address)
#    pinged_hosts.add(ip_address)

def worker_scan(host_address, start_port, end_port):
    opened_ports = list()
    for port in range(start_port, end_port+1, 1):
        connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        connection.settimeout(CONST_TCP_WAIT_TIMEOUT)
        is_opened = 0
        #print(f"{host_address}:{port}")
        try:
            connection.connect((host_address.strip(), port))
            is_opened = 1
            connection.close()
        except socket.error:
            pass
        if is_opened == 1:
            log(f"{host_address}:{port} is opened")
            opened_ports.append(port)
    if len(opened_ports) > 0:
        results[host_address] = opened_ports
    scanned_hosts.add(host_address)

# loading startup data
log("_INIT_")
log("START")
file = open(CONST_NETWORKS_FILE, "r")
networks_CIDR = file.read().strip().split()
file.close()
del file

# converting CIDR to set of IP addresses
ip_addresses = set()
for network_CIDR in networks_CIDR:
    ip_list = cidr_to_addrlist(network_CIDR)
    for ip in ip_list:
        ip_addresses.add(ip)
del ip
del ip_list

# pinging IP addresses
#ping_threads = set()
#pinged_hosts = set()
#alive_hosts = set()
#for ip in ip_addresses:
#    ping_threads.add(threading.Thread(target=worker_pinger, args=(ip, )))
#for pinger_thread in ping_threads:
#    pinger_thread.start()
#    time.sleep(CONST_START_THREAD_TIMEOUT)

#while len(pinged_hosts) != len(ip_addresses):
#    time.sleep(1)

#for pinger_thread in ping_threads:
#    pinger_thread.join()

#del ip_addresses
#del pinger_thread
#del pinged_hosts
#del ping_threads
#del ip

# scanning living hosts
scan_threads = set()
scanned_hosts = set()
results = dict()
for host in ip_addresses:
    scan_threads.add(threading.Thread(target=worker_scan, args=(host, 1, CONST_MAX_PORTS)))

for scan_thread in scan_threads:
    time.sleep(CONST_START_THREAD_TIMEOUT)
    scan_thread.start()

last_scanned_hosts = 0
while len(scanned_hosts) != len(ip_addresses):
    currently_scanned_hosts = len(scanned_hosts)
    if currently_scanned_hosts > last_scanned_hosts:
        log(f"scanned {currently_scanned_hosts} hosts")
        last_scanned_hosts = currently_scanned_hosts
    time.sleep(1)
del last_scanned_hosts
del currently_scanned_hosts

for scan_thread in scan_threads:
    scan_thread.join()

del scan_thread
del scan_threads
del scanned_hosts

# saving results
log("saving results")
first_time_scan = 0
if os.path.exists(CONST_RESULT_FILE):
    load = open(CONST_RESULT_FILE, "r")
    inner_dict = json.load(load)
    load.close()
else:
    inner_dict = dict()
    first_time_scan = 1

save = open(CONST_RESULT_FILE, "w")
json.dump(results, save, indent=4)
save.close()
#-------------------- Analyzing --------------------------------------
log("analysing the result")
old_dict = dict() # contains ips and ports closed since last scan
new_dict = dict() # contains ips and ports opened since last scan
for old_key in inner_dict.keys():
    if old_key not in results.keys():
        old_dict[old_key] = inner_dict[old_key]
for new_key in results.keys():
    if new_key not in inner_dict.keys():
        new_dict[new_key] = results[new_key]
    if (new_key in inner_dict.keys()) and (new_key in results.keys()):
        removed_ports = list()
        added_ports = list()
        for r_port in inner_dict[new_key]:
            if r_port not in results[new_key]:
                removed_ports.append(r_port)
        for a_port in results[new_key]:
            if a_port not in inner_dict[new_key]:
                added_ports.append(a_port)
        if len(removed_ports) > 0:
            old_dict[new_key] = list(removed_ports)
        if len(added_ports) > 0:
            new_dict[new_key] = list(added_ports)
        del removed_ports
        del added_ports

old_outstr = str()
for t_ip in old_dict:
    old_outstr += str(t_ip)
    old_outstr += ": "
    d_len = len(old_dict[t_ip])
    for t_port in old_dict[t_ip]:
        old_outstr += str(t_port)
        if t_port != old_dict[t_ip][d_len - 1]:
            old_outstr += ", "
    old_outstr += "\n"

new_outstr = str()
for t_ip in new_dict:
    new_outstr += str(t_ip)
    new_outstr += ": "
    d_len = len(new_dict[t_ip])
    for t_port in new_dict[t_ip]:
        new_outstr += str(t_port)
        if t_port != new_dict[t_ip][d_len - 1]:
            new_outstr += ", "
    new_outstr += "\n"

output_str = str()

if first_time_scan == 1:
    output_str = f"PORT SCANNER:\nFirst scan !!!\n{new_outstr}"
else:
    output_str = f"PORT SCANNER:\n"
    if len(old_outstr) > 0:
        output_str += f"closed since last scan:\n{old_outstr}\n"
    if len(new_outstr) > 0:
        output_str += f"opened since last scan:\n{new_outstr}\n"

wget_str = TG_STR_1 + output_str + TG_STR_2

if (len(old_outstr) > 0) or (len(new_outstr) > 0):
    r0 = os.popen(wget_str).read()
    time.sleep(1)
    del r0
#-------------------- End Analyzing ------------------------------------

log("END")


import tensorflow as tf
import cv2
import random, math
import numpy as np
from collections import deque
import util
from gym import  spaces
from copy import copy
import gym
from gym import wrappers
import gym_ple
from PIL import Image
import png

# -- possible network flavor types -----------------------------------
__DEEP_RECURRENT_Q_NETWORK_LSTM = 'deepRecurrentQNetwork'
__DEEP_Q_NETORK = "deepQNetwork"
logs_path = '/home/tf/tensorflow_logs/example'
saved_networks_path = '/home/tf/saved_networks/'
#logs_path = '/tmp/'
#saved_networks_path = '/tmp/'
# -------------------------------------------------------------------

class DDQN():

    def __init__(self,
                 modelType="DDQN",
                 gameEnv=None,
                 agentClass=None,
                 epsilon=1,
                 epsilonDecay=.99,
                 gamma=.99,
                 numTraining=1000,
                 batchSize=32,
                 framePerAction=1,
                 numActions=2,
                 replayMemory=40000,
                 game='flappyBird',
                 learningRate=1e-3,
                 learningDecay=1.0,
                 numStepsBeforeSaveModel=10000,
                 numEpisodesRun=10,
                 mode='cpu',
                 displayGraphics=False,
                 numEpsilonCycle=7,
                 updateTargetNetworkAfterIterations=10000,
                 useRewardClippingMode=False,
                 useRMSOptimizer=False
                 ):

        self.gameEnv = gameEnv
        self.modelType = modelType
        self.epsilon = epsilon  # uses random action or predicted action based on epsilon value
        self.epsilon0 = epsilon
        self.epsilonDecay = epsilonDecay  # decay rate for epsilon
        self.gamma = gamma  # reward factor
        self.numTraining = numTraining  #  number of examples for training
        self.batchSize = batchSize  # number of data points descent
        self.framePerAction = framePerAction
        self.numActions = numActions
        self.game = game
        self.explore = self.numTraining  # frames over which to anneal epsilon
        self.replayMemory = replayMemory  # number of previous steps to store
        self.learningRate = learningRate  # learning rate optimization algorithm(RMS/ADAM etc)
        self.learningDecay = learningDecay
        self.numStepsBeforeSaveModel = numStepsBeforeSaveModel  # save model after these number of steps
        self.numEpisodesRun = numEpisodesRun  # total number of episodes we want to execute
        self.mode = mode
        # self.agent = util.lookup(agentClass, globals())(actionFn=self.getLegalActions)
        self.displayGraphics = displayGraphics
        self.numEpsilonCycle = .5 + numEpsilonCycle  # number of time epsilon will decay to 0 throughout the training cycle
        self.updateTargetNetworkAfterIterations = updateTargetNetworkAfterIterations
        self.debug = False  # will log some extra print statements to standard out
        self.useRewardClippingMode = useRewardClippingMode
        self.useRMSOptimizer=useRMSOptimizer
        print("-------- BASIC MODEL HYPER PARAMS USED TO RUN THE MODEL -------------------")

        print("STARTING DDQN MODEL WITH FOLLOWING PARAMS: \n 1. episilon = ", self.epsilon, \
              " \n 2. epsilonDecay=", self.epsilonDecay, \
              " \n 3. gamma = ", self.gamma, \
              " \n 4. numTraining = ", self.numTraining , \
              " \n 5. batchSize = ", self.batchSize , \
              " \n 6. numActions = ", self.numActions , \
              " \n 7. learningRate = ", self.learningRate , \
              " \n 8. numStepsBeforeSaveModel = ", self.numStepsBeforeSaveModel, \
              " \n 9. numEpisodesRun = ", self.numEpisodesRun, \
              " \n 10. numEpsilonCycle = ", self.numEpsilonCycle , \
              " \n 11. updateTargetNetworkAfterIterations = ", self.updateTargetNetworkAfterIterations,
              " \n 12 . useRewardClippingMode = ", self.useRewardClippingMode,
              " \n 13 . useRMSOptimizer =",self.useRMSOptimizer
              )
        print("----------------------------------------------------------------------------")

    # ------------- OPEN AI RELATED METHODS-------------------------------

    def getAction(self, observation):
        return self.agent.getAction(observation)

    def getLegalActions(self, state=None):
        if isinstance(self.gameEnv.action_space, spaces.discrete.Discrete):
            return tuple(range(self.gameEnv.action_space.n))
        raise Exception('Action type not supported: %s' % type(self.gameEnv.action_space))

    def observeTransition(self, old_obs, action, observation, reward):
        self.agent.observeTransition(old_obs, action, observation, reward)

    def prepareState(self, observation):

        if isinstance(self.gameEnv.observation_space, spaces.discrete.Discrete):
            pass

        elif isinstance(self.gameEnv.observation_space, spaces.box.Box):
            observation = tuple(observation)

        else:
            raise Exception('Observation type not supported: %s' % type(self.gameEnv.observation_space))

        return observation

    # ------------- END OF OPEN AI RELATED METHODS-------------------------------

    def solve_network(self):
        print('start solving network')

    def weight_variable(self, shape):
        initial = tf.truncated_normal(shape, stddev=0.01)
        return tf.Variable(initial)

    def bias_variable(self, shape):
        initial = tf.constant(0.01, shape=shape)
        return tf.Variable(initial)

    def conv2d(self, x, W, stride):
        return tf.nn.conv2d(x, W, strides=[1, stride, stride, 1], padding="SAME")

    def max_pool_2x2(self, x):
        return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")

    def updateTargetNetwork(self):
        for targetVariableName in self.targetToSourceVariableNames:
            sourceTensorVariableName = self.targetToSourceVariableNames[targetVariableName]
            sourceWeight = self.sess.run(self.sourceNameToVariableDict[sourceTensorVariableName])
            if len(sourceWeight.shape) == 4 and self.debug :
                print('Source variable :{} value being applied to target {} : \n'.
                      format(sourceTensorVariableName, sourceWeight[0, 0, 0, :]))
            self.sess.run(self.t_w_assign_op[targetVariableName], feed_dict={self.t_w_input[targetVariableName] : sourceWeight})

            if len(sourceWeight.shape) == 4 and self.debug :
                print('Target Variable : {} after update from source weights {} :\n'.
                      format(targetVariableName, self.sess.run(self.targetNameToVariableDict[targetVariableName])[0, 0, 0, :]))

    def createNetwork(self):

        with tf.device('/cpu:0' if self.mode == 'cpu' else '/gpu:0'):

            with tf.variable_scope('prediction') :
                # input layer
                self.inputImageVector = tf.placeholder("float", [None, 80, 80, 4])
                # hidden layers
                h_conv1 = tf.layers.conv2d(self.inputImageVector, 32, 8, 4, activation=tf.nn.relu, use_bias=True, name="CONV1")
                h_conv2 = tf.layers.conv2d(h_conv1, 64, 4, 2, activation=tf.nn.relu, use_bias=True, name="CONV2")
                h_conv3 = tf.layers.conv2d(h_conv2, 64, 3, 1, activation=tf.nn.relu, use_bias=True, name="CONV3")
                flatten = tf.reshape(h_conv3, [-1, 2304])
                # fully connected layers
                h_fc1 = tf.layers.dense(flatten, 512, activation=tf.nn.relu, use_bias=True, name="FC1")
                self.fc_out = tf.layers.dense(h_fc1, 2, activation=None, use_bias=True, name="FC_OUT")
                self.fc_out_action = tf.argmax(self.fc_out, dimension=1)

            with tf.variable_scope('target'):

                self.inputImageVector_target = tf.placeholder("float", [None, 80, 80, 4])
                h_conv1_target = tf.layers.conv2d(self.inputImageVector_target, 32, 8, 4, activation=tf.nn.relu, use_bias=True, name="CONV1_TARGET")
                h_conv2_target = tf.layers.conv2d(h_conv1_target, 64, 4, 2, activation=tf.nn.relu, use_bias=True, name="CONV2_TARGET")
                h_conv3_target = tf.layers.conv2d(h_conv2_target, 64, 3, 1, activation=tf.nn.relu, use_bias=True, name="CONV3_TARGET")
                flatten_target = tf.reshape(h_conv3_target, [-1, 2304])
                # fully connected layers
                h_fc1_target = tf.layers.dense(flatten_target, 512, activation=tf.nn.relu, use_bias=True, name="FC1_TARGET")
                self.fc_out_target = tf.layers.dense(h_fc1_target, 2, activation=None, use_bias=True, name="FC_OUT_TARGET")

                self.target_q_idx = tf.placeholder('int32', [None, None], 'outputs_idx')
                self.target_q_with_idx = tf.gather_nd(self.fc_out_target, self.target_q_idx)

                self.sourceToTargetVariableNames = { 'prediction/CONV1/kernel:0' : 'target/CONV1_TARGET/kernel:0',
                               'prediction/CONV2/kernel:0' : 'target/CONV2_TARGET/kernel:0',
                               'prediction/CONV3/kernel:0' : 'target/CONV3_TARGET/kernel:0',
                               'prediction/FC1/kernel:0'   : 'target/FC1_TARGET/kernel:0' ,
                               'prediction/FC_OUT/kernel:0': 'target/FC_OUT_TARGET/kernel:0'
                            }

                self.targetToSourceVariableNames = {}
                for sourceVariableName, targetVariableName in self.sourceToTargetVariableNames.items():
                    self.targetToSourceVariableNames[targetVariableName] = sourceVariableName

                self.sourceNameToVariableDict = {}
                for sVariable in tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope='prediction'):
                    if sVariable.name in self.sourceToTargetVariableNames:
                        self.sourceNameToVariableDict[sVariable.name] = sVariable

                self.targetNameToVariableDict = {}
                print(tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope='target'))
                for tVariable in tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope='target'):
                    if tVariable.name in self.targetToSourceVariableNames:
                        self.targetNameToVariableDict[tVariable.name] = tVariable

            with tf.variable_scope('optimizer'):

                self.predictedActionScoreVector = tf.placeholder("float", [None, self.numActions])  # min_batch_size * numActions
                self.predictedScore = tf.reduce_sum(tf.multiply(self.fc_out, self.predictedActionScoreVector), axis=1)  # scalar
                self.targetQ = tf.placeholder("float", [None])  # scalar value  --> this will be calculated by the

                if  self.useRewardClippingMode:
                    self.loss = tf.reduce_mean(self.clipped_error(self.targetQ - self.predictedScore))
                else :
                    self.loss = tf.reduce_mean(tf.square(self.targetQ - self.predictedScore))

                tf.summary.scalar("loss", self.loss)  # Create predictedActionScoreVector summary to monitor cost tensor
                tf.summary.scalar("Predicted_Q-Value", tf.reduce_mean(self.predictedScore))
                tf.summary.scalar("Actual_Q-Value", tf.reduce_mean(self.targetQ))
                self.merged_summary_op = tf.summary.merge_all()  # Merge all summaries into predictedActionScoreVector single operation
                if self.useRMSOptimizer:
                    self.optimizer= tf.train.RMSPropOptimizer(self.learningRate).minimize(self.loss)
                else :
                    self.optimizer = tf.train.AdamOptimizer(self.learningRate).minimize(self.loss)  # TODO make this configurable

            with tf.variable_scope('pred_to_target') :

                self.t_w_input = {}
                self.t_w_assign_op = {}
                namePrefix = "variable"
                num = 1
                for targetVariableName in self.targetToSourceVariableNames:
                    self.t_w_input[targetVariableName] = tf.placeholder('float32', self.targetNameToVariableDict[targetVariableName].get_shape().as_list(), name=namePrefix + str(num))
                    self.t_w_assign_op[targetVariableName] = self.targetNameToVariableDict[targetVariableName].assign(self.t_w_input[targetVariableName])
                    num += 1

    ## --- Finished creating model ----------------------------------

    def trainNetwork(self):

        # ----------------- tensor flow related setup -------------------

        saver = tf.train.Saver()  # saving and loading networks .. this will by default save all tensor flow Variables (placeholders)
        self.sess.run(tf.global_variables_initializer())  # initialize session for  tensor flow
        summary_writer = tf.summary.FileWriter(logs_path, graph=tf.get_default_graph())  # op to write logs to Tensor board

        checkpoint = tf.train.get_checkpoint_state(saved_networks_path)  # set up to save and load  networks
        if checkpoint and checkpoint.model_checkpoint_path:
            saver.restore(self.sess, checkpoint.model_checkpoint_path)
            self.updateTargetNetwork()
            print("Successfully loaded:", checkpoint.model_checkpoint_path)
        else:
            print("Could not find old network weights")

        # --------------------------------start training ----------------

        replayMemoryQueue = deque()  # this will store all the event for replay memory
        numIterations = 0  # number of iterations
        avgReward = 0
        updateTargetThresh = 10000
        for episodeNum in range(self.numEpisodesRun + 100):  # TODO : number of episodes can be tuned

            # ---- open-ai game emulator integration  with initial bootstrapping------

            initialColoredObservation = self.gameEnv.reset()
            self.gameEnv.render(close=not self.displayGraphics)
            gameAction = random.choice(self.getLegalActions(initialColoredObservation))  # choose predictedActionScoreVector scalar randomly from predictedActionScoreVector set of legal actions

            initialColoredObservation, _, _, _ = self.gameEnv.step(gameAction)  # pass in scalar action to get output
            imageBackgroundGray = self.convertImageBackgroundToGray(initialColoredObservation)  # do pre-processing on image
            currentLastFourImageFrames = np.stack((imageBackgroundGray, imageBackgroundGray, imageBackgroundGray, imageBackgroundGray), axis=2)

            episode_pos_reward = 0  # count the number of positive rewards received
            done = False
            modelSaved = False

            while not done :

                actionVector = np.zeros([self.numActions])
                action_index = 0

                if random.random() <= self.epsilon:  # we will gradually decrease epsilon as we explore
                    action_index = random.randrange(self.numActions)
                    actionVector[action_index] = 1
                else:
                    yout_t = self.sess.run(self.fc_out, feed_dict={self.inputImageVector: [currentLastFourImageFrames]})  # <-- get action from current state prediction
                    action_index = np.argmax(yout_t)
                    actionVector[action_index] = 1

                nextColoredImageObservation, reward, isEpisodeDone, _ = self.gameEnv.step(action_index)  # <--run the selected action and observe next state and reward

                if reward != 0:  # <-- process reward to be [-1, 0, 1]
                    reward /= math.fabs(reward)

                nextImageBackgroundGray = self.convertImageBackgroundToGray(nextColoredImageObservation)  # <-- do pre-processing on image
                nextImageBackgroundGray = np.reshape(nextImageBackgroundGray, (80, 80, 1))
                nextLastFourImageFrames = np.append(nextImageBackgroundGray, currentLastFourImageFrames[:, :, 0:3], axis=2)  # <-- stack last 4 image frames for next state

                replayMemoryQueue.append((currentLastFourImageFrames, actionVector, reward, nextLastFourImageFrames, 1 if isEpisodeDone else 0))  # <-- store the transition in replayMemoryQueue

                if len(replayMemoryQueue) > self.replayMemory:
                    replayMemoryQueue.popleft()

                done = isEpisodeDone

                if numIterations > self.numTraining:  # <-- only train/optimize if done observing [ if iterations > numTraining threshold -x option]

                    minibatch = random.sample(replayMemoryQueue, self.batchSize)  # min-batch to perform optimization
                    current_state_image_minibatch = [d[0] for d in minibatch]  # get batch of stacked image frames
                    current_action_minibatch = [d[1] for d in minibatch]  # min batch for actions performed on each stacked image frame
                    current_rewards_minibatch = [d[2] for d in minibatch]  # min batch for rewards
                    next_state_image_minibatch = [d[3] for d in minibatch]  # get next batch of stacked image frames

                    targetQ = []
                    if self.useRewardClippingMode :
                        current_rewards_minibatch = np.clip(current_rewards_minibatch, -1, 1)

                    next_state_reward_eval = self.sess.run(self.fc_out_action, feed_dict={self.inputImageVector: next_state_image_minibatch})  # <-- get the next state reward
                    next_state__reward_dqn_eval = self.sess.run(self.target_q_with_idx, feed_dict={  # <----------------------------- Double Q Learning , we don't do max
                                                           self.inputImageVector_target: next_state_image_minibatch,
                                                           self.target_q_idx: [[idx, pred_a] for idx, pred_a in enumerate(next_state_reward_eval)]
                    })
                    batchEpisodeDone = [d[4] for d in minibatch]
                    targetQ = (1. - np.array(batchEpisodeDone)) * self.gamma * next_state__reward_dqn_eval + current_rewards_minibatch
                    _, _, _, summary = self.sess.run([self.optimizer, self.fc_out, self.loss, self.merged_summary_op], feed_dict={  # <-- Do the optimization step
                                              self.targetQ: targetQ,  # and summary nodes
                                              self.predictedActionScoreVector: current_action_minibatch,
                                              self.inputImageVector: current_state_image_minibatch
                                           })
                    summary_writer.add_summary(summary, numIterations - self.numTraining + 1)  #  <-- tensor board stuff

                    # hyperparameter based on paper https://deepmind.com/research/dqn/
                    if numIterations == updateTargetThresh:
                        print('------Updating target network ---------------')
                        self.updateTargetNetwork()
                        updateTargetThresh += int((avgReward+1) * 750 + 9000)

                # update the old values
                currentLastFourImageFrames = nextLastFourImageFrames
                # currentColoredImageObservation = nextColoredImageObservation

                # update numIterations to track how many training or observations sampled
                numIterations += 1

                # save progress every epsilon cycle
                if not modelSaved  and (episodeNum + int(self.numEpisodesRun / self.numEpsilonCycle / 2)) % int(self.numEpisodesRun / self.numEpsilonCycle + 1) == 0:
                    print("****** Saving Model at episode ", episodeNum, " iteration ", numIterations, " epsilon ", self.epsilon, " ******")
                    saver.save(self.sess, saved_networks_path + self.game + '-ddqn', global_step=numIterations)
                    modelSaved = True

                # print info
                state = ""
                if numIterations <= self.numTraining:
                    state = "observe"
                elif numIterations > self.numTraining and numIterations <= self.numTraining + self.explore:
                    state = "explore"
                else:
                    state = "train"

                if reward > 0:
                    episode_pos_reward += 1

            # --------------- End of Episode -----------------------------#
            print("EPISODE", episodeNum, \
                    "/ POSITIVE REWARDS", episode_pos_reward, \
                    "/ STATE", state, \
                    "/ EPSILON", self.epsilon, \
                    "/ Iteration number", numIterations , \
                    "/ RUNNING AVERAGE REWARDS", avgReward)
            avgReward = .99 * avgReward + .01 * episode_pos_reward
            # scale down epsilon as we train ( epsilon oscillating decay formula)
            if state != "observe":
                self.epsilon = self.epsilon0 * np.power(self.epsilonDecay, episodeNum) * \
                (1 + np.cos(2 * np.pi * episodeNum / (self.numEpisodesRun / self.numEpsilonCycle))) / 2 + .001

            if episodeNum > self.numEpisodesRun:
                self.epsilon = 0
        # ---- all done , close game environment ----
        self.gameEnv.close()

    def playGame(self):
        self.sess = tf.Session(config=tf.ConfigProto(allow_soft_placement=True, log_device_placement=True))
        self.createNetwork()
        self.trainNetwork()

    def convertImageBackgroundToGray(self, currentImageOutputColored):
        imgGray = cv2.cvtColor(currentImageOutputColored, cv2.COLOR_BGR2GRAY)
        block_size = 3
        imgGaussGray = cv2.adaptiveThreshold(imgGray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, \
                    cv2.THRESH_BINARY, block_size, 2)
        imgResize = cv2.resize(imgGaussGray, (80, 80))
        imgNormalized = np.divide(imgResize, 255)
        return imgNormalized

    def updateTargetNetworkSlowlyButFrequently(self):
        print('--- This updates the target network to source network ---')

    def clipped_error(self,x):
        return tf.where(tf.abs(x) < 1.0, 0.5 * tf.square(x), tf.abs(x) - 0.5)

    def main(self):
        self.playGame()


from __future__ import print_function

import httplib2
import itertools
import json
import os
import re

BASE_HG_URI = "http://code.google.com/p/google-api-python-client/source/" "browse/#hg"

http = httplib2.Http(".cache")
r, c = http.request("https://www.googleapis.com/discovery/v1/apis")
if r.status != 200:
    raise ValueError("Received non-200 response when retrieving Discovery.")

# Dictionary mapping api names to their discovery description.
DIRECTORY = {}
for item in json.loads(c)["items"]:
    if item["preferred"]:
        DIRECTORY[item["name"]] = item

# A list of valid keywords. Should not be taken as complete, add to
# this list as needed.
KEYWORDS = {
    "appengine": "Google App Engine",
    "oauth2": "OAuth 2.0",
    "cmdline": "Command-line",
    "django": "Django",
    "threading": "Threading",
    "pagination": "Pagination",
    "media": "Media Upload and Download",
}

def get_lines(name, lines):
    """Return lines that begin with name.

  Lines are expected to look like:

     name: space separated values

  Args:
    name: string, parameter name.
    lines: iterable of string, lines in the file.

  Returns:
    List of values in the lines that match.
  """
    retval = []
    matches = itertools.ifilter(lambda x: x.startswith(name + ":"), lines)
    for line in matches:
        retval.extend(line[len(name) + 1 :].split())
    return retval

def wiki_escape(s):
    """Detect WikiSyntax (i.e. InterCaps, a.k.a. CamelCase) and escape it."""
    ret = []
    for word in s.split():
        if re.match(r"[A-Z]+[a-z]+[A-Z]", word):
            word = "!%s" % word
        ret.append(word)
    return " ".join(ret)

def context_from_sample(api, keywords, dirname, desc, uri):
    """Return info for expanding a sample into a template.

  Args:
    api: string, name of api.
    keywords: list of string, list of keywords for the given api.
    dirname: string, directory name of the sample.
    desc: string, long description of the sample.
    uri: string, uri of the sample code if provided in the README.

  Returns:
    A dictionary of values useful for template expansion.
  """
    if uri is None:
        uri = BASE_HG_URI + dirname.replace("/", "%2F")
    else:
        uri = "".join(uri)
    if api is None:
        return None
    else:
        entry = DIRECTORY[api]
        context = {
            "api": api,
            "version": entry["version"],
            "api_name": wiki_escape(entry.get("title", entry.get("description"))),
            "api_desc": wiki_escape(entry["description"]),
            "api_icon": entry["icons"]["x32"],
            "keywords": keywords,
            "dir": dirname,
            "uri": uri,
            "desc": wiki_escape(desc),
        }
        return context

def keyword_context_from_sample(keywords, dirname, desc, uri):
    """Return info for expanding a sample into a template.

  Sample may not be about a specific api.

  Args:
    keywords: list of string, list of keywords for the given api.
    dirname: string, directory name of the sample.
    desc: string, long description of the sample.
    uri: string, uri of the sample code if provided in the README.

  Returns:
    A dictionary of values useful for template expansion.
  """
    if uri is None:
        uri = BASE_HG_URI + dirname.replace("/", "%2F")
    else:
        uri = "".join(uri)
    context = {
        "keywords": keywords,
        "dir": dirname,
        "uri": uri,
        "desc": wiki_escape(desc),
    }
    return context

def scan_readme_files(dirname):
    """Scans all subdirs of dirname for README files.

  Args:
    dirname: string, name of directory to walk.

  Returns:
    (samples, keyword_set): list of information about all samples, the union
      of all keywords found.
  """
    samples = []
    keyword_set = set()

    for root, dirs, files in os.walk(dirname):
        if "README" in files:
            filename = os.path.join(root, "README")
            with open(filename, "r") as f:
                content = f.read()
                lines = content.splitlines()
                desc = " ".join(itertools.takewhile(lambda x: x, lines))
                api = get_lines("api", lines)
                keywords = get_lines("keywords", lines)
                uri = get_lines("uri", lines)
                if not uri:
                    uri = None

                for k in keywords:
                    if k not in KEYWORDS:
                        raise ValueError(
                            "%s is not a valid keyword in file %s" % (k, filename)
                        )
                keyword_set.update(keywords)
                if not api:
                    api = [None]
                samples.append((api[0], keywords, root[1:], desc, uri))

    samples.sort()

    return samples, keyword_set

def main():
    # Get all the information we need out of the README files in the samples.
    samples, keyword_set = scan_readme_files("./samples")

    # Now build a wiki page with all that information. Accumulate all the
    # information as string to be concatenated when were done.
    page = ['<wiki:toc max_depth="3" />\n= Samples By API =\n']

    # All the samples, grouped by API.
    current_api = None
    for api, keywords, dirname, desc, uri in samples:
        context = context_from_sample(api, keywords, dirname, desc, uri)
        if context is None:
            continue
        if current_api != api:
            page.append(
                """
=== %(api_icon)s %(api_name)s ===

%(api_desc)s

Documentation for the %(api_name)s in [https://google-api-client-libraries.appspot.com/documentation/%(api)s/%(version)s/python/latest/ PyDoc]

"""
                % context
            )
            current_api = api

        page.append("|| [%(uri)s %(dir)s] || %(desc)s ||\n" % context)

    # Now group the samples by keywords.
    for keyword, keyword_name in KEYWORDS.iteritems():
        if keyword not in keyword_set:
            continue
        page.append("\n= %s Samples =\n\n" % keyword_name)
        page.append("
\n")
        for _, keywords, dirname, desc, uri in samples:
            context = keyword_context_from_sample(keywords, dirname, desc, uri)
            if keyword not in keywords:
                continue
            page.append(
                """

  [%(uri)s %(dir)s]
   %(desc)s

"""
                % context
            )
        page.append("
\n")

    print("".join(page))

if __name__ == "__main__":
    main()


from django.urls import path
from . import views

app_name="disponibilites"

urlpatterns = [
    path('', views.LoginPage, name="login"),
    path('logout', views.logoutUser, name="logout"),
    path('dashboard', views.dashboard ,name='dashboard'),
    path('dashboard/rdv', views.getMyRdv ,name='dashboard_rdv'),
    path('dashboard/rdv/validate/<int:id>', views.validate ,name='validate'),
    path('dashboard/rdv/validate/go/<int:id>', views.validerRdv ,name='validate_go'),
    path('dashboard/event/add', views.gotToAddEventPage,name='event_add'),
    path('dashboard/event', views.getMyEvent,name='dashboard_evt'),
    path('dashboard/event/add/go', views.AddEvenement,name='event_add_go'),
    path('dashboard/event/get', views.GetEvenement,name='event_get'),
    path('dashboard/event/get/rdv', views.GetEventForAdministrateCalendar,name='event_get_for_rdv'),
    path('connexion', views.authentication, name="authenticate"),
    path('notifications/get/<int:vue_chk>', views.getNotification, name="notifications"),
    path('notifications/valdate/<int:id>', views.ValidateNotif, name="notifications_vldt"),
    path('notifications', views.dashboardNotif, name="notifications_see"),
    path('agent/edit', views.goToEditAgent, name="edit_agent"),
    path('agent/edit/go', views.editAgent, name="edit_agent_go"),

    #filtre
    path('conge/user/<int:id>', views.GetEvenementSingle, name="get_jour_off_single"),
]


import pdb
import traceback

class PromptOnError(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        try:
            return self.func(*args, **kwargs)
        except Exception as e:
            traceback.print_exc()

            print('Falling back to prompt to try live fix.')
            print('c to continue when things look ok again.')
            print('exit() to gtfo and cancel.')

            pdb.set_trace()


from django.conf.urls import url
from django.contrib import admin
from.views import  (
    home_list,
    home_create,
    home_detail,
    home_update,
    home_delete,

)
urlpatterns = [
    url(r'^$', home_list,name="list"),
    url(r'^create/$', home_create, name = "create"),
    url(r'^(?P<id>\d+)/$',home_detail, name="detail"),
    url(r'^(?P<id>\d+)/edit/$', home_update, name="update"),
    url(r'^(?P<id>\d+)/delete/$', home_delete,name = "delete"),
]


#performance benchmark
from engine import game_engine_240123 as engine
import pygame, os, random, time, settings
debug_file = open("debug.txt", "w")
file_dir = os.getcwd()

#create window
w, h = settings.width, settings.height
window = engine.window("Performance Test", w, h, (0, 0, 128))

#variables
run = True
clock = pygame.time.Clock()
performance_mode = settings.performance_mode                #remove alpha transparencies
player_speed = 4                #set the speed of the player
Canvas = engine.Canvas          #get the canvas
fps_stat = []           #keeps track of the fps
angle = 0           #sets the sprites angle position
no_of_sprites = settings.number_of_sprites          #how many sprites to be loaded in
no_of_text = settings.number_of_texts
direction = True            #true is right, left is false

#lists
display = []
ground = engine.properties_object("bg", f"{file_dir}/textures/ground.png", 0, 0, 3848, 3200, not performance_mode)
display += [ground]

#sprites
display_sprite = []
player = engine.properties_object("player", f"{file_dir}/textures/player.png", w/2, h/2, 64, 64, not performance_mode)
display_sprite += [player]

#foreground
foreground = []
#create sprites that rotate in place
for _ in range(no_of_sprites):
    x = random.randint(0, w-64)
    y = random.randint(0, h-64)
    sprite = engine.properties_object("sprite", f"{file_dir}/textures/player.png", x, y, 64, 64, not performance_mode)
    foreground += [sprite]

#text
text_foreground = []
for _ in range(no_of_text):
    x = random.randint(0, w-64)
    y = random.randint(0, h-64)
    text = engine.properties_text("text", "Hello, World!", "YELLOW", x, y, 30, False)
    text_foreground += [text]

def main():
    global fps_stat, text_foreground
    global angle, direction

    if player.x >= 3848:
        direction = not direction

    if player.x <= 0:
        direction = not direction

    if direction:
        if engine.object.right(player, player_speed, w - 64):
            Canvas.offsetX += player_speed
            engine.object.right(player, player_speed, w - 64)
            #move the text and the sprites
            for object in foreground:
                engine.object.right(object, player_speed, w - 64)

            for text in text_foreground:
                engine.object.right(text, player_speed, w - 64)

        if engine.object.down(player, player_speed, h - 64):
            Canvas.offsetY += player_speed
            engine.object.down(player, player_speed, h - 64)
            #move the text and the sprites
            for object in foreground:
                engine.object.down(object, player_speed, w - 64)

            for text in text_foreground:
                engine.object.down(text, player_speed, w - 64)

    else:
        if engine.object.left(player, player_speed, 64):
            Canvas.offsetX -= player_speed
            engine.object.left(player, player_speed, 64)
            #move the text and the sprites
            for object in foreground:
                engine.object.left(object, player_speed, 64)

            for text in text_foreground:
                engine.object.left(text, player_speed, 64)

        if engine.object.up(player, player_speed, 64):
            Canvas.offsetY -= player_speed
            engine.object.up(player, player_speed, 64)
            #move the text and the sprites
            for object in foreground:
                engine.object.up(object, player_speed, 64)

            for text in text_foreground:
                engine.object.up(text, player_speed, 64)

    #set the angle for the sprites
    if player.animationTime <= engine.frames:
        player.animationTime = engine.frames + 60
        for object in foreground:
            engine.object.setAngle(object, angle)
            angle += 90

    #keeps the data of how it performaed
    if engine.frames > 30:
        fps_stat += [int(clock.get_fps())]
        engine.window.writeDebug_file(debug_file, display, display_sprite, foreground, text_foreground, clock)

start = time.time()
while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    keys = pygame.key.get_pressed()

    if keys[pygame.K_ESCAPE]:
        run = False

    main()
    engine.counter.update()
    engine.window.update(window, display, display_sprite, foreground, text_foreground, clock)
    clock.tick()

    #end game when the time passed is 30 seconds or greater
    if time.time() - start >= settings.duration:
        run = False

pygame.quit()
debug_file.close()

summary = open("summary.txt", "w")
average_fps = sum(fps_stat) / len(fps_stat)
print(f"Average FPS: {average_fps}")

#sort the list to find the lowest fps and the maximum fps
fps_stat.sort()
low_fps = fps_stat[0]
high_fps = fps_stat[len(fps_stat) - 1]
print(f"Lowest FPS: {low_fps}       Highest FPS: {high_fps}")

#find the lowest and highest deviation to the average fps
low_fps_devi = 100 - (low_fps / average_fps * 100)
high_fps_devi = (high_fps / average_fps * 100) - 100

if high_fps_devi > low_fps_devi:
    highest_devi = high_fps_devi
    lowest_devi = low_fps_devi
else:
    highest_devi = low_fps_devi
    lowest_devi = high_fps_devi

print(f"Lowest deviation: {int(lowest_devi)}%           Highest deviation: {int(highest_devi)}%")

#write to the file
summary.write(f"Average FPS: {average_fps}\n")
summary.write(f"Lowest FPS: {low_fps}       Highest FPS: {high_fps}\n")
summary.write(f"Lowest deviation: {int(lowest_devi)}%           Highest deviation: {int(highest_devi)}%\n")
summary.close()


#!/usr/bin/env python
#

from cliff.helpers.system import System
import scipy
from scipy import stats
from scipy.spatial.distance import pdist, cdist, squareform
import numpy as np
import logging
import pickle
import cliff.helpers.constants as constants
import cliff.helpers.utils as utils
import math
import os
import copy
import time
import glob

import cliff.tests as t
testpath = os.path.abspath(t.__file__).split('__init__')[0]
# qml
import qml
from qml.representations import get_slatm_mbtypes

class Multipole:
    '''
    Multipole class. Predicts multipoles from machine learning.
    No local axis system. Instead, basis set expansion along the pairwise vectors.
    '''

    def __init__(self, options, ref = None):

        name = options.name
        # Set logger
        self.logger = options.logger
        self.ref = ref
        self.multipoles = None
        self.descr_train  = {'H':[], 'C':[], 'O':[], 'N':[], 'S':[], 'Cl':[], 'F':[], 'Br':[]}
        self.target_train = {'H':[], 'C':[], 'O':[], 'N':[], 'S':[], 'Cl':[], 'F':[], 'Br':[]}
        # support vector regression
        self.clf = None
        # alpha_train has size 1,3,9
        self.max_coeffs = [1, 3, 9]
        self.offset_mtp = [0, 1, 4]
        self.alpha_train = {'H':None, 'C':None, 'O':None, 'N':None, 'S':None, 'Cl':None, 'F':None, 'Br':None}
        self.ml_method  = options.multipole_ml_method
        self.kernel     = options.multipole_kernel
        self.krr_sigma  = options.multipole_krr_sigma
        self.krr_lambda = options.multipole_krr_lambda
        # Normalization of the target data - mean and std for each MTP component
        self.norm_tgt_mean = {'H':np.zeros((3)),'C':np.zeros((3)),'O':np.zeros((3)), 'N':np.zeros((3)), 'S':np.zeros((3)), 'Cl':np.zeros((3)), 'F':np.zeros((3)), 'Br':np.zeros((3))}
        self.norm_tgt_std  = {'H':np.ones((3)), 'C':np.ones((3)), 'O':np.ones((3)), 'N':np.ones((3)), 'S':np.ones((3)), 'Cl':np.ones((1)), 'F':np.zeros((3)), 'Br':np.zeros((3))}
        self.num_mols_train = {'H':0, 'C':0, 'O':0, 'N':0, 'S':0, 'Cl':0, 'F':0, 'Br':0}
        # Set of qml mols. Used for SLATM
        self.qml_mols = []
        self.qml_filter_ele = []
        self.mbtypes = None
        self.ref_mtp = options.multipole_ref_mtp

        self.cutoff = options.multipole_rcut

        self.ref_path = ""
        if self.ref_mtp:
            self.ref_path = options.multipole_ref_path
        if options.test_mode:
            self.ref_path = testpath + self.ref_path
        self.training_dir = options.multipole_training

        self.correct_charge = options.multipole_correct_charge

        self.nn_mtp = None

    def set_ref_path(self, path):
        self.ref_path = path

    def load_ml(self, load_file=None):
        '''Load machine learning model'''
        # Try many atoms and see which atoms we find
        load_files = []
        if load_file != None:
            load_files.append(load_file)
        else:
            load_files = glob.glob(self.training_dir + "/*.pkl")
            self.logger.info(
                "    Loading Multipole training from %s" % self.training_dir)

            #print("MTP models: ", self.training_dir)
        for mtp_file in load_files:
            try:
                with open(mtp_file, 'rb') as f:
                    descr_train_at, alpha_train, norm_tgt_mean, \
                    norm_tgt_std, mbtypes = pickle.load(f,encoding="ISO-8859-1")
                    #norm_tgt_std, mbtypes = pickle.load(f) #try for old pickles
                    #norm_tgt_std, mbtypes = pickle.load(f, encoding='latin1') #try for old pickles
                    for e in self.descr_train.keys():
                        if e in descr_train_at.keys() and len(descr_train_at[e]) > 0:
                            # Update
                            self.descr_train[e] = descr_train_at[e]
                            self.alpha_train[e] = alpha_train[e]
                            self.norm_tgt_mean[e] = norm_tgt_mean[e]
                            self.norm_tgt_std[e] = norm_tgt_std[e]
                    self.mbtypes = mbtypes
            except:
                print("Could not load model ", mtp_file)
        return None

    def save_ml(self, save_file):
        '''Save machine learning model'''
        self.logger.info("   Saving multipole machine learning model to %s" %
            save_file)
        with open(save_file, 'wb') as f:
            pickle.dump([self.descr_train, self.alpha_train,
                         self.norm_tgt_mean, self.norm_tgt_std, self.mbtypes], f, protocol=2)
        return None

    def train_mol(self):
        '''Train machine learning model of multipole rank mtp_rank and
        basis set expansion coefficient coeff.'''
        # SLATM: First compute mbtypes and the representation
        # Reinitialize descriptor
        for key in self.descr_train.keys():
            self.descr_train[key] = []
        # Check that mbtypes exists
        if self.mbtypes is None:
            raise ValueError("mbtypes missing")
        for i,mol in enumerate(self.qml_mols):
            mol.generate_slatm(self.mbtypes, rcut = self.cutoff, local=True)
            for j,at in enumerate(self.qml_filter_ele[i]):
                if at == 1:
                    # Do include the descriptor
                    self.descr_train[mol.atomtypes[j]].append(mol.representation[j])
        for e in self.descr_train.keys():
            size_training = len(self.target_train[e])
            # self.normalize(e)
           # print(e, size_training, len(self.descr_train[e]))
           # print(self.alpha_train)
           # exit()
            if len(self.descr_train[e]) > 0:
                self.logger.info("Training set size: %d atoms; %d molecules" % (size_training,
                    self.num_mols_train[e]))
                #print("Training set size: %d atoms; %d molecules" % (size_training,
                #    self.num_mols_train[e]))
                tgt_prop = [[self.target_train[e][i][mtp_rank][coeff]
                            for mtp_rank in range(3)
                            for coeff in range(self.max_coeffs[mtp_rank])]
                            for i in range(len(self.target_train[e]))]
                pairwise_dists = squareform(pdist(self.descr_train[e],
                    constants.ml_metric[self.kernel]))
                self.logger.info("building kernel matrix of size (%d,%d); %7.4f Gbytes" \
                    % (size_training, size_training, 8*size_training**2/1e9))
                #print("building kernel matrix of size (%d,%d); %7.4f Gbytes" \
                #    % (size_training, size_training, 8*size_training**2/1e9))
                power  = constants.ml_power[self.kernel]
                prefac = constants.ml_prefactor[self.kernel]
                kmat = np.exp(- pairwise_dists**power / (prefac*self.krr_sigma**power))
                #kmat += self.krr_lambda*np.identity(len(self.target_train[e]))
                kmat.flat[::len(self.target_train[e])+1] += self.krr_lambda

                self.alpha_train[e] = np.linalg.solve(kmat,tgt_prop)
        print(self.alpha_train)
        self.logger.info("training of multipoles finished.")
        return None

    def predict_mol(self, _system, charge=0, xyz=None, force_predict = False):
        '''Predict multipoles in local reference frame given descriptors.'''
        tp = time.time()
        _system.initialize_multipoles()
        _system.compute_basis()

        _system.build_slatm(self.mbtypes,self.cutoff)
        power  = constants.ml_power[self.kernel]
        prefac = constants.ml_prefactor[self.kernel]
        for e in self.alpha_train.keys():
            if self.alpha_train[e] is not None:
                # slowest part of the whole project
                pairwise_dists = cdist(_system.slatm, \
                    self.descr_train[e], constants.ml_metric[self.kernel])
                kmat = np.exp(- pairwise_dists**power / (prefac*self.krr_sigma**power))
                pred = np.dot(kmat,self.alpha_train[e])
                for i in range(_system.num_atoms):
                    if _system.elements[i] == e:
                        _system.mtp_expansion[i] = pred[i]
        # Revert normalization
        # self.rev_normalize(_system)
        # Correct to get integer charge
        if self.correct_charge:
            # Weigh by ML error
            mol_mu = sum([constants.ml_chg_correct_error[ele]
                            for ele in _system.elements])
            totalcharge = sum([mtp[0] for mtp in _system.mtp_expansion])
            excess_chg = totalcharge - float(charge)
            if mol_mu > 0.:
                for i,mtp_i in enumerate(_system.mtp_expansion):
                    w_i = constants.ml_chg_correct_error[_system.elements[i]]
                    mtp_i[0] += -1.*excess_chg * (w_i/mol_mu)
        # Compute multipoles from basis set expansion
        _system.expand_multipoles()

        self.logger.debug("Predicted multipole expansion for %s" % ( _system.xyz[0]))

       # print("    Time spent predicting multipoles:                     %8.3f s" % (time.time() - tp))
        return None

    def add_mol_to_training(self, new_system, pun, atom=None, xyz=None):
        'Add molecule to training set'
        new_system.initialize_multipoles()

        # Don't build SLATM yet, only add information to mbtypes
        mol = None
        if new_system.xyz[0] is None:
            if xyz is not None:
                mol = qml.Compound(xyz)
            else:
                raise ValueError("Missing xyz file")
        else:
            mol = qml.Compound(new_system.xyz[0])
        self.qml_mols.append(mol)
        if atom is None:
            self.qml_filter_ele.append([1 for i in range(mol.natoms)])
        else:
            self.qml_filter_ele.append([1 if (str(mol.atomtypes[i]) == atom) else 0
                                for i in range(mol.natoms)])
        new_system.multipoles = pun
        # Read in multipole moments from txt file
        if len(new_system.multipoles) != new_system.num_atoms:
            raise Exception("Wrong number of charges in %s" % (len(pun)))

        for i in range(len(new_system.elements)):
            ele_i = new_system.elements[i]
            if (ele_i == atom) or atom is None:
                if ele_i not in self.target_train.keys():
                    self.target_train[ele_i] = []
                    self.descr_train[ele_i] = []
                    self.num_mols_train[ele_i] = 0
                new_target_train = []
                # Rotate system until atom pairs point in all x,y,z directions
                vec_all_dir = new_system.compute_basis()
                # charge
                new_target_train.append([new_system.multipoles[i][0]])
                # dipole
                new_target_train.append(np.dot(new_system.multipoles[i][1:4],
                                    new_system.basis[i].T))
                # quadrupole
                tmp = np.dot(np.dot(new_system.basis[i],
                    utils.spher_to_cart(new_system.multipoles[i][4:9])),
                            new_system.basis[i].T).reshape((9,))
                new_target_train.append(tmp)
                self.target_train[ele_i].append(new_target_train)
            if atom in new_system.elements or atom is None:
                self.num_mols_train[ele_i] += 1
        self.logger.info("Added file to training set: %s" % new_system)
        return None


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

size=1000
iterations=20
startgrid=[]
grid=[]
gridx=[0]*size**2
gridylist=[0]*size
gridy=[0]*size**2
result=[iterations]*(size**2)
row=[0]*size

#populate the result grid with coordinate positions
#x coordinates
for n in range (0, size):
    row[n]=n/(size/2)-2*((size-n)/size)

gridx=row*size

#y coordinates
for n in range(0,size):
    gridylist[n]=[-1*(n/(size/2)-2*((size-n)/size))]*size

gridy=[val for sublist in gridylist for val in sublist]

#populate grid coordinates
for p in range(0,(size**2)-1):
    grid==grid.append([gridx[p],gridy[p]])
    startgrid==startgrid.append([gridx[p],gridy[p]])
    #print(grid[p])

g=0 #grid position counter
#generate points. do each pixel in turn
for e in grid:

    for i in range(1,iterations):

        c=((e[0]**2)+(e[1]**2))**(0.5)

        #update element values for next iteration
        e[0]=((e[0]**2)-(e[1]**2))+(startgrid[g][0])
        e[1]=(2*e[0]*e[1])+(startgrid[g][1])

        if (c>2):
            result[g]=i
            break

    g+=1 #increase outer loop counter by 1

#reshape as array
result=np.array(result)
result=np.reshape(result,(size,size))

plt.clf()
plt.imshow(result)
plt.show()