from ray.rllib.utils.framework import try_import_torch

torch, nn = try_import_torch()
if torch:
    import torch.nn.functional as F
    from torch import Tensor
    import kornia.augmentation as K
    import torch

class Augmentation(torch.nn.Module):
    def __init__(
            self,
            strong: bool,
            consistent: bool,
            pad: int = 4,
            image_size: int = 64
    ) -> None:
        super().__init__()
        self.random_crop = K.VideoSequential(
            K.RandomCrop((image_size, image_size), padding=pad, padding_mode='replicate'))
        self.strong_transform = None
        if strong:
            self.strong_transform = [
                K.RandomRotation(5, p=1 / 8),
                K.RandomSharpness((0, 1.0), p=1 / 8),
                K.RandomPosterize((3, 8), p=1 / 8),
                K.RandomSolarize(0.1, p=1 / 8),
                K.RandomEqualize(p=1 / 8),
                K.ColorJiggle(brightness=(0.4, 1.4), p=1 / 8),
                K.ColorJiggle(saturation=(0.4, 1.4), p=1 / 8),
                K.ColorJiggle(contrast=(0.4, 1.4), p=1 / 8),
            ]
            # Strong augmentations are always consistent across timesteps
            self.strong_transfom_fn = K.VideoSequential(*self.strong_transform, same_on_frame=True)
        self.consistent_crop = consistent

    def forward(self, X: Tensor) -> Tensor:
        X = (X + 0.5) * 255.0
        orig_shape = X.shape
        if len(orig_shape) == 4:
            print("STUPID RLLIB TEST USELESS - NOT AUGMENTING")
            return X
        if self.consistent_crop:
            X = self.random_crop(X.view(orig_shape[0], 1, orig_shape[1] * orig_shape[2], *orig_shape[3:])).view(
                orig_shape)
        else:
            X = self.random_crop(X)
        if self.strong_transform:
            X = self.strong_transfom_fn(X / 255.0)
        X = X - 0.5
        return X

class RandomShiftsAug(nn.Module):
    def __init__(self, pad=4, consistent=False):
        super().__init__()
        self.pad = pad
        self.consistent = consistent

    def forward(self, x):
        orig_size = x.size()
        if len(orig_size) > 4:
            x = x.permute(1, 0, 2, 3, 4)
            orig_size = x.size()
            t, n, c, h, w = x.size()
        else:
            n, c, h, w = x.size()

        assert h == w

        padding = tuple([self.pad] * 4 + [0, 0])  # len(x.size()))
        x = F.pad(x, padding, 'replicate')
        eps = 1.0 / (h + 2 * self.pad)
        arange = torch.linspace(-1.0 + eps,
                                1.0 - eps,
                                h + 2 * self.pad,
                                device=x.device,
                                dtype=x.dtype)[:h]
        arange = arange.unsqueeze(0).repeat(h, 1).unsqueeze(2)
        base_grid = torch.cat([arange, arange.transpose(1, 0)], dim=2)
        base_grid = base_grid[None, None, ...].repeat(t, n, 1, 1, 1) if len(x.size()) > 4 else base_grid.unsqueeze(
            0).repeat(n, 1, 1, 1)

        if self.consistent:
            shift = torch.randint(0,
                                  2 * self.pad + 1,
                                  size=(n, 1, 1, 2),
                                  device=x.device,
                                  dtype=x.dtype)
            shift = shift.repeat(t, 1, 1, 1, 1) if len(orig_size) > 4 else shift
        else:
            shift = torch.randint(0,
                                  2 * self.pad + 1,
                                  size=(t, n, 1, 1, 2) if len(orig_size) > 4 else (n, 1, 1, 2),
                                  device=x.device,
                                  dtype=x.dtype)

        shift *= 2.0 / (h + 2 * self.pad)

        grid = base_grid + shift
        if len(orig_size) > 4:
            x = x.view((-1, *x.size()[2:]))
            grid = grid.view((-1, *grid.size()[2:]))

        samples = F.grid_sample(x,
                                grid,
                                padding_mode='zeros',
                                align_corners=False)
        # samples = samples.view(orig_size)
        if len(orig_size) > 4:
            samples = samples.view(orig_size)
            samples = samples.permute(1, 0, 2, 3, 4)
        else:
            return samples

        return samples


import torch
import torch.nn as nn
from torch.nn.parameter import Parameter
import torchvision
import torch.optim as optim
from torchvision import transforms, utils, datasets
from torch.utils.data import Dataset, DataLoader, Subset
import torch.nn.functional as F
import numpy as np
import random
import gc
from collections import deque
from itertools import chain

class Student:
    def __init__(self, data_size, device):
        #Store dataset so we have access
        self.data_size = data_size
        self.device = device
        #Create ConvNetwork
        self.network = ConvNetwork(data_size).to(device)
        #Use Cross Entropy Loss function
        self.objective = nn.CrossEntropyLoss()
        #Use Adam optimizer
        self.optimizer = optim.Adam(self.network.parameters(), lr=1e-3)

    def reset(self):
        #Reinstantiate new ConvNetwork
        self.network = ConvNetwork(self.data_size).to(self.device)
        #Zero Gradients of optimizer
        self.optimizer.zero_grad()

    def train(self, batch):
        x, y_truth = batch[0].to(self.device), batch[1].to(self.device)
        self.network.train()
        #Zero optimizer gradient
        self.optimizer.zero_grad()
        #Get network predictions
        y_hat = self.network(x)
        #Compute Loss, call .backward()
        loss = self.objective(y_hat, y_truth)
        loss.backward()
        #optimizer step
        self.optimizer.step()

    def compute_accuracy(self, y_hat, y_truth):
        return (y_hat.argmax(1) == y_truth).float().mean()

    def eval(self, validation_set):
        #dim x should len x channels x h x w
        x = torch.cat([x[0] for x in validation_set], dim=0).to(self.device).unsqueeze(1)
        #y_truth should be len
        y_truth = torch.tensor([x[1] for x in validation_set], dtype=torch.long, device=self.device)

        self.network.eval()
        y_hat = self.network(x)
        accuracy = self.compute_accuracy(y_hat, y_truth)

        return y_hat, accuracy

class ConvNetwork(nn.Module):
    def __init__(self, data_size):
        super(ConvNetwork, self).__init__()
        c, h, w = data_size
        self.linear_in = h * w * 10
        output = 10

        self.conv = nn.Sequential(
                nn.Conv2d(c, 10, (3,3), padding=(1,1)),
                nn.BatchNorm2d(10),
                nn.ReLU(),
                nn.Conv2d(10, 10, (3,3), padding=(1,1)),
                nn.BatchNorm2d(10),
                nn.ReLU(),
                nn.Conv2d(10, 10, (3,3), padding=(1,1)),
                nn.BatchNorm2d(10),
                nn.ReLU(),
                nn.Conv2d(10, 10, (3,3), padding=(1,1)),
                nn.BatchNorm2d(10),
                nn.ReLU(),
                nn.Dropout(.2)
        )

        self.linear = nn.Linear(self.linear_in, output)

    def forward(self, x):
        conv_out = self.conv(x).view(-1, self.linear_in)
        return self.linear(conv_out)


import json
from datetime import datetime as dt, datetime

import requests
from flask import jsonify
import jwt

from db_manager.config.agent_url import AGENT_ENDPOINT, AGENT_ATTRACTION_GET, AGENT_TAGS_GET
from db_manager.config.secrets import SERVER_SECRET_KEY, PROFILE_REMOVE_SP, PROFILE_RATE_SET_SP, \
                                      TRIP_UPDATE_RSRV_SP, UPSERT_TRIP_FLIGHT_SP, DEFAULT_STARS, DELETE_TRIP_SP
from db_manager.config.exteranl_apis import FLIGHTS_BASE_ENDPOINT, HOTELS_BASE_ENDPOINT, HOTELS_HEADER
from db_manager.location_code_matcher import LocationMatcher
from api.models.attraction import Attraction
from api.models.city import City
from api.models.tag import Tag
from api.models.tag_attraction import TagAttraction
from api.models.trip import Trip, TripAttraction
from api.models.user import User
from api.models.user_trip_profile import UserProfile, ProfileTag
from api.models.hotel import Hotel, TripHotel
from api.models.flight import TripFlight
from db_manager.pytheas_db_manager_base import PytheasDBManagerBase
from trip_builder.city_trip_builder import CityWalkTripBuilder
from trip_builder.routes_builder.basic_route_builder import BasicRoutesBuilder
from trip_builder.routes_builder.dfs_routes_builder import DFSRoutesBuilder

class SQLPytheasManager(PytheasDBManagerBase):
    # TODO: Refactor exceptions handling

    def __init__(self, db):
        super().__init__(db)

    def initialize(self):
        self.db.create_all()
        self.db.session.commit()
        return "success", 200

    @staticmethod
    def serialize_result(elements):
        serialized_result = []
        if type(elements) is not list:
            elements = [elements]
        for element in elements:
            element = vars(element)
            fields = {}
            for field in [x for x in list(element.keys()) if not x.startswith('_') and x != 'metadata']:
                data = element[field]
                try:
                    fields[field] = data
                except TypeError:
                    fields[field] = None
            serialized_result.append(fields)
        return jsonify(serialized_result)

    #  CREATES FUNCTIONS
    def _create(self, model, **kwargs):
        try:
            new_model = model(**kwargs)
            self.db.session.add(new_model)
            self.db.session.commit()
            return new_model, 200
        except Exception as e:
            return f"Error creating {model}: {e}", 500

    def create_user(self, username, full_name, email, google_token):
        email_exist = User.query.filter_by(email=email).first()
        username_exist = User.query.filter_by(username=username).first()
        if not email_exist and not username_exist:
            new_user, status = self._create(
                User,
                id=None,
                username=username,
                full_name=full_name,
                created=dt.now(),
                email=email,
                google_token=google_token
            )
            if status is not 200:
                return new_user, status
        return jsonify({"token": jwt.encode({'username': username}, SERVER_SECRET_KEY, algorithm='HS256').decode('utf8')}), 200

    def create_tag(self, tag_name):
        return self._create(
            Tag,
            name=tag_name
        )

    def create_attraction(self, name, rate, address, price, description, phone, website, city_id, attraction_id=None):
        return self._create(
            Attraction,
            id=attraction_id,
            name=name,
            rate=rate,
            address=address,
            price=price,
            description=description,
            phone_number=phone,
            website=website,
            city_id=city_id
        )

    def create_city(self, name, country, city_id=None):
        return self._create(
            City,
            name=name,
            country=country,
            id=city_id
        )

    def create_profile(self, username, profile_name, tags, image):
        try:
            # get tags id from name
            user = User.query.filter_by(username=username).first()
            # create new profile
            new_profile = UserProfile(
                user_id=user.id,
                name=profile_name,
                image=image
            )
            self.db.session.add(new_profile)
            self.db.session.commit()

            # create tags
            for tag in tags:
                db_tag = Tag.query.filter_by(name=tag).first()
                if db_tag:
                    new_profile_tag = ProfileTag(
                        tag_id=db_tag.id,
                        profile_id=new_profile.id
                    )
                    self.db.session.add(new_profile_tag)
                    self.db.session.commit()
        except Exception as e:
            print(e)
            self.db.session.rollback()
            return "Error creating new profile", 500
        else:
            return "success", 200

    def upsert_trip(self, username, profile_id, flight_rsrv, hotel_rsrv_code, trip_data):
        trip_id = int(trip_data['id'])
        trip = None
        if trip_id is not None and trip_id > 0:
            trip = Trip.query.filter_by(id=trip_id).first()

        if trip is None:
            return self.create_trip(username, profile_id, flight_rsrv, hotel_rsrv_code, trip_data)
        else:
            return self.update_trip(username, trip_id, flight_rsrv, hotel_rsrv_code)

    def create_trip(self, username, profile_id, flight_rsrv, hotel_rsrv_code, trip_data):
        try:
            user_id = User.query.filter_by(username=username).first().id
            profile_id = UserProfile.query.filter_by(user_id=user_id, id=profile_id).first().id
            if profile_id is None:
                raise Exception("No profile found")

            city_id = City.query.filter_by(name=trip_data['destination']).first().id
            if flight_rsrv is None or flight_rsrv == {} or hotel_rsrv_code is None:
                is_booked = False
            else:
                is_booked = True

            new_trip = Trip(
                user_id=user_id,
                profile_id=profile_id,
                currency=trip_data['currency'],
                price=int(trip_data['price']),
                days=int(trip_data['days']),
                start_date=datetime.strptime(trip_data['start_date'], '%d/%m/%Y'),
                end_date=datetime.strptime(trip_data['end_date'], '%d/%m/%Y'),
                is_booked=is_booked,
                people_number=int(trip_data['people_number']),
                city_id=city_id,
                hotel_rsrv_code=hotel_rsrv_code
            )
            self.db.session.add(new_trip)
            self.db.session.commit()

            if flight_rsrv is not None and flight_rsrv != {}:
                self._upsert_trip_flight(new_trip.id, flight_rsrv)

            attractions = self._add_trip_attractions(trip_data, new_trip)
            self._create_hotel(new_trip.id, trip_data['hotel'])
            return_trip = self._get_trip(new_trip.id)
            return return_trip, 200
        except Exception as e:
            print(e)
            self.db.session.rollback()
            return "Error creating new profile", 500

    def _add_trip_attractions(self, trip_full_data, trip):
        try:
            for day in range(0, len(trip_full_data['places'])):
                for i in range(1, len(trip_full_data['places'][day])):
                    attraction = trip_full_data['places'][day][i]
                    if attraction is None or attraction=={}:
                        continue
                    new_trip_attraction = TripAttraction(
                        trip_id=trip.id,
                        attraction_id=attraction['id'],
                        day=day + 1
                    )
                    self.db.session.add(new_trip_attraction)
                    self.db.session.commit()
        except Exception as e:
            print(e)
            self.db.session.rollback()
            raise e

    def _create_hotel(self, trip_id, hotel_data):
        hotel_id = 0
        hotel_name = hotel_data['name']
        hotel_url = hotel_data['url']
        hotel = Hotel.query.filter_by(name=hotel_name, url=hotel_url).first()
        if hotel is None:
            stars = hotel_data['stars'] if hotel_data['stars'] != DEFAULT_STARS else None
            new_hotel = Hotel(
                name=hotel_name,
                address=hotel_data['address'],
                main_photo_url=hotel_data['main_photo_url'],
                stars=stars,
                url=hotel_url,
                description=hotel_data['description'],
                facilities=""
            )
            self.db.session.add(new_hotel)
            self.db.session.commit()
            hotel_id = new_hotel.id
        else:
            hotel_id = hotel.id

        #Insert trip-hotel
        new_trip_hotel = TripHotel(
            trip_id=trip_id,
            hotel_id=hotel_id,
            start_date=hotel_data['start_date'],
            end_date=hotel_data['end_date'],
            price_per_night=int(hotel_data['price_per_night']),
            room_type=hotel_data['room_type'],
            currency=hotel_data['currency'],
        )
        self.db.session.add(new_trip_hotel)
        self.db.session.commit()

    def update_trip(self, username, trip_id, flight_rsrv_info, hotel_rsrv_code):
        try:
            user_id = User.query.filter_by(username=username).first().id

            trip = Trip.query.filter_by(id=trip_id, user_id=user_id).first()
            if trip is None:
                raise Exception("No trip found")

            trip_flight = TripFlight.query.filter_by(trip_id=trip_id).first()
            if not trip.hotel_rsrv_code:
                trip.hotel_rsrv_code = hotel_rsrv_code if hotel_rsrv_code else trip.hotel_rsrv
            if flight_rsrv_info:
                trip_flight = flight_rsrv_info

            is_booked = False if not trip_flight or not trip.hotel_rsrv_code else True

            self._upsert_trip_flight(trip_id, flight_rsrv_info)
            hotel_rsrv_code = f"'{trip.hotel_rsrv_code}'" if not trip.hotel_rsrv_code.startswith("'") else trip.hotel_rsrv_code
            params = [trip_id, hotel_rsrv_code, is_booked]
            self._exec_procedure(TRIP_UPDATE_RSRV_SP, params)
            self.db.session.commit()

            return_trip = self._get_trip(trip_id)
            return return_trip, 200
        except Exception as e:
            print(e)
            self.db.session.rollback()
            return "Error updating trip", 500

    def _upsert_trip_flight(self, trip_id, flight):
        if flight is not None and flight != {}:
            arrival_time = self._get_string_param(flight['arrival_time'])
            departure_time = self._get_string_param(flight['departure_time'])
            from_city = City.query.filter_by(name=flight['from_city']).first().id
            to_city = City.query.filter_by(name=flight['to_city']).first().id
            duration = self._get_string_param(flight['duration'])
            price = int(flight['price'])
            reservation_number = self._get_string_param(flight['reservation_number'])

            params = [trip_id, arrival_time, departure_time, from_city, to_city, duration, price, reservation_number]
            self._exec_procedure(UPSERT_TRIP_FLIGHT_SP, params)
            self.db.session.commit()

    def set_profile_attraction_rate(self, username, profile_id, attraction_id, rate):
        try:
            user_id = User.query.filter_by(username=username).first().id
            profile_id = UserProfile.query.filter_by(user_id=user_id, id=profile_id).first().id

            rate = int(rate)
            if rate < 1 or rate > 5:
                raise Exception("Rate is out of range")

            self._exec_procedure(PROFILE_RATE_SET_SP, [str(profile_id), str(attraction_id), str(rate)])
        except Exception as e:
            print(e)
            self.db.session.rollback()
            return "Error adding profile rate", 500
        return "success", 200

    #  DELETE FUNCTIONS

    def delete_trip(self, username, trip_id):
        try:
            user_id = User.query.filter_by(username=username).first().id
            if user_id is None:
                raise Exception("No user found")

            params = [trip_id]
            self._exec_procedure(DELETE_TRIP_SP, params)
            self.db.session.commit()
        except Exception as e:
            print(e)
            self.db.session.rollback()
            return "Error deleting trip", 500
        return "success", 200

    #  GET FUNCTIONS

    def get_profile(self, username):
        try:
            user_id = User.query.filter_by(username=username).first().id
            profiles = UserProfile.query.filter_by(user_id=user_id).\
                        with_entities(UserProfile.id, UserProfile.name, UserProfile.image).all()
            return jsonify({
                        'profiles': [{'id': profile.id, 'name': profile.name, 'image': profile.image} for profile in profiles]
                   }), 200
        except Exception as e:
            return f"Error getting user's profiles: {str(e)}", 500

    def get_cities(self):
        try:
            return self.serialize_result(City.query.all()), 200
        except Exception as e:
            return f'Error getting cities: {str(e)}', 500

    def get_tags(self):
        try:
            return self.serialize_result(Tag.query.all())
        except Exception as e:
            return str(e), 500

    def get_attractions(self, **kwargs):
        if kwargs:
            if 'name' in kwargs:
                try:
                    attraction = Attraction.query.filter_by(name=kwargs['name']).first()
                    return attraction
                except:
                    return None
        try:
            return self.serialize_result(Attraction.query.all()), 200
        except Exception as e:
            return f"Error getting attractions: {str(e)}", 500

    def add_to_attr_tags(self, att_id, tag_id):
        try:
            tag_att = TagAttraction(tag_id=tag_id, attraction_id=att_id)
            self.db.session.add(tag_att)
            self.db.session.commit()
            return tag_att, 200
        except Exception as e:
            self.db.session.rollback()
            return f"Error adding tag to attractions: {str(e)}", 500

    def get_trip_attraction(self, trip_id, hotel, city_name):
        attractions_trip = TripAttraction.query.filter_by(trip_id=trip_id)
        parsed_attractions = []
        day = -1
        parsed_hotel = {
            'id': -1,
            'name': hotel['name'],
            'rate': 5,
            'address': hotel['address'],
            'price': int(hotel['price_per_night']),
            'description': '',
            'phone number': '',
            'website': hotel['url'],
            'city': city_name,
            'photo_url': hotel['main_photo_url'],
            'suggested_duration': ''
        }
        for attraction_trip in attractions_trip:
            attraction = Attraction.query.get(attraction_trip.attraction_id)
            parsed_attraction = {
                'id': attraction.id,
                'name': attraction.name,
                'rate': attraction.rate,
                'address': attraction.address,
                'price': int(attraction.price),
                'description': attraction.description,
                'phone number': attraction.phone_number,
                'website': attraction.website,
                'city': City.query.get(attraction.city_id).name,
                'photo_url': attraction.photo_url,
                'suggested_duration': attraction.suggested_duration
            }
            if (attraction_trip.day - 1) is not day:
                parsed_attractions.append([parsed_hotel])
                day += 1
            parsed_attractions[day].append(parsed_attraction)
        return parsed_attractions

    def get_trip_hotel(self, trip_id):
        trip_hotel = TripHotel.query.filter_by(trip_id=trip_id).first()
        if trip_hotel is None:
            return None
        hotel_data = Hotel.query.filter_by(id=trip_hotel.hotel_id).first()
        if hotel_data is None:
            return None
        stars = str(hotel_data.stars) if hotel_data.stars is not None else DEFAULT_STARS
        hotel = {
            "address": hotel_data.address,
            "currency": trip_hotel.currency,
            "end_date": trip_hotel.end_date,
            "facilities": [],
            "main_photo_url": hotel_data.main_photo_url,
            "name": hotel_data.name,
            "price_per_night": trip_hotel.price_per_night,
            "room_type": trip_hotel.room_type,
            "start_date": trip_hotel.start_date,
            "url": hotel_data.url,
            "description": hotel_data.description,
            "stars": stars
        }
        return hotel

    def _get_trip_flight(self, trip_id):
        trip_flight = TripFlight.query.filter_by(trip_id=trip_id).first()
        if trip_flight is None:
            return {}

        from_city_name = City.query.get(trip_flight.from_city).name
        to_city_name = City.query.get(trip_flight.to_city).name

        flight = {
            "arrival_time": trip_flight.arrival_time,
            "departure_time": trip_flight.departure_time,
            "from_city": from_city_name,
            "from_city_code": LocationMatcher.get_iata_for_city(from_city_name),
            "to_city": to_city_name,
            "to_city_code":  LocationMatcher.get_iata_for_city(to_city_name),
            "duration": trip_flight.duration,
            "price": trip_flight.price,
            "reservation_number": trip_flight.reservation_number
        }
        return flight

    def _get_trip(self, trip_id):
        try:
            trip = Trip.query.filter_by(id=trip_id).first()
            city_name = City.query.get(trip.city_id).name
            new_flights = []

            hotel = self.get_trip_hotel(trip.id)
            flight = self._get_trip_flight(trip.id)
            if flight is None or flight == {}:
                date_start = trip.start_date.strftime("%d/%m/%Y")
                date_end = trip.end_date.strftime("%d/%m/%Y")
                new_flights = self._get_flights('tel aviv', city_name, date_start, date_end, trip.people_number)

            attractions = self.get_trip_attraction(trip.id, hotel, city_name)
            if flight is not None and flight != {} and hotel is not None:
                trip.price = int(int(flight['price']) + (int(hotel['price_per_night'])*(trip.days-1)))

            parsed_trip = {
                'id': trip.id,
                'profile': trip.profile_id,
                'destination': city_name,
                'start_date': trip.start_date,
                'end_date': trip.end_date,
                'days': trip.days,
                'price': trip.price,
                'currency': trip.currency,
                'people_number': int(trip.people_number),
                'pictures': [],
                'flights': new_flights,
                'hotel': hotel,
                'explore': False,
                'places': attractions,
                'flight_rsrv': flight,
                'hotel_rsrv_code': trip.hotel_rsrv_code,
                'is_booked': trip.is_booked
            }
        except Exception as e:
            print(e)
            return str(e)
        else:
            return jsonify(parsed_trip)

    def get_trips(self, username):
        try:
            print(datetime.now())
            user_id = User.query.filter_by(username=username).first().id
            trips = Trip.query.filter_by(user_id=user_id)
            print(datetime.now())
            all_trips = []
            new_flights = []
            for trip in trips:
                city_name = City.query.get(trip.city_id).name
                hotel = self.get_trip_hotel(trip.id)
                if hotel is None:
                    continue
                flight = self._get_trip_flight(trip.id)
                if flight is None or flight == {}:
                    date_start = trip.start_date.strftime("%d/%m/%Y")
                    date_end = trip.end_date.strftime("%d/%m/%Y")
                    new_flights = self._get_flights('tel aviv', city_name, date_start, date_end, trip.people_number)
                attractions = self.get_trip_attraction(trip.id, hotel, city_name)

                if flight is not None and flight != {} and hotel is not None:
                    trip.price = int(flight['price']) + (int(hotel['price_per_night']) * (trip.days - 1))

                parsed_trip = {
                    'id': trip.id,
                    'profile': trip.profile_id,
                    'destination': city_name,
                    'start_date': trip.start_date,
                    'end_date': trip.end_date,
                    'days': trip.days,
                    'price': trip.price,
                    'currency': trip.currency,
                    'people_number': int(trip.people_number),
                    'pictures': [],
                    'flights': new_flights,
                    'hotel': hotel,
                    'explore': False,
                    'places': attractions,
                    'flight_rsrv': flight,
                    'hotel_rsrv_code': trip.hotel_rsrv_code,
                    'is_booked': trip.is_booked
                }
                all_trips.append(parsed_trip)
        except Exception as e:
            print(e)
            return str(e), 500
        else:
            return jsonify(all_trips), 200

    def get_explore_trips(self, username, profile, from_date, to_date, city=None, travelers='2', budget=None):
        try:
            user_id = User.query.filter_by(username=username).first().id
            profile_id = UserProfile.query.filter_by(user_id=user_id, id=profile).first().id

            city_id = None
            if city:
                city_id = City.query.filter_by(name=city).first().id
            budget = None if budget == "" else budget
            if budget:
                budget = int(budget)
            fromdate = datetime.strptime(from_date, '%d/%m/%Y')
            todate = datetime.strptime(to_date, '%d/%m/%Y')
            days = (todate - fromdate).days
            estimated_requiired_attractions = days * 6
            agent_response = requests.get(url=(AGENT_ENDPOINT+AGENT_ATTRACTION_GET),
                                          params={'profile_id': profile_id, 'city_id': city_id, 'days': days})

            if agent_response.status_code is not 200:
                return agent_response.content, agent_response.status_code
            agent_results = json.loads(agent_response.content)

            trips = []
            for result in agent_results:
                attractions = []
                city = City.query.filter_by(id=result['city_id']).first().name

                flight_price = 0
                flights = self._get_flights('tel aviv', city, from_date, to_date, travelers)
                if flights is not None and len(flights) > 0 and flights[1] is not 502:
                    flight_price = int(flights[0]["price"])
                else:
                    continue

                hotels = self._get_hotels(city, from_date, to_date, travelers)
                if hotels is None or len(hotels) == 0:
                    continue

                if '5' in result['attractions']:
                    attractions = result['attractions']['5']
                if '4'in result['attractions'] and len(attractions) <= estimated_requiired_attractions:
                    attractions.extend(result['attractions']['4'])
                if '3'in result['attractions'] and len(attractions) <= estimated_requiired_attractions:
                    attractions.extend(result['attractions']['3'])
                if '2'in result['attractions'] and len(attractions) <= estimated_requiired_attractions:
                    attractions.extend(result['attractions']['2'])
                if '1'in result['attractions'] and len(attractions) <= estimated_requiired_attractions:
                    attractions.extend(result['attractions']['1'])
                if len(attractions) < estimated_requiired_attractions:
                    continue

                trip_builder = CityWalkTripBuilder(DFSRoutesBuilder())
                attractions = [Attraction.query.get(attraction_id) for attraction_id in attractions]
                for hotel in hotels:
                    price = int(int(flight_price) + (int(hotel["price_per_night"])*days)) #need to convert currencies
                    if budget is not None and price > budget:
                        continue

                    trips.append({
                        'id': -1,
                        'profile': profile_id,
                        'destination': city,
                        'start_date': from_date,
                        'end_date': to_date,
                        'days': days,
                        'price': price,
                        'currency': 'USD',
                        'people_number': travelers,
                        'pictures': [],
                        'flights': flights,
                        'hotel': hotel,
                        'explore': True,
                        'places': trip_builder.build_trip(days, attractions, city, hotel),
                        'flight_rsrv': None,
                        'hotel_rsrv_code': None,
                        'is_booked': False
                    })
            return jsonify(trips), 200
        except Exception as e:
            return str(e), 500

    def get_trip_config(self, city, username, profile):
        user_id = User.query.filter_by(username=username).first().id
        profile_id = UserProfile.query.filter_by(user_id=user_id, id=profile).first().id
        city_id = City.query.filter_by(name=city).first().id
        return {
            'user_id': user_id,
            'profile_id': profile_id,
            'city_id': city_id
        }

    def get_popular_tags(self):
        try:
            agent_response = requests.get(url=(AGENT_ENDPOINT+AGENT_TAGS_GET), params={})
            if agent_response.status_code is not 200:
                return agent_response.content, agent_response.status_code
            agent_results = json.loads(agent_response.content)
            tags = []
            for result in agent_results:
                id = result[0]
                tag = result[1]
                tags.append(
                    {"id": id, "name": tag}
                )
            return jsonify(tags), 200
        except Exception as e:
            return str(e), 500

    def _get_flights(self, from_city, to_city, from_date, to_date, travelers, max_stop_overs=3):
        max_returned_values = 2
        from_city_code = LocationMatcher.get_iata_for_city(from_city)
        to_city_code = LocationMatcher.get_iata_for_city(to_city)

        for stops in range(0, max_stop_overs):
            flight_url = FLIGHTS_BASE_ENDPOINT + 'flyFrom=' + from_city_code + '&to=' + to_city_code + '&dateFrom=' \
                         + str(from_date) + '&dateTo=' + str(to_date) + '&partner=picky&flight_type=return&' \
                         + 'max_stopovers=' + str(stops)

            api_response = requests.get(url=flight_url, params={})
            if api_response.status_code is not 200:
                return api_response.content, api_response.status_code
            api_results = json.loads(api_response.content)
            if len(api_results['data']) == 0:
                continue

            flights = []
            max_returned_values = min(max_returned_values, len(api_results['data']))
            for i in range(max_returned_values):
                flights.append(
                    {
                        "from_city": from_city,
                        "from_city_code": from_city_code,
                        "to_city": to_city,
                        "to_city_code": to_city_code,
                        "arrival_time": self._get_time_from_timestamp(api_results['data'][i]['aTime']),
                        "departure_time": self._get_time_from_timestamp(api_results['data'][i]['dTime']),
                        "price": int(api_results['data'][i]['price']),
                        "duration": api_results['data'][i]['fly_duration'],
                        "link": api_results['data'][i]['deep_link']
                    }
                )
            return flights

    def get_flights_for_trip(self, from_city, to_city, from_date, to_date, travelers, max_stop_overs=0):
        try:
            flights = self._get_flights(from_city, to_city, from_date, to_date, travelers, max_stop_overs)
            return jsonify(flights), 200
        except Exception as e:
            return str(e), 500

    def _get_hotels(self, city_name, from_date, to_date, travelers, rooms='1'):
        max_returned_values = 2

        city_code = LocationMatcher.get_booking_code_for_city(city_name)
        from_date = datetime.strptime(from_date, '%d/%m/%Y')
        to_date = datetime.strptime(to_date, '%d/%m/%Y')
        days = (to_date - from_date).days

        search_url = HOTELS_BASE_ENDPOINT + 'dest_ids=' + city_code + '&arrival_date=' \
                     + str(from_date.date()) + '&departure_date=' + str(to_date.date()) + '&guest_qty=' + str(travelers) \
                     + '&room_qty=' + rooms

        api_response = requests.get(url=search_url, headers=HOTELS_HEADER)
        if api_response.status_code is not 200:
            return api_response.content, api_response.status_code
        api_results = json.loads(api_response.content)
        hotels = []
        api_results_data = api_results['result']
        max_returned_values = min(max_returned_values, len(api_results_data))

        for i in range(max_returned_values):
            if 'price_breakdown' not in api_results_data[i].keys():
                continue
            row_data = api_results_data[i]
            address = str(row_data['address_trans']) + ',' + str(row_data['district']) + ',' + \
                      str(row_data['city_trans']) + ',' + str(row_data['zip']) + ',' + \
                      str(row_data['country_trans'])
            stars = str(row_data['class']) if row_data['class'] != 0 else DEFAULT_STARS

            hotels.append(
                {
                    "address": address,
                    "currency": row_data['price_breakdown']['currency'],
                    "end_date": to_date.date(),
                    "facilities": [],
                    "main_photo_url": row_data['main_photo_url'].replace('square60', 'square350'),
                    "name": row_data['hotel_name'],
                    "price_per_night": int((float(row_data['price_breakdown']['gross_price']) / days)),
                    "room_type": "",
                    "start_date": from_date.date(),
                    "url": row_data['url'],
                    "description": '',
                    "stars": stars
                }
            )
        return hotels

    def get_hotels(self, city_name, from_date, to_date, travelers, rooms='1'):
        try:
            hotels = self._get_hotels(city_name, from_date, to_date, travelers, rooms)
            return jsonify(hotels), 200
        except Exception as e:
            return str(e), 500

    #Delete functions

    def delete_profile(self, username, profile_id):
        try:
            user = User.query.filter_by(username=username).first()
            profile_id = UserProfile.query.filter_by(user_id=user.id, id=profile_id).first().id
            self._exec_procedure(PROFILE_REMOVE_SP, [str(profile_id)])
        except Exception as e:
            print(e)
            return "Error removing a profile", 500
        return "success", 200

    #Private functions

    def _get_time_from_timestamp(self,timestamp):
        dt = datetime.utcfromtimestamp(int(timestamp)).strftime('%H:%M')
        return dt

    def _exec_procedure(self, procedure_name, params):
        try:
            params_conc = self._join(params, ',')
            print(params_conc)
            query = 'CALL ' + procedure_name + "(" + params_conc + ")"
            results = self.db.session.execute(query, [])
            self.db.session.commit()
            return results
        except Exception as e:
            self.db.session.rollback()
            print(e)
            raise e

    def _join(self, l, sep):
        out_str = ''
        for el in l:
            if el is None:
                el = 'Null'
            out_str += '{}{}'.format(el, sep)
        return out_str[:-len(sep)]

    def _get_string_param(self, param):
        return "'" + str(param) + "'"


#!/usr/bin/env python

import rospy
import rosbag
import tf

import geometry_msgs.msg
import nav_msgs.msg
import sensor_msgs.msg
import std_msgs.msg

import argparse
import matplotlib.pyplot as plt
import numba
import numpy as np
import skimage.draw
import sys
import copy

import numpy as np

dtype = np.float64

LINEAR_MODEL_VAR_X = 0.5
LINEAR_MODEL_VAR_Y = 0.5
ANGULAR_MODEL_VAR = 0.3
SENSOR_MODEL_VAR = 15.0
NUM_PARTICLES = 2000

@numba.jit(nopython=True)
def _CastRay(p0, p1, grid_data):
    '''
    Cast a ray from p0 in the direction toward p1 and return the first cell the
    sensor will detect.
    '''
    delta = np.abs(np.array([p1[0] - p0[0], p1[1] - p0[1]]))
    p = p0.copy()
    n = 1 + np.sum(delta)
    inc = np.array([1 if p1[0] > p0[0] else -1, 1 if p1[1] > p0[1] else -1])
    error = delta[0] - delta[1]
    delta *= 2

    last_p = p.copy()
    for i in range(n):
        if not (0 <= p[0] < grid_data.shape[1]
                and 0 <= p[1] < grid_data.shape[0]):
            return last_p
        last_p = p.copy()

        if grid_data[p[1], p[0]] != 0:  # coord flip for handedness
            return p

        if error > 0:
            p[0] += inc[0]
            error -= delta[1]
        else:
            p[1] += inc[1]
            error += delta[0]

    return p1

@numba.jit(nopython=True)
def _ComputeSimulatedRanges(scan_angles, scan_range_max, world_t_map,
                            map_t_particle, map_R_particle, grid_data,
                            grid_resolution):
    '''
    Cast rays in all directions and calculate distances to obstacles in all
    directions from a given particle.
    '''
    src_cell = ((map_t_particle - world_t_map) / grid_resolution).astype(
        np.int32)

    # Compute max possible cell indices
    angles = scan_angles + map_R_particle
    max_points = map_t_particle + scan_range_max * np.stack(
        (np.cos(angles), np.sin(angles)), axis=-1)
    max_cells = ((max_points - world_t_map) / grid_resolution).astype(np.int32)

    simulated_cell_ranges_sq = np.zeros_like(scan_angles)
    for i in range(len(max_cells)):
        hit_cell = _CastRay(src_cell, max_cells[i], grid_data)
        simulated_cell_ranges_sq[i] = np.sum(np.square(hit_cell - src_cell))

    return grid_resolution * np.sqrt(simulated_cell_ranges_sq)

@numba.jit(nopython=True)
def RotateBy(x, angle):
    c, s = np.cos(angle), np.sin(angle)
    m = np.zeros((2, 2), dtype=dtype)
    m[0, 0] = c
    m[0, 1] = -s
    m[1, 0] = s
    m[1, 1] = c
    return np.dot(m, x)

class Pose(object):
    '''
    2D pose representation of robot w.r.t. world frame.
    Given by the translation from origin to the robot, and then rotation to math
    robot orientation.
    '''
    def __init__(self, rotation=0, translation=[0, 0]):
        self.rotation = rotation
        self.translation = np.array(translation, dtype=dtype)

    @staticmethod
    def FromGeometryMsg(pose_msg):
        q = pose_msg.orientation
        tx = 1 - 2 * (q.y * q.y + q.z * q.z)
        ty = 2 * (q.x * q.y + q.z * q.w)
        yaw = np.arctan2(ty, tx)
        x, y = pose_msg.position.x, pose_msg.position.y
        return Pose(yaw, [x, y])

    def ToGeometryMsg(self):
        msg = geometry_msgs.msg.Pose()
        msg.position.x = self.translation[0]
        msg.position.y = self.translation[1]
        msg.orientation.z = np.sin(self.rotation / 2)
        msg.orientation.w = np.cos(self.rotation / 2)
        return msg

class Grid(object):
    '''
    Occupancy grid representation, with coordinate helpers.
    World is global coordinate frame and grid is index-based array coordinates.
    '''
    def __init__(self, grid_msg):
        self.cols = grid_msg.info.width
        self.rows = grid_msg.info.height
        self.resolution = grid_msg.info.resolution
        self.map_t_extents = [
            self.cols * self.resolution,
            self.rows * self.resolution,
        ]
        self.world_T_map = Pose.FromGeometryMsg(grid_msg.info.origin)
        self.data = np.asarray(grid_msg.data,
                               dtype=np.int8).reshape(self.rows, self.cols)

    def GridToWorld(self, cell_index):
        return self.resolution * cell_index + self.world_T_map.translation

    def WorldToGrid(self, point):
        return ((point - self.world_T_map.translation) /
                self.resolution).astype(np.int32)

    def GetWorldCoords(self, point):
        x, y = self.WorldToGrid(point)
        return self.data[y, x]

    def ToNavMsg(self):
        msg = nav_msgs.msg.OccupancyGrid()
        msg.info.resolution = self.resolution
        msg.info.width = self.cols
        msg.info.height = self.rows
        msg.info.origin = self.world_T_map.ToGeometryMsg()
        msg.data = self.data.flatten().tolist()
        return msg

class Scan(object):
    '''
    Scan representation / cache.
    Scan consists of angles and distances (ranges).
    '''
    def __init__(self, scan_msg):
        angle_min = scan_msg.angle_min
        angle_max = scan_msg.angle_max
        self.range_max = scan_msg.range_max

        self.angles = np.linspace(angle_min,
                                  angle_max,
                                  num=len(scan_msg.ranges))

        self.ranges = np.array(scan_msg.ranges, dtype=dtype)

class Particle(object):
    '''Individual particle representation.'''
    def __init__(self, grid, map_T_particle=None):
        if map_T_particle is None:
            ll = grid.world_T_map.translation
            ul = ll + grid.map_t_extents

            found = False
            while not found:
                translation = np.random.uniform(ll, ul)
                if grid.GetWorldCoords(translation) == 0:
                    break

            map_T_particle = Pose(rotation=np.random.uniform(-np.pi, np.pi),
                                  translation=translation)

        self.grid = grid
        self.map_T_particle = map_T_particle
        self.last_odom_timestamp = None

    def UpdateOdom(self, odom_msg):
        '''Propagate this particle according to the sensor data and motion model.'''
        if self.last_odom_timestamp is not None:
            dt = (odom_msg.header.stamp - self.last_odom_timestamp).to_sec()
        else:
            dt = 0
        self.last_odom_timestamp = odom_msg.header.stamp

        #  Extract the particle's velocity in its local frame
        vel_x = odom_msg.twist.twist.linear.x
        vel_y = odom_msg.twist.twist.linear.y
        #  Add noise to the velocity, with component variance
        vel_x += np.random.normal(0, np.sqrt(LINEAR_MODEL_VAR_X))
        vel_y += np.random.normal(0, np.sqrt(LINEAR_MODEL_VAR_Y))
        #  Transform the linear velocity into map frame
        vel = np.array([vel_x, vel_y]).reshape((2, 1))
        curr_pose = self.map_T_particle
        actual_vel = RotateBy(vel, curr_pose.rotation)
        #  Integrate the linear velocity to the particle pose
        self.map_T_particle.translation += (actual_vel * dt).reshape((-1))
        #  Extract the particle's rotational velocity
        rotational_vel = odom_msg.twist.twist.angular.z
        #  Add noise to the rotational velocity
        rotational_vel += np.random.normal(0, np.sqrt(ANGULAR_MODEL_VAR))
        #  Integrate the rotational velocity into the particle pose
        self.map_T_particle.rotation += rotational_vel * dt

    def _ComputeSimulatedRanges(self, scan):
        translation = self.map_T_particle.translation
        rotation = self.map_T_particle.rotation
        return _ComputeSimulatedRanges(scan.angles, scan.range_max,
                                       self.grid.world_T_map.translation,
                                       translation, rotation, self.grid.data,
                                       self.grid.resolution)

    def UpdateScan(self, scan):
        '''
        Calculate weights of particles according to scan / map matching.
        '''
        sim_ranges = self._ComputeSimulatedRanges(scan)

        #  Compute the likelihood of each beam, compared to sim_ranges
        probs = []
        for i in range(len(sim_ranges)):
            r = scan.ranges[i]
            r_est = sim_ranges[i]
            p = 1 / np.sqrt(2 * np.pi * SENSOR_MODEL_VAR) * np.exp(-pow(r - r_est, 2) / (2 * SENSOR_MODEL_VAR))
            probs.append(p)
        #  Assign the weight of this particle
        prod = 1
        for i in probs:
            prod *= i
        self.weight = prod

class ParticleFilter(object):
    def __init__(self, grid, num_particles):
        self.grid = grid
        self.last_timestamp = None
        self.particles = [Particle(grid) for _ in range(num_particles)]
        self.pose_publisher = rospy.Publisher("pose_hypotheses",
                                              geometry_msgs.msg.PoseArray,
                                              queue_size=10)
        self.scan_publisher = rospy.Publisher("lidar",
                                              sensor_msgs.msg.LaserScan,
                                              queue_size=10)
        self.tf_broadcaster = tf.TransformBroadcaster()

    def GetMeanPose(self):
        sum_translation = np.zeros(2, dtype=dtype)
        sum_rotation = 0
        for particle in self.particles:
            sum_translation += particle.map_T_particle.translation
            sum_rotation += particle.map_T_particle.rotation

        avg_translation = sum_translation / len(self.particles)
        avg_rotation = sum_rotation / len(self.particles)

        return Pose(avg_rotation, avg_translation)

    def GetPoseArray(self):
        msg = geometry_msgs.msg.PoseArray()
        msg.poses = [
            particle.map_T_particle.ToGeometryMsg()
            for particle in self.particles
        ]
        msg.header.stamp = self.last_timestamp
        msg.header.frame_id = "map"

        return msg

    def UpdateOdom(self, odom_msg):
        if self.last_timestamp is None:
            self.last_timestamp = odom_msg.header.stamp

        for particle in self.particles:
            particle.UpdateOdom(odom_msg)

    def UpdateScan(self, scan_msg):
        if self.last_timestamp is None:
            self.last_timestamp = scan_msg.header.stamp

        scan = Scan(scan_msg)

        # Update weights for particles.
        for particle in self.particles:
            particle.UpdateScan(scan)

        #  Each particle has a weight
        weights = np.array([self.particles[i].weight for i in range(len(self.particles))])
        #  Use these weights to build a normalized discrete probability distribution for sampling each particle
        norm_weights = weights / np.sum(weights)
        #  Randomly choose the indices of the particles to propagate
        select_indices = np.random.choice(NUM_PARTICLES, size=NUM_PARTICLES, p=norm_weights)
        #  Copy the proper particles into a new population
        select_particles = [copy.deepcopy(self.particles[i]) for i in select_indices]
        #  Set self.particles to the new population of particles.
        self.particles = select_particles

        # Publish cloud for visualization.
        self.pose_publisher.publish(self.GetPoseArray())
        avg_pose = self.GetMeanPose()
        avg_trans = (avg_pose.translation[0], avg_pose.translation[1], 0)
        avg_quat = tf.transformations.quaternion_from_euler(
            0, 0, avg_pose.rotation)
        self.tf_broadcaster.sendTransform(avg_trans, avg_quat,
                                          scan_msg.header.stamp, "robot",
                                          "map")
        self.scan_publisher.publish(scan_msg)

def main():
    # rospy initialization.
    rospy.init_node('usc545mcl')
    args = rospy.myargv(argv=sys.argv)

    argparser = argparse.ArgumentParser()
    argparser.add_argument('--num_particles', type=int, default=NUM_PARTICLES)
    argparser.add_argument('bag_filename', type=str)
    args = argparser.parse_args(args[1:])

    # Wait for map server to start up.
    print("Waiting for map_server...")
    grid_msg = rospy.wait_for_message("/map", nav_msgs.msg.OccupancyGrid)
    print("Map received.")

    # Construct particle filter for the received map.
    grid = Grid(grid_msg)
    mcl = ParticleFilter(grid, args.num_particles)

    # "Subscribe" to message channels from bag.
    # You can see the relevant channels by running
    # $ rosbag info /path/to/bag
    bag = rosbag.Bag(args.bag_filename)
    subscribers = {"lidar": mcl.UpdateScan, "odom": mcl.UpdateOdom}
    x_err = []
    y_err = []
    yaw_err = []
    for topic, msg, t in bag.read_messages(topics=["gt_odom"] +
                                           subscribers.keys()):
        if topic == "gt_odom":
            x = msg.pose.pose.position.x
            y = msg.pose.pose.position.y
            yaw = 2 * np.arcsin(msg.pose.pose.orientation.z)

            mcl_pose = mcl.GetMeanPose()
            x_err.append((x - mcl_pose.translation[0])**2)
            y_err.append((y - mcl_pose.translation[1])**2)
            yaw_err.append((yaw - mcl_pose.rotation)**2)

        else:
            subscribers[topic](msg)

    plt.plot(x_err, label="x_mse")
    plt.plot(y_err, label="y_mse")
    plt.plot(yaw_err, label="yaw_mse")
    plt.legend()
    plt.show()

if __name__ == '__main__':
    main()


from sklearn.datasets import make_blobs

X,Y = make_blobs(random_state = 10)

# from sklearn.cluster import KMeans
# kmeans = KMeans(n_clusters = 3)
# k_predict = kmeans.fit_predict(X)
#
# from python_prac.adspy_shared_utilities import plot_labelled_scatter
# plot_labelled_scatter(X,k_predict,class_labels = ['cluster1','cluster2','cluster3'])

# from sklearn.cluster import KMeans
#
# k_means = KMeans(n_clusters = 3)
# k_means.fit(X)

from python_prac.adspy_shared_utilities import plot_labelled_scatter

# plot_labelled_scatter(X,k_means.labels_,['cluster1','cluster2','cluster3'])

# import pandas as pd
#
# fruits = pd.read_table('C:\\Users\\ASUS\\Desktop\\python_prac\\knn\\fruit_data_with_colors.txt')
# X_fruits = fruits[['mass','width','height','color_score']].as_matrix()
# Y_fruits = fruits[['fruit_label']] - 1
#
# from sklearn.preprocessing import MinMaxScaler
# X_fruits_normalized = MinMaxScaler().fit_transform(X_fruits)
#
# k_means2 = KMeans(n_clusters = 4,random_state = 0)
# k_means2.fit(X_fruits)
#
# plot_labelled_scatter(X,k_means2.labels_,['cluster1','cluster2','cluster3','cluster4'])

# AGGLOMERATIVE CLUSTERING
from sklearn.cluster import AgglomerativeClustering
cls = AgglomerativeClustering(n_clusters = 3)#n_clusters paramter that causesthe algo to stop when it has
#reach the number of clusters

# cls.fit(X)
#cls.predict(X)
cls_predict = cls.fit_predict(X)
plot_labelled_scatter(X = X,y = Y,class_labels = ['cluster1','cluster2','cluster3'])

#DENDOGRAM EXAPMPLE(IT IS USED TO FOR REPRESNTATION OF HEIRARICHAL CLUSTERING
from scipy.cluster.hierarchy import ward,dendrogram
# these ward function returns an array which can then be passed to the dendogram function to plot the tree
X,Y = make_blobs(random_state = 10,n_samples = 10)

import matplotlib.pyplot as plt
plt.figure()
dendrogram(ward(X))
plt.title('Dendogram')
plt.show()

from sklearn.cluster import DBSCAN

X1,Y1 = make_blobs(random_state = 9,n_samples = 25)
dbscan = DBSCAN(eps = 2,min_samples = 2)
cls = dbscan.fit_predict(X1)
print('cluster membership values : \n{}',format(cls))
print(cls)
plot_labelled_scatter(X1,cls + 1,['noise','cluster1','cluster2','cluster3'])


# 99 VIP P40
import logging

# 定义一个类封装自定义日志流程
class LogClass():
    def get_log(self):
        # 1、日志收集器
        logger = logging.getLogger("log_test40")

        # 2、设置日志收集器的级别
        logger.setLevel(logging.WARN)

        # 3、设置日志输出的渠道，一个是控制台输出，一个是在后台形成文件
        handle1 = logging.StreamHandler()
        handle2 = logging.FileHandler(filename="log_demo.log",encoding="utf-8")

        # 可选步骤
        # 设置输出渠道的日志级别（注意要大于前面收集器设置的级别）
        handle1.setLevel(logging.ERROR)

        # 4、设置日志输出的格式
        fmt = "%(asctime)s %(name)s %(levelname)s %(filename)s %(lineno)d %(message)s) "
        formt = logging.Formatter(fmt)

        # 5、需要让控制台输出的渠道格式为刚刚定义好的格式：formt
        handle1.setFormatter(formt)

        # 6、日志收集器添加需要的日志输出渠道
        logger.addHandler(handle1)
        logger.addHandler(handle2)

        # 测试日志生成打印
        # logger.warning("运行失败警告")
        # logger.error("运行出错")
        return logger

def sum_data(a,b):
    try:
        return a + b
    except Exception as e:
        LogClass().get_log().error("这个计算参数有问题，这是报错信息")

if __name__ == '__main__':
    # LogClass().get_log().error("报错信息")
    print(sum_data(1,2))
    print(sum_data(1,"2"))


import sqlite3

conn = sqlite3.connect('database.db')

from datetime import datetime

conn.execute("""CREATE TABLE IF NOT EXISTS books (
                title TEXT,
                author TEXT,
                pages INTEGER,
                published INTEGER
                )""")

values = ('Deep Learning',
          'Ian Goodfellow et al.',
          775,
          datetime(2016, 11, 18).timestamp())

conn.execute("""INSERT INTO books VALUES (?, ?, ?, ?)""", values)

r = conn.execute("""SELECT * FROM books""")
print(r.fetchall())


import sys
input = sys.stdin.readline
def n_int():
    return int(input())

def n_str():
    return input()

def map_int():
    return map(int, input().split())

def n_list():
    return list(map(int, input().split()))

def n_lislis(n):
    a = []
    for _ in range(n):
        a.append(list(map(int, input().split())))
    return a

import sys
sys.setrecursionlimit(10**7)
h, w= map_int()

c = []
for _ in range(h):
    c.append(input())

for i in range(h):
    for j in range(w):
        if c[i][j] =='S':
            y0, x0 = (i,j)
            break

visited = [[-1]*w for _ in range(h)]
from collections import deque
def bfs(y0, x0):
    visited = [[-1]*w for _ in range(h)]
    visited[y0][x0] = 0
    q =deque()
    q.append([y0,x0])

    while(len(q) > 0):
        y, x = q.popleft()
        for dy, dx in [[-1, 0], [1, 0], [0,-1], [0,1]]:
            ny , nx = y + dy, x+dx
            if ny < 0 or ny >=h or nx<0 or nx >=w:
                continue
            if visited[ny][nx] != -1 or c[ny][nx] == '#' or c[ny][nx] == 'S':
                continue
            visited[ny][nx] = visited[y][x] + 1
            q.append([ny,nx])

    return visited

for dy1, dx1 in [[-1, 0], [1, 0], [0,-1], [0,1]]:
    for dy2, dx2 in [[-1, 0], [1, 0], [0,-1], [0,1]]:
        if (dy1 == dy2) and (dx1 == dx2):
            continue
        ny1, nx1 = y0 + dy1, x0 + dx1
        ny2, nx2 = y0 + dy2, x0 + dx2
        if ny1 < 0 or ny1 >=h or nx1 < 0 or nx1 >= w or ny2 < 0 or ny2 >=h or nx2 < 0 or nx2 >= w:
            continue
        if c[ny1][nx1] =='#' or c[ny2][nx2] == '#':
            continue
        visited = bfs(ny1, nx1)
        if visited[ny1][nx1] != -1 and visited[ny2][nx2] != -1:
            print('Yes')
            exit()
print('No')


import matplotlib.pyplot as plt
plt.style.use('classic') # 其它style可以试试seaborn, bmh, ggplot, grayscale
import numpy as np

fig = plt.figure()  # 创建图形
ax = plt.axes()  # 创建维度

x = np.linspace(0, 10, 1000)  # 在0到10之间生成1000个等间隔的数

# 已有x, 画出sin(x)的数据, 颜色设置为红,加入label用于制作图例
ax.plot(x, np.sin(x), color='red', label='sin(x)')

# 在同一幅图形中绘制多根线条，只需要多次调用plot函数即可
# 颜色设置为16进制金色，16进制需要前面打井号
# linestyle 可以是 -  --  -.  :
ax.plot(x, np.cos(x), color='#ffd700', linestyle='--', label='cos(x)')

plt.xlim(0, 11)  # 调整x轴的范围
plt.ylim(-1.5, 1.5)  # 调整y轴的范围
# 同样功能的函数：plt.axis([xmin, xmax, ymin, ymax])
# 如： plt.axis([-10, 10, -2, 2])
# plt.axis('tight')  # 可以将坐标轴压缩到刚好足够绘制折线图像的大小
# plt.axis('equal')  # 设置x轴与y轴使用相同的长度单位

plt.title("A Sine Curve")  # 设置标题
plt.xlabel("x")  # 设置x轴标签
plt.ylabel("sin(x)")  # 设置y轴标签
plt.legend()  # 召唤标签
plt.show()  # 显示图像


import numpy as np

from typing import Optional

from automl.components.transformers.category_coalescer import CategoryCoalescer

from ..stage import AutoMLStage
from ...problems.problem_type import ProblemType
from ...components.flow import (
    ColumnTransformer,
    TopPipeline,
)
from ...components.transformers import *
from ...components.estimators import *
from ...components.component import Component, ComponentLevel, ComponentConfig
from ...components.estimators.linear_model.linear_model_estimator import (
    LinearModelEstimator,
)
from ...components.estimators.neural_network.neural_network_estimator import (
    NeuralNetworkEstimator,
)
from ...components.estimators.knn.knn_estimator import KNNEstimator
from ...utils import validate_type

from automl_models.components.flow.column_transformer import make_column_selector

def _scaler_passthrough_condition(config, stage) -> bool:
    return config.estimator is None or not isinstance(
        config.estimator, (LinearModelEstimator, KNNEstimator, NeuralNetworkEstimator)
    )

def _column_is_binary_condition(column) -> bool:
    return len(column.cat.categories) <= 2

categorical_selector = make_column_selector(dtype_include="category")
numeric_selector = make_column_selector(dtype_exclude=["category", "bool"])

categorical_binary_selector = make_column_selector(
    _column_is_binary_condition, dtype_include="category"
)
categorical_not_binary_selector = make_column_selector(
    _column_is_binary_condition, dtype_include="category", negate_condition=True
)

def create_pipeline_blueprint(
    problem_type: ProblemType,
    X=None,
    y=None,
    categorical_columns: Optional[list] = None,
    numeric_columns: Optional[list] = None,
    level: ComponentLevel = ComponentLevel.COMMON,
    is_secondary: bool = False,
) -> TopPipeline:
    validate_type(problem_type, "problem_type", ProblemType)
    validate_type(level, "level", ComponentLevel)

    # steps in [] are tunable
    # there are three possible states for a step depending on an estimator:
    # - none of the components are valid
    # - one component is valid
    # - all components are valid
    # make sure that is the case!

    passthrough = {
        "Passthrough": Passthrough(),
        "Passthrough_Scaler": Passthrough(
            validity_condition=_scaler_passthrough_condition
        ),
    }
    unknown_categories = {"UnknownCategoriesDropper": UnknownCategoriesDropper()}
    category_coalescers = {"CategoryCoalescer": CategoryCoalescer()}
    imbalance = {"AutoSMOTE": AutoSMOTE()}
    imputers = {
        "CombinedSimpleImputer": CombinedSimpleImputer(),
        "IterativeImputer": IterativeImputer(),
    }
    scalers_normalizers = {
        "CombinedScalerTransformer": CombinedScalerTransformer(),
        "MinMaxScaler": MinMaxScaler(),
    }
    target_transformers = {"QuantileTransformer": QuantileTargetTransformer()}
    binary_encoders = {
        "BinaryEncoder": BinaryEncoder(),
    }
    categorical_encoders = {
        "OneHotEncoder": OneHotEncoder(),
        "CatBoostEncoderBinary": CatBoostEncoderBinary(),
        "CatBoostEncoderMulticlass": CatBoostEncoderMulticlass(),
        "CatBoostEncoderRegression": CatBoostEncoderRegression(),
        # "BayesianTargetEncoderBinary": BayesianTargetEncoderBinary(),
        # "BayesianTargetEncoderMulticlass": BayesianTargetEncoderMulticlass(),
        # "BayesianTargetEncoderRegression": BayesianTargetEncoderRegression(),
    }
    oridinal_encoder = {"OrdinalEncoder": OrdinalEncoder()}
    feature_selectors = {
        # "BorutaSHAPClassification": BorutaSHAPClassification(),
        # "BorutaSHAPRegression": BorutaSHAPRegression(),
        "SHAPSelectFromModelClassification": SHAPSelectFromModelClassification(),
        "SHAPSelectFromModelRegression": SHAPSelectFromModelRegression(),
    }
    svm_kernels = {
        "NystroemRBF": NystroemRBF(),
        "PolynomialCountSketch": PolynomialCountSketch(),
        "NystroemSigmoid": NystroemSigmoid(),
    }
    knn_transformers = {
        "KNNTransformer": KNNTransformer(),
        # "NCATransformer": NCATransformer(),
    }
    estimators = {
        # "DecisionTreeClassifier": DecisionTreeClassifier(),
        # "DecisionTreeRegressor": DecisionTreeRegressor(),
        "LogisticRegression": LogisticRegression(),
        # "LogisticRegression_L1": LogisticRegression(l1_ratio=1),
        "LogisticRegression_EN": LogisticRegression(l1_ratio=0.15, alpha=0.01),
        "LinearRegression": LinearRegression(),
        "ElasticNet": ElasticNet(),
        "ElasticNet_EN": ElasticNet(l1_ratio=0.15, alpha=0.01),
        "LGBMClassifier": LGBMClassifier(),
        "LGBMClassifier_UnBalanced": LGBMClassifier(class_weight=None),
        "LGBMRegressor": LGBMRegressor(),
        "CatBoostClassifierBinary": CatBoostClassifierBinary(),
        "CatBoostClassifierMulticlass": CatBoostClassifierMulticlass(),
        "CatBoostRegressor": CatBoostRegressor(),
        "CatBoostClassifierBinary_UnBalanced": CatBoostClassifierBinary(
            auto_class_weights=None
        ),
        "CatBoostClassifierMulticlass_UnBalanced": CatBoostClassifierMulticlass(
            auto_class_weights=None
        ),
        "RandomForestClassifier": RandomForestClassifier(),
        "RandomForestRegressor": RandomForestRegressor(),
        "ExtraTreesClassifier": RandomForestClassifier(randomization_type="et"),
        "ExtraTreesRegressor": RandomForestRegressor(randomization_type="et"),
        "RandomForestClassifier_UnBalanced": RandomForestClassifier(
            class_weight=None
        ),
        "ExtraTreesClassifier_UnBalanced": RandomForestClassifier(
            randomization_type="et", class_weight=None
        ),
        "LinearSVC": LinearSVC(),
        "LinearSVR": LinearSVR(),
        "KNeighborsClassifier": KNeighborsClassifier(),
        "KNeighborsRegressor": KNeighborsRegressor(),
        "FTTransformerClassifier": FTTransformerClassifier(),
        "FTTransformerRegressor": FTTransformerRegressor(),
        "FastAINNClassifier": FastAINNClassifier(),
        "FastAINNRegressor": FastAINNRegressor(),
        "GaussianNB": GaussianNB(),
        "QuadraticDiscriminantAnalysis": QuadraticDiscriminantAnalysis(),
    }
    components = {
        **passthrough,
        **unknown_categories,
        **category_coalescers,
        **imbalance,
        **imputers,
        **scalers_normalizers,
        **binary_encoders,
        **categorical_encoders,
        **oridinal_encoder,
        **feature_selectors,
        **svm_kernels,
        **knn_transformers,
        **estimators,
    }

    pipeline_steps = [
        (
            "target_pipeline__TransformTarget",
            [components["Passthrough"]] + list(target_transformers.values()),
        ),
        ("UnknownCategories", list(unknown_categories.values())),
        ("Imputer", list(imputers.values())),
        (
            "CategoryCoalescer",
            list(category_coalescers.values()),
        ),
        (
            "FeatureSelector",
            [components["Passthrough"]] + list(feature_selectors.values()),
        ),
        ("Imbalance", [components["Passthrough"]] + list(imbalance.values())),
        (
            "ColumnOrdinal",
            ColumnTransformer(
                transformers=[
                    (
                        "OrdinalEncoder",
                        [components["OrdinalEncoder"]],
                        categorical_selector,
                    ),
                ],
            ),
        ),
        (
            "ColumnEncoding",
            ColumnTransformer(
                transformers=[
                    (
                        "BinaryEncoder",
                        [components["BinaryEncoder"]],
                        categorical_binary_selector,
                    ),
                    (
                        "CategoricalEncoder",
                        list(categorical_encoders.values()),
                        categorical_not_binary_selector,
                    ),
                ],
            ),
        ),
        (
            "ColumnScaling",
            ColumnTransformer(
                transformers=[
                    (
                        "ScalerNormalizer",
                        list(scalers_normalizers.values()),
                        numeric_selector,
                    ),
                ],
            ),
        ),
        (
            "SVMKernelApproximation",
            list(svm_kernels.values()),
        ),
        (
            "KNNTransformer",
            list(knn_transformers.values()),
        ),
        (
            "Estimator",
            list(estimators.values()),
        ),
    ]

    pipeline = TopPipeline(
        steps=pipeline_steps,
        # preset_configurations=[d, d2]
    )
    config = ComponentConfig(
        level=level,
        problem_type=problem_type,
        categorical_columns=categorical_columns,
        numeric_columns=numeric_columns,
        X=X,
        y=y,
    )
    # TODO: move to trainer
    pipeline.call_tuning_grid_funcs(config=config, stage=AutoMLStage.PREPROCESSING)
    pipeline.convert_duplicates_in_steps_to_extra_configs()
    pipeline.remove_invalid_components(
        pipeline_config=config,
        current_stage=AutoMLStage.PREPROCESSING,
    )
    return pipeline


# -*- coding: utf-8 -*-

from collections import OrderedDict

from brainpy import backend
from brainpy.backend import ops
from brainpy.simulation import utils
from .dynamic_system import DynamicSystem
from .neu_group import NeuGroup
from .syn_conn import SynConn

__all__ = [
    'Network',
]

class Network(object):
    """The main simulation controller in ``BrainPy``.

    ``Network`` handles the running of a simulation. It contains a set
    of objects that are added with `add()`. The `run()` method actually
    runs the simulation. The main loop runs according to user add orders.
    The objects in the `Network` are accessible via their names, e.g.
    `net.name` would return the `object`.
    """

    def __init__(self, *args, show_code=False, **kwargs):
        # record the current step
        self.t_start = 0.
        self.t_end = 0.

        # store all nodes
        self.all_nodes = OrderedDict()

        # store the step function
        self.run_func = None
        self.show_code = show_code

        # add nodes
        self.add(*args, **kwargs)

        # driver
        self.driver = backend.get_net_driver()(target=self)

    def __getattr__(self, item):
        if item in self.all_nodes:
            return self.all_nodes[item]
        else:
            return super(Network, self).__getattribute__(item)

    def _add_obj(self, obj, name=None):
        # 1. check object type
        if not isinstance(obj, DynamicSystem):
            raise ValueError(f'Unknown object type "{type(obj)}". '
                             f'Currently, Network only supports '
                             f'{NeuGroup.__name__} and {SynConn.__name__}.')
        # 2. check object name
        name = obj.name if name is None else name
        if name in self.all_nodes:
            raise KeyError(f'Name "{name}" has been used in the network, '
                           f'please change another name.')
        # 3. add object to the network
        self.all_nodes[name] = obj

    def add(self, *args, **kwargs):
        """Add object (neurons or synapses) to the network.

        Parameters
        ----------
        args
            The nameless objects.
        kwargs
            The named objects, which can be accessed by `net.xxx`
            (xxx is the name of the object).
        """
        for obj in args:
            self._add_obj(obj)
        for name, obj in kwargs.items():
            self._add_obj(obj, name)

    def run(self, duration, inputs=(), report=False, report_percent=0.1):
        """Run the simulation for the given duration.

        This function provides the most convenient way to run the network.
        For example:

        Parameters
        ----------
        duration : int, float, tuple, list
            The amount of simulation time to run for.
        inputs : list, tuple
            The receivers, external inputs and durations.
        report : bool
            Report the progress of the simulation.
        report_percent : float
            The speed to report simulation progress.
        """
        # preparation
        start, end = utils.check_duration(duration)
        dt = backend.get_dt()
        ts = ops.arange(start, end, dt)

        # build the network
        run_length = ts.shape[0]
        format_inputs = utils.format_net_level_inputs(inputs, run_length)
        self.run_func = self.driver.build(duration=duration,
                                          formatted_inputs=format_inputs,
                                          show_code=self.show_code)

        # run the network
        res = utils.run_model(self.run_func, times=ts, report=report, report_percent=report_percent)

        # end
        self.t_start, self.t_end = start, end
        for obj in self.all_nodes.values():
            if obj.mon.num_item > 0:
                obj.mon.ts = ts
        return res

    @property
    def ts(self):
        """Get the time points of the network.
        """
        return ops.arange(self.t_start, self.t_end, backend.get_dt())

    def schedule(self):
        """Network scheduling in a network.
        """
        for node in self.all_nodes.values():
            for key in node.schedule():
                yield f'{node.name}.{key}'