import json
from collections import deque, defaultdict
from copy import deepcopy

import datetime
import gym
import numpy as np
import pandas as pd
import time
import torch
import torch.nn.functional as F
import torch.optim as optim
from pathlib import Path
from torch.optim.lr_scheduler import ReduceLROnPlateau
from torch.utils.tensorboard import SummaryWriter
from typing import Dict, List, Tuple, Optional

from src.models.tg_edge_action_models import PolicyGNN
from src.training.torch_utils import get_available_device

class PPOAgent:
    def __init__(
            self,
            env: Optional[gym.Wrapper],
            config: dict,
            model: PolicyGNN,
            eval_seeds: List[int],
            baseline_eval_values: Dict[str, Dict[int, float]],
    ):
        self.config = config
        self.env: gym.Wrapper = env
        self.device = get_available_device()
        print(f'device chosen:{self.device}')
        self.policy: torch.nn.Module = model.to(self.device)
        self.old_policy = deepcopy(self.policy)
        self.old_policy.load_state_dict(self.policy.state_dict())
        self.optimizer = optim.Adam(self.policy.parameters(), lr=self.config['lr'])
        self.episode_rewards = []
        self.n_ppo_updates = self.config['n_ppo_updates']
        self.target_kl = self.config['target_kl']
        # fpr clamped policy loss (PPO)
        self.eps_clip = self.config['eps_clip']
        self.logit_normalizer = self.config['logit_normalizer']
        self.episode_log_probabilities = []
        (
            self.state,
            self.reward,
            self.next_state,
            self.done,
            self.total_episode_score_so_far,
        ) = (None, None, None, None, None)
        self.episode_step_number = 0
        self.episode_number = 0
        self.episode_number_in_batch = 0
        self.reward_normalizer = 1
        # lr scheduler for changing the lr during training.
        self.lr_scheduler = ReduceLROnPlateau(
            optimizer=self.optimizer,
            factor=0.95,
            patience=200,
            cooldown=200,
            verbose=True,
        )
        time_str = datetime.datetime.now().strftime('%Y-%m-%d_%H_%M_%S')
        output_dir = f"runs/{self.config['problem_name']}/ppo_agent/{time_str}"
        path = Path(output_dir)
        if not path.exists():
            path.mkdir(parents=True)
        self.writer = SummaryWriter(log_dir=output_dir)
        self.config['model_save_dir'] = output_dir
        self.eval_seeds = eval_seeds
        self.evaluation_episodes = 0
        self.baseline_eval_values = baseline_eval_values or {}
        self.total_rewards_window = deque(
            maxlen=self.config['reward_average_window_size']
        )
        self.best_overall_models = []
        # dictionary where the key is the reward and the value is the episode number (used for
        # deleting the worst model saved so far)
        self.best_overall_model_episodes = {}
        self.batch_states, self.batch_rtgs, self.batch_actions, self.batch_log_probs, self.batch_vals, self.batch_advantage = [], [], [], [], [], []
        self.best_episode_score = np.inf

    def reset_game(self):
        """Resets the game information so we are ready to play a new episode"""
        self.state = self.env.reset()
        self.next_state = None
        self.reward = None
        self.done = False
        self.total_episode_score_so_far = 0
        self.episode_rewards = []
        self.episode_log_probabilities = []
        self.episode_step_number = 0

    def train(self):
        # dump hyperparams to tb:
        self.writer.add_text(f'config/{self.config["run_name"]}', json.dumps(self.config, indent=True), 0)
        for episode in range(self.config['number_of_episodes']):
            self.train_episode()
            if episode % self.config['evaluate_every'] == 0 and episode > 0:
                print(f'Episode: {episode}, running evaluation')
                self.evaluate()
            if episode % self.config['save_checkpoint_every'] == 0 and episode > 0:
                print('Saving a checkpoint')
                self.save_checkpoint()

    def train_episode(self):
        """Runs a step within a game including a learning step if required"""
        # start the environment with a seed from a list of seeds
        self.env.seed(np.random.randint(0, self.config['num_train_seeds']))
        self.reset_game()
        loss = 0
        while not self.done:
            # print(f"calculating episode {self.episode_number},  step:{self.episode_step_number}")
            self.episode_step()
            # self.update_next_state_reward_done_and_score()
            # TODO need to update time_to_learn to be based on number of batches
            if self.time_to_learn():
                loss = self.learn()
            self.state = self.next_state  # this is to set the state for the next iteration
            self.episode_step_number += 1
        self.episode_number += 1
        self.total_rewards_window.append(sum(self.episode_rewards))

    def episode_step(self, train=True):
        """Picks and then conducts actions. Then saves the log probabilities of the actions it
        conducted to be used for
        learning later"""
        action, log_probability, val = self.select_action_and_log_prob(state=self.state)
        # store the state, action, reward and log-prob of this step (before action is preformed)
        self.next_state, self.reward, self.done, info = self.env.step(action)
        if train:
            self.store_step(state=self.state, reward=self.reward, action=action, log_prob=log_probability, val=val)
        if self.done:
            # add return to go (rtg) of this episode to batch rtgs
            self.batch_rtgs.extend(self.compute_episode_rtgs())
            if train:
                self.episode_number_in_batch += 1
        self.total_episode_score_so_far += self.reward

    def select_action_and_log_prob(self, state):
        """Picks actions and then calculates the log probabilities of the actions it picked given
        the policy"""
        action, logprob, value = self.policy.step(state, self.device)
        return action.item(), logprob.item(), value.item()

    def store_step(self, state, reward, action, log_prob, val):
        # save action as action_index (this is used if action is in the nodes)
        state.action_chosen_index = torch.tensor([action], device=state.x.device, dtype=torch.long)
        self.batch_states.append(state)
        if reward is not None:
            self.episode_rewards.append(self.reward)
        self.batch_actions.append(action)
        self.batch_log_probs.append(log_prob)
        self.batch_vals.append(val)

    def compute_episode_rtgs(self):
        """Calculates the cumulative discounted return for the episode"""
        discounted_rewards = []

        for t in range(len(self.episode_rewards)):
            rtg_t = 0
            for pw, r in enumerate(self.episode_rewards[t:]):
                # normalize rewards by a constant number
                rtg_t = rtg_t + self.config['discount'] ** pw * (r / self.reward_normalizer)
            discounted_rewards.append(rtg_t)
        return discounted_rewards

    def normalize_batch_advantage(self, batch_advantage_tensor):
        normalized_batch_advantage = (batch_advantage_tensor - batch_advantage_tensor.mean()) \
                                     / (batch_advantage_tensor.std() + 1e-9)
        return normalized_batch_advantage.to(dtype=torch.float32)

    def learn(self):
        """Runs a learning iteration for the policy"""
        self.old_policy.load_state_dict(self.policy.state_dict())
        start_time = time.time()
        total_loss = 0
        for i in range(self.n_ppo_updates):
            self.optimizer.zero_grad()
            total_loss, chosen_logprobs_train, mean_kl = self.compute_loss()
            total_loss.backward()
            self.optimizer.step()
            if mean_kl >= 1.5 * self.target_kl:
                print(f"Training: early stopping in PPO pass {i}. Reached approx. mean KL: {mean_kl}")
                break
        print(f'Total time for all PPO updates: {time.time() - start_time}s')
        # self.lr_scheduler.step(sliding_average_total_reward)
        # add things to tb and evaluate if needed -
        if (self.episode_number + 1) % 40 == 0:
            print(
                f'Finished episode {self.episode_number}, total loss:{total_loss:.2f},'
                f' ' f'total reward:{sum(self.episode_rewards)}')
            # log the network params histogram, used to understand if network is still learning
            for name, param in self.policy.named_parameters():
                self.writer.add_histogram('parameters/{}'.format(name), param.data, self.episode_number)
                self.writer.add_scalar(f'parameters/min-{name}', torch.min(param.grad), self.episode_number)
                self.writer.add_scalar(f'parameters/max-{name}', torch.max(param.grad), self.episode_number)
        self.writer.add_scalar('Train/Reward', sum(self.episode_rewards), self.episode_number)
        self.writer.add_scalar('Train/lr', self.optimizer.state_dict()['param_groups'][0]['lr'], self.episode_number)
        self.writer.add_scalar('Train/AverageLogProb_data_collection', torch.mean(torch.tensor(self.batch_log_probs)),
                               self.episode_number)
        self.writer.add_scalar('Train/AverageLogProb_train', torch.mean(torch.tensor(chosen_logprobs_train)).item(),
                               self.episode_number)
        self.writer.add_scalar('Train/MinRTGs', torch.min(torch.tensor(self.batch_rtgs)), self.episode_number)
        self.writer.add_scalar('Train/MaxRTGs', torch.max(torch.tensor(self.batch_rtgs)), self.episode_number)
        if torch.cuda.is_available():
            # log memory to see cuda information:
            mem_allocated = torch.cuda.memory_allocated(self.device)*1e-9
            mem_reserved = torch.cuda.memory_reserved(self.device)*1e-9
            self.writer.add_scalar('Memory/cuda_reserved', mem_reserved, self.episode_number)
            self.writer.add_scalar('Memory/cuda_allocated', mem_allocated, self.episode_number)

        self.reset_batches()
        return total_loss

    def reset_batches(self):
        self.batch_rtgs = []
        self.batch_actions = []
        self.batch_states = []
        self.batch_log_probs = []
        self.batch_advantage = []
        self.batch_vals = []
        self.episode_number_in_batch = 0

    def compute_loss(self):
        """
        Calculates the loss for the current batch
        :return: (total loss, chosen log probabilities, mean approximate kl divergence

        """
        original_logprobs = torch.tensor(self.batch_log_probs).to(device="cpu").view(-1, 1)
        # get the chosen logprobs, probs and state values from the policy neural network
        policy_results = self.policy.compute_probs_and_state_values(self.batch_states, self.batch_actions, self.device)
        chosen_logprob, batch_probabilities, batch_state_values = policy_results
        # Policy loss
        batch_rtgs_tensor = torch.tensor(self.batch_rtgs, device="cpu", dtype=torch.float32).view(-1, 1)
        batch_advantage_tensor = self.normalize_batch_advantage(batch_rtgs_tensor - batch_state_values.detach())
        ratio = torch.exp(chosen_logprob - original_logprobs)

        base_policy_loss = ratio * batch_advantage_tensor
        clamped_policy_loss = torch.clamp(ratio, 1 - self.eps_clip, 1 + self.eps_clip) * batch_advantage_tensor
        policy_loss = -torch.min(base_policy_loss, clamped_policy_loss).mean()

        # normalized_batch_advantage = self.normalize_batch_advantage(batch_advantage_tensor)
        # Entropy loss
        entropy_loss = -batch_probabilities.mean() * self.config['entropy_coeff']
        # Value loss
        value_loss = (F.mse_loss(batch_state_values, batch_rtgs_tensor) * self.config['value_coeff'])
        total_loss = policy_loss + entropy_loss + value_loss
        mean_kl = (original_logprobs - chosen_logprob).mean().detach().cpu().item()

        self.writer.add_scalar('Train/Loss/value', value_loss.item(), self.episode_number)
        self.writer.add_scalar('Train/Loss/policy', policy_loss.mean().item(), self.episode_number)
        self.writer.add_scalar('Train/Loss/entropy', entropy_loss.item(), self.episode_number)
        self.writer.add_scalar('Train/mean_kl', mean_kl, self.episode_number)
        # self.writer.add_scalar('Train/Loss/total', total_loss, self.episode_number)
        return total_loss, chosen_logprob, mean_kl

    def time_to_learn(self):
        """Tells us whether it is time for the algorithm to learn. With REINFORCE we only learn
        at the end of every
        episode so this just returns whether the episode is over"""
        if self.done and self.episode_number_in_batch == self.config['number_of_episodes_in_batch']:
            return True
        else:
            return False

    def evaluate(self):
        seeds = np.random.choice(self.eval_seeds, self.config['num_eval_seeds'], replace=False)
        errors_dict = defaultdict(list)
        rewards_list = []

        for seed in seeds:
            total_rewards = 0
            self.env.seed(seed=seed)
            self.reset_game()
            while not self.done:
                self.episode_step(train=False)
                self.state = self.next_state  # this is to set the state for the next iteration
                self.episode_step_number += 1
                total_rewards += self.reward
            rewards_list.append(total_rewards)
            for baseline, values_dict in self.baseline_eval_values.items():
                relative_diff = (100 * (total_rewards - values_dict[seed]) / values_dict[seed])
                errors_dict[baseline].append(relative_diff)
        mean_reward = np.mean(rewards_list)
        mean_relative_diff = np.mean(list(errors_dict.values())[0])
        self.writer.add_scalar('Eval/Reward per eval', mean_reward, self.evaluation_episodes)
        self.writer.add_scalar('Eval/Reward per episode', mean_reward, self.episode_number)
        # save the best overall model -
        if len(self.best_overall_models) >= 1:
            print(f"current relative:{mean_relative_diff},"
                   f" max relative:{max(self.best_overall_models, key=lambda x: x[0])[0]}")
        if (len(self.best_overall_models) < 3 or
                mean_relative_diff < max(self.best_overall_models, key=lambda x: x[0])[0]):
            self.save_model()
            self.best_overall_models.append((mean_relative_diff, self.episode_number))

            # remove the lowest-scoring among the top models
            if len(self.best_overall_models) > 3:
                max_score_index = self.best_overall_models.index(max(self.best_overall_models, key=lambda x: x[0]))
                reward_del, episode_number_to_delete = self.best_overall_models.pop(max_score_index)
                self.delete_model(episode_number_to_delete)
            top_models_df = (pd.DataFrame(data=self.best_overall_models, columns=['mean_relative_diff', 'episode_num'])
                             .sort_values(by='mean_relative_diff', ascending=False))
            top_models_df.to_csv(Path(self.config['model_save_dir']) / 'top_models.tsv', sep='\t', index=False)
        # print(f'Done evaluating on {len(seeds)}, resulting relative diffs: {errors_dict}')
        for baseline, relative_diffs in errors_dict.items():
            mean_relative_diff = np.mean(relative_diffs)
            print(f'Eval/Relative diff - {baseline} : {mean_relative_diff}')
            self.writer.add_scalar(f'Eval/Relative diff per eval- {baseline}', mean_relative_diff,
                                   self.evaluation_episodes)
            self.writer.add_scalar(f'Eval/Relative diff per episode - {baseline}', mean_relative_diff,
                                   self.episode_number)
        self.evaluation_episodes += 1

    def save_model(self):
        # print(f"saving model, episode {self.episode_number}")
        dir_name = self.config['model_save_dir']
        file_name = f'model_ep_{self.episode_number}'
        torch.save(self.policy.state_dict(), Path(dir_name) / file_name)
        conf_file = Path(dir_name) / 'agent_params.json'
        with conf_file.open(mode='w') as f:
            json.dump(obj=self.config, fp=f, indent=2)

    def delete_model(self, episode_num):
        dir_name = self.config['model_save_dir']
        file_name = f'model_ep_{episode_num}'
        file_path = Path(dir_name) / file_name
        if file_path.exists():
            file_path.unlink()
        # os.remove(os.path.join(dir_name, file_name))

    def get_checkpoint(self):
        checkpoint = {
            'episode_number': self.episode_number,
            'old_policy': self.old_policy.state_dict(),
            'policy': self.policy.state_dict(),
            'config': self.config,
            'optimizer': self.optimizer.state_dict(),
            'batch_rtgs': self.batch_rtgs,
            'batch_states': self.batch_states,
            'batch_logprobs': self.batch_log_probs,
            'batch_actions': self.batch_actions,
            'eval_seeds': self.eval_seeds,
            'baseline_eval_values': self.baseline_eval_values
        }
        return checkpoint

    def save_checkpoint(self, filename='./checkpoint.pth.tar'):
        checkpoint = self.get_checkpoint()
        checkpoint_path = Path(self.config['model_save_dir']) / filename

        if checkpoint_path.exists():
            checkpoint_path.unlink()
        torch.save(checkpoint, checkpoint_path)

    @staticmethod
    def load_checkpoint(checkpoint_path: str):
        """
        Loads the agent with the checkpoint information
        Note: the environment is set to None
        :param checkpoint_path:
        :return:
        """
        if torch.cuda.is_available():
            checkpoint = torch.load(checkpoint_path)
        else:
            checkpoint = torch.load(checkpoint_path, map_location=torch.device('cpu'))
        model: PolicyGNN = PolicyGNN(cfg=checkpoint['config']['model_config'], model_name='ppo_policy_model')
        model.load_state_dict(checkpoint['policy'])
        old_model = deepcopy(model)
        old_model.load_state_dict(checkpoint['old_policy'])
        agent = PPOAgent(None, checkpoint['config'], model, checkpoint['eval_seeds'],
                         checkpoint['baseline_eval_values'])
        agent.old_policy = old_model
        agent.optimizer.load_state_dict(checkpoint['optimizer'])
        agent.episode_number = checkpoint['episode_number']
        agent.batch_states = checkpoint['batch_states']
        agent.batch_rtgs = checkpoint['batch_rtgs']
        agent.batch_actions = checkpoint['batch_actions']
        agent.batch_log_probs = checkpoint['batch_logprobs']
        return agent


import cv2
import sys
from os import listdir

#Get classifier to detect faces\
face_classifier = cv2.CascadeClassifier('Haarcascades/haarcascade_frontalface_default.xml')
#Get image names
data_path = 'D:/Image Processing Tutorial/Practices/amsrf/face_database/'

#LBPH Face recognizer
model = cv2.face.LBPHFaceRecognizer_create()
model.read("trained_model.yml")

#Eigen face recognizer
# model = cv2.face.EigenFaceRecognizer_create()
# model.read("trained_data_eigen.yml")

#Fisher face recognizer
# model = cv2.face.FisherFaceRecognizer_create()
# model.read("trained_data_fisher.yml")

noFaceMsg = ""

def face_detector(img):
    global noFaceMsg
    # Convert image to grayscale
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_classifier.detectMultiScale(
        gray,
        scaleFactor=1.3,
        minNeighbors=8,
        minSize=(30, 30)
    )

    if faces is ():
        noFaceMsg = "Couldn't detect any faces"
        return img, []

    for (x,y,w,h) in faces:
        cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,255),2)
        roi = gray[y:y+h, x:x+w]
        roi = cv2.resize(roi, (300, 300))

    return img, roi

def getNames(folder_path):
    seen = set()
    for files in listdir(folder_path):
        temp = files.split("_")
        #print(temp)
        if temp[0] not in seen:
            #Labels.append(temp[1].split(".")[0])
            Labels.append(temp[0])

            IDs.append(temp[0])
            seen.add(temp[0])

def removeDuplicates(values):
    output = []
    seen = set()
    for value in values:
        if value not in seen:
            output.append(value)
            seen.add(value)
    return output

def recognizePerson(grayFace):
    # Pass face to prediction model
    # "results" comprises of a tuple containing the label and the confidence value
    results = model.predict(grayFace)

    #print("line68")
    #print(""+str(results[1]))
    #print("line70")
    #print("{}, {}".format(results[0], results[1]))
    if results[1] < 500:
        confidence = int( 100 * (1 - (results[1])/500) )
        #print("cf = ".format(confidence))
        #print(format(results[0])+"test")
        #print("line76")
        recognizedName = ""
        for i, ids in enumerate(IDs):
            #print(str(results[0])+", "+ids)
            if int(results[0]) == int(ids):
                recognizedName = Labels[i]
                #print(recognizedName)
                break
            else:
                recognizedName = "Unknown"
        #print("line86")
        #print("{}, {}".format(results[0], results[1]))
        return results[0], confidence
    else:
        return results[0],results[1]

if __name__ == '__main__':

    Labels = []
    IDs = []

    getNames(data_path)
    # Labels = removeDuplicates(Labels)
    # IDs = removeDuplicates(IDs)
    # print(Labels)
    # print(IDs)

    # Open Webcam
    cap = cv2.VideoCapture(0 + cv2.CAP_DSHOW)

    while True:

        ret, frame = cap.read()

        if not ret:
            continue

        image, face = face_detector(frame)

        if face == []:
            cv2.putText(image, noFaceMsg, (100, 50), cv2.FONT_HERSHEY_COMPLEX, 1, (0,255,0), 2)
            cv2.imshow('Face Recognition', image )
            #continue
        else:
            try:

                recognizedName, confidence = recognizePerson(face)
                #print("line122")
                display_string = format(confidence) + '% Confident'
                cv2.putText(image, display_string, (50, 50), cv2.FONT_HERSHEY_COMPLEX, 1, (255,120,150), 2)
                #print("Line125")
                if confidence > 85:
                    #print("Line127")
                    cv2.putText(image, "Recognized {0}".format(recognizedName), (50, 450), cv2.FONT_HERSHEY_COMPLEX, 1, (0,255,0), 2)
                    #print("Line129")
                    cv2.imshow('Face Recognition', image )
                else:
                    cv2.putText(image, "Unknown person detected", (50, 450), cv2.FONT_HERSHEY_COMPLEX, 1, (0,0,255), 2)
                    cv2.imshow('Face Recognition', image )

            except:
                #cv2.putText(image, "No Face Found", (220, 120) , cv2.FONT_HERSHEY_COMPLEX, 1, (0,0,255), 2)
                #cv2.putText(image, "Couldn't Recognize", (250, 450), cv2.FONT_HERSHEY_COMPLEX, 1, (0,0,255), 2)
                #cv2.imshow('Face Recognition', image )
                print("Unexpected error: ", sys.exc_info()[0])
                #raise
                break

        if cv2.waitKey(1) == 13: #13 is the Enter Key
            break

    cap.release()
    cv2.destroyAllWindows()


# Python 2 and 3 compatability
from __future__ import division, unicode_literals, print_function

# Common Imports
import os
import numpy as np

# ML Imports
from sklearn.svm import SVC
from sklearn.base import BaseEstimator
from sklearn import datasets

# Graph Imports
import matplotlib.pyplot as plt
plt.rcParams['axes.labelsize'] = 14
plt.rcParams['xtick.labelsize'] = 12
plt.rcParams['ytick.labelsize'] = 12

# Declare Functions
def save_fig(fig_id, tight_layout=True):
    if not os.path.exists('images'):
        os.makedirs('images')
    path = os.path.join(PROJECT_ROOT_DIR, 'images', fig_id + '.png')
    print("Saving figure", fig_id)
    if tight_layout:
        plt.tight_layout()
    plt.savefig(path, format="png", dpi=300)

def plot_svc_decision_boundary(svm_clf, xmin, xmax):
    w = svm_clf.coef_[0]
    b = svm_clf.intercept_[0]

    # w0*x0 + w1*x1 + b = 0
    # x1 = -w0/w1 * x0 - b/w1
    x0 = np.linspace(xmin, xmax, 200)
    decision_boundary = -w[0] / w[1] * x0 - b / w[1]

    margin = 1 / w[1]
    gutter_up = decision_boundary + margin
    gutter_down = decision_boundary - margin

    svs = svm_clf.support_vectors_
    plt.scatter(svs[:, 0], svs[:, 1], s=180, facecolors='#FFAAAA')
    plt.plot(x0, decision_boundary, "k-", linewidth=-2)
    plt.plot(x0, gutter_up, "k--", linewidth=2)
    plt.plot(x0, gutter_down, "k--", linewidth=2)

# Directory settings
PROJECT_ROOT_DIR = '.'

# Declare classes
class MyLinearSVC(BaseEstimator):
    def __init__(self,
                 C=1,
                 eta0=1,
                 eta_d=10000,
                 n_epochs=1000,
                 random_state=None):
        self.C = C
        self.eta0 = eta0
        self.n_epochs = n_epochs
        self.random_state = random_state
        self.eta_d = eta_d

    def eta(self, epoch):
        return self.eta0 / (epoch + self.eta_d)

    def fit(self, X, y):
        # Random Init
        if self.random_state:
            np.random.seed(self.random_state)
        w = np.random.randn(X.shape[1], 1)  # n feat weights
        b = 0

        # m = len(X)
        t = y * 2 - 1  # -1 if t==0, +1 if t ==1
        X_t = X * t
        self.Js = []

        # Training
        for epoch in range(self.n_epochs):
            support_vectors_idx = (X_t.dot(w) + t * b < 1).ravel()
            X_t_sv = X_t[support_vectors_idx]
            t_sv = t[support_vectors_idx]

            J = 1 / 2 * np.sum(w * w) + self.C * (
                np.sum(1 - X_t_sv.dot(w)) - b * np.sum(t_sv))
            self.Js.append(J)

            w_gradient_vector = w - self.C * np.sum(
                X_t_sv, axis=0).reshape(-1, 1)
            b_derivative = -C * np.sum(t_sv)

            w = w - self.eta(epoch) * w_gradient_vector
            b = b - self.eta(epoch) * b_derivative

        self.intercept_ = np.array([b])
        self.coef_ = np.array([w])
        support_vectors_idx = (X_t.dot(w) + t * b < 1).ravel()
        self.support_vectors_ = X[support_vectors_idx]
        return self

    def decision_function(self, X):
        return X.dot(self.coef_[0]) + self.intercept_[0]

    def predict(self, X):
        return (self.decision_function(X) >= 0).astype(np.float64)

# Create Datasets
iris = datasets.load_iris()
X = iris["data"][:, (2, 3)]  # (petal lenght.cm, petal width.cm)
y = (iris["target"] == 2).astype(np.float64).reshape(-1, 1)  # Iris-Verginica

# Fit model
C = 2
svm_clf = MyLinearSVC(
    C=C, eta0=10, eta_d=1000, n_epochs=60000, random_state=42)
svm_clf.fit(X, y)
# svm_clf.predict(np.array([[5, 2], 4, 1]))

# Plot learning curve
plt.plot(range(svm_clf.n_epochs), svm_clf.Js)
plt.axis([0, svm_clf.n_epochs, 0, 100])
save_fig('l-SVC_BGD_learning_curve')
plt.show()

# Intercept and Coefficients
print(svm_clf.intercept_, svm_clf.coef_)

# Compare to sk_base
svm_clf2 = SVC(kernel="linear", C=C)
svm_clf2.fit(X, y.ravel())
print(svm_clf2.intercept_, svm_clf2.coef_)

# Plot SVMs
yr = y.ravel()
plt.figure(figsize=(12, 3.2))
plt.subplot(121)
plt.plot(X[:, 0][yr == 1], X[:, 1][yr == 1], "g^", label="Iris-Virginica")
plt.plot(
    X[:, 0][yr == 0], X[:, 1][yr == 0], "bs", label="Not Iris-Virginica")
plot_svc_decision_boundary(svm_clf, 4, 6)
plt.xlabel("Petal length", fontsize=14)
plt.ylabel("Petal width", fontsize=14)
plt.title("MyLinearSVC", fontsize=14)
plt.axis([4, 6, 0.8, 2.8])

plt.subplot(122)
plt.plot(X[:, 0][yr == 1], X[:, 1][yr == 1], "g^")
plt.plot(X[:, 0][yr == 0], X[:, 1][yr == 0], "bs")
plot_svc_decision_boundary(svm_clf2, 4, 6)
plt.xlabel("Petal length", fontsize=14)
plt.title("SVC", fontsize=14)
plt.axis([4, 6, 0.8, 2.8])
save_fig("my_svc_comparison")
plt.show()


from django.shortcuts import render, redirect
from django.views.generic import View
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from . import config
from django.db.models import Q
from django.core.exceptions import ObjectDoesNotExist
from textblob import TextBlob

import os

import sys
sys.path.insert(1, "/home/Aiasem/mysite/chat_bot/")
import find_sim

from .models import ArtGallery, InfoGuide

import telebot
from telebot import types

image_names_kz = []
image_names_ru = []
image_names_eng = []
image_codes = []

def index(request):
    pass
    global image_names_kz, image_names_ru, image_names_eng, image_codes

    # find_sim.dataset_process()
    return render(request, "chat_bot/index.html", {'test': image_names_eng})

language_used = 'en'
result = ""
TOKEN = config.token
bot = telebot.TeleBot(TOKEN, threaded=False)

for q in ArtGallery.objects.all():
    image_names_kz.append(q.image_title_kz)
    image_names_ru.append(q.image_title_ru)
    image_names_eng.append(q.image_title_eng)
    image_codes.append(str(q.unique_code))
# так как вебхук уже подключен будем ждать новую информацию в TelegramBotView

class TelegramBotView(View):
    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super(TelegramBotView, self).dispatch(request, *args, **kwargs)

    def post(self, request):
        bot.process_new_updates([telebot.types.Update.de_json(request.body.decode('utf-8'))]) # тут мы уже получили новую инфу, эти изменения обновляем в боте
        return JsonResponse({'code': 'accepted'})

# КОД БОТА

@bot.message_handler(commands=['start'])
def handle_start(message):
    user_id = message.from_user.id
    startObj = InfoGuide.objects.get(command_title = message.text.replace('/',""))
    text_eng = startObj.eng
    probel = '_'* 45
    text_ru = startObj.ru
    text_kz = startObj.kz
    text_fin = text_eng + "\n" + probel + "\n" + text_ru + "\n" + probel + "\n" +text_kz

    bot.send_message(user_id, text_fin )

    # bot.send_message(user_id, "HIIIII" )

@bot.message_handler(commands=['help','imagename', 'camera', 'uniquecode'])
def get_help_info(message):
    user_id = message.from_user.id
    text = message.text.replace('/',"")
    bot.send_message(user_id, command_text(text))

@bot.message_handler(commands=['language'])
def handle_language(message):

    user_id = message.from_user.id

    eng_button = types.InlineKeyboardButton(text="English", callback_data="en")
    ru_button = types.InlineKeyboardButton(text="Русский", callback_data="ru")
    kz_button = types.InlineKeyboardButton(text="Қазақша", callback_data="kk")
    keyboard = types.InlineKeyboardMarkup()
    keyboard.add(eng_button)
    keyboard.add(ru_button)
    keyboard.add(kz_button)

    bot.send_message(user_id, "Please choose the language:", reply_markup=keyboard)

@bot.message_handler(content_types=['photo']) # если отправили фото
def handle_image(message):

    global result
    user_id = message.from_user.id

    try:
        file_info = bot.get_file(message.photo[len(message.photo)-1].file_id)
        downloaded_file = bot.download_file(file_info.file_path)
        src = find_sim.query_image
        bot.send_message(user_id, command_text("wait"))
        with open(src, 'wb') as new_file:
            new_file.write(downloaded_file)

        base = os.path.basename(find_sim.calculation())

        # bot.send_message(user_id, base)
        if(base == ""):
            text = command_text("error")
            bot.send_message(user_id, text)
        else:
            result = os.path.splitext(base)[0]
            result = result.replace('_'," ")
            result = ArtGallery.objects.get(image_title_eng = result)

            text = command_text("info")
            bot.send_message(user_id, text , reply_markup = gen_markup())

    except Exception:
        bot.send_message(user_id, command_text("error"))

@bot.message_handler(content_types=['voice'])
def handle_voice(message):
    global language_used, result
    global image_names_kz, image_names_ru, image_names_eng, image_codes

    error = False
    user_id = message.from_user.id

    try:
        file_info = bot.get_file(message.voice.file_id)
        downloaded_file = bot.download_file(file_info.file_path)
        src = find_sim.query_voice
        with open(src, 'wb') as new_file:
            new_file.write(downloaded_file)
        res = find_sim.voice_proccess(language_used)
    except Exception:
        bot.send_message(user_id, command_text("error"))
    try:
        res = res.replace(' ',"").lower()

        if res.isdigit():
            result = ArtGallery.objects.get(unique_code = res)
            text = command_text("info")
            bot.send_message(user_id, text , reply_markup = gen_markup())
            error = False
        else:
            for (b, c, d) in zip(image_names_eng, image_names_kz, image_names_ru):
                x = b.replace(" ", "").lower()
                y = c.replace(" ", "").lower()
                z = d.replace(" ", "").lower()

                if( (x in res) or (y in res) or (z in res) ):
                    result = ArtGallery.objects.get(
                        Q(image_title_eng__contains = b)  |
                        Q(image_title_kz__contains = c) |
                        Q(image_title_ru__contains = d)
                    )
                    text = command_text("info")
                    bot.send_message(user_id, text , reply_markup = gen_markup())
    except Exception:
        bot.send_message(user_id, command_text("error"))

@bot.message_handler(content_types=['text']) # обычные сообщения
def handle_text(message):
    global language_used, result
    global image_names_kz, image_names_ru, image_names_eng, image_codes
    user_id = message.from_user.id
    #lang = TextBlob(text)
    try:
        res = message.text.replace(' ',"").lower()
        if res.isdigit():
            result = ArtGallery.objects.get(unique_code = res)
            text = command_text("info")
            bot.send_message(user_id, text , reply_markup = gen_markup())
            error = False
        else:
            for (b, c, d) in zip(image_names_eng, image_names_kz, image_names_ru):
                x = b.replace(" ", "").lower()
                y = c.replace(" ", "").lower()
                z = d.replace(" ", "").lower()

                if( (x in res) or (y in res) or (z in res) ):
                    result = ArtGallery.objects.get(
                        Q(image_title_eng__contains = b)  |
                        Q(image_title_kz__contains = c) |
                        Q(image_title_ru__contains = d)
                    )
                    text = command_text("info")
                    bot.send_message(user_id, text , reply_markup = gen_markup())
        #             error = False
        #         else:
        #             error = True
        # if error:
        #     bot.send_message(user_id, command_text("error"))

    except Exception:
        bot.send_message(user_id, command_text("error"))

def gen_markup():

    keyboard = types.InlineKeyboardMarkup()

    audio_button = types.InlineKeyboardButton(text="Audio", callback_data="audio")
    text_button = types.InlineKeyboardButton(text="Text", callback_data="text")

    keyboard.row(audio_button, text_button)

    return keyboard

def command_text(text):
    global language_used
    text = InfoGuide.objects.get(command_title = text)
    if(language_used == "en"):
        text = text.eng
    elif(language_used == "kk"):
        text = text.kz
    elif(language_used == "ru"):
        text = text.ru
    return text

def audio_lang():
    global result,language_used
    if(language_used == "kk"):
        data = result.audio_kz
    elif(language_used == "ru"):
        data = result.audio_ru
    elif(language_used == "en"):
        data = result.audio_eng
    return data

def text_lang():
    global result, language_used
    if(language_used == "kk"):
        data = result.text_kz
    elif(language_used == "ru"):
        data = result.text_ru
    elif(language_used == "en"):
        data = result.text_eng
    return data

@bot.callback_query_handler(func=lambda call: True)
def query_handler(call):
    global result
    global language_used

    user_id = call.from_user.id

    if call.data == 'en':
        language_used = "en"
        # command_text("language")
        bot.answer_callback_query(call.id,"OK")

    if call.data == "ru":
        language_used = "ru"
        bot.answer_callback_query(call.id,"OK")

    if call.data == "kk":
        language_used = "kk"
        bot.answer_callback_query(call.id,"OK")

    if call.data == "audio":
        bot.send_voice(user_id, audio_lang())
        result = ""

    if call.data == "text":
        bot.send_message(user_id, text_lang())
        result = ""


# coding=utf-8
import logging

import os
import telebot

from kzn_bot.sites import (
    Kzn, presets, UserSearchData, Filters, user_filters_cache)

logger = logging.getLogger(__name__)

BOT = GOD = None

MARKUP_HIDE = telebot.types.ReplyKeyboardRemove()

def initialize_bot(token_path):
    if not os.path.exists(token_path):
        raise ValueError('No token file found! get token from bot father')

    with open(token_path) as fh:
        bot_father_token = fh.readline().strip()
        god = fh.readline().strip()

    if BOT:
        try:
            BOT.stop_polling()
        except Exception as e:
            logger.exception(e)

    bot = telebot.TeleBot(bot_father_token, threaded=False)

    @bot.message_handler(commands=Filters.keys)
    def number_command(message):
        @user_filters_cache.filter_saver
        def handler(message, key):
            user_id = message.chat.id
            value = message.text[:Filters.max_value_len]
            log_msg = u'New key filter for user {u}: {k}={v}'.format(
                k=key, v=value, u=message.chat.first_name)
            logger.info(log_msg)

            filters = user_filters_cache[user_id]
            if filters:
                cached_user_filter = user_filters_cache.get_last(user_id)
                cached_user_filter.update(**{key: value})
            else:
                user_filters_cache.add(user_id, **{key: value})

            if GOD:
                BOT.send_message(GOD, log_msg)
                BOT.send_message(GOD, '; '.join(
                    [str(k) for k in user_filters_cache._data.keys()]))

        key = message.text.strip(u'/')
        BOT.send_message(message.chat.id,
                         Filters.description.get(key,
                                                 u'Не распознанная команда'))

        callback_fn = lambda msg: handler(msg, key)
        BOT.register_next_step_handler(message, callback_fn)

    @bot.message_handler(commands=['start'])
    @user_filters_cache.filter_saver
    def start_command(message):
        user_filters_cache.clear(message.chat.id)
        logger.debug(
            'New user %d : %s' % (message.chat.id, message.chat.first_name))
        BOT.send_message(message.chat.id,
                         u'Сейчас пришлю последние 10 документов')
        if GOD:
            BOT.send_message(
                GOD,
                u'New user {0}, {1}'.format(message.chat.first_name,
                                            unicode(message.chat.id)))

    @bot.message_handler(commands=['clear'])
    @user_filters_cache.filter_saver
    def clear_command(message):
        e = "Нет фильтрации"
        user_filters_cache.clear(message.chat.id)
        logger.debug('User cleared filters: %d' % message.chat.id)
        BOT.send_message(message.chat.id,
                         str(user_filters_cache[message.chat.id]) or e)

    @bot.message_handler(commands=['list'])
    def list_command(message):
        e = "Нет фильтрации"
        BOT.send_message(message.chat.id,
                         str(user_filters_cache[message.chat.id]) or e)
        logger.debug(str(message.chat.id) + ': ' +
                     str(user_filters_cache[message.chat.id]))

    @bot.message_handler(commands=['add'])
    @user_filters_cache.filter_saver
    def add_command(message):
        if len(user_filters_cache[message.chat.id]) >= UserSearchData.max_filters:
            msg = (u'Достигнуто максимально допустимое количество '
                   u'фильтров: %d' % UserSearchData.max_filters)
        else:
            user_filters_cache.add(message.chat.id)
            msg = str(user_filters_cache[message.chat.id])
        logger.debug(msg)
        BOT.send_message(message.chat.id, msg)

    @bot.message_handler(commands=['presets'])
    def presets_command(message):
        @user_filters_cache.filter_saver
        def callback(msg):
            filter_name = msg.text
            if filter_name in presets:
                user_id = msg.chat.id
                user_filters_cache.add(user_id, **presets[filter_name])

                user_msg = u'Фильтр добавлен: {0}'.format(filter_name)
            else:
                user_msg = u'Неверно задан фильтр: {0}'.format(filter_name)

            markup_hide = MARKUP_HIDE
            BOT.send_message(message.chat.id, user_msg,
                             reply_markup=markup_hide)
            logger.debug(user_msg + u' ' + msg.chat.first_name)
            if GOD:
                BOT.send_message(GOD, user_msg + u' ' + msg.chat.first_name)

        markup = telebot.types.ReplyKeyboardMarkup(one_time_keyboard=True,
                                                   selective=True)
        markup.row(*presets.keys())

        msg = BOT.send_message(message.chat.id, u"Выберите фильтр",
                               reply_markup=markup)

        BOT.register_next_step_handler(msg, callback)

    return bot, god

def emergency_message(message, default=None):
    if not os.path.exists('token'):
        raise ValueError('No token file found! get token from bot father')

    with open('token') as fh:
        bot_father_token = fh.readline().strip()
        god = fh.readline().strip()

    if god:
        msg = message or default or 'Error occurred! Save the bot'
        try:
            bot = telebot.TeleBot(bot_father_token, threaded=False)
            bot.send_message(god, msg)

        except Exception as e:
            logger.critical(e)
    else:
        logger.critical('No god persists')

def send_data(user_id, **kwargs):
    for url, title in Kzn.get_new_doc(user_id, **kwargs):
        msg = title.replace(u'\n', u' ').strip() + u'\n' + url
        try:
            BOT.send_message(user_id, msg)
        except Exception as e:
            logger.critical(e)
            emergency_message(unicode(user_id)+u' '+unicode(e),
                              'unrecognized error')

        else:
            logger.debug('user "%s" got message: %s' % (user_id, msg))


from tkinter import *
import tkinter as tk

import pymysql
from tkinter import messagebox, ttk,Tk,Canvas
from tkinter.messagebox import askquestion

from PIL import ImageTk

class Add_Category:

    def __init__(self):
        self.my_frame = tk.Toplevel()

        self.my_frame.title("Add Login Type")
        width = 400
        height = 200
        screen_width = self.my_frame.winfo_screenwidth()
        screen_height = self.my_frame.winfo_screenheight()
        x = (screen_width / 2) - (width / 2)
        y = (screen_height / 2) - (height / 2)
        self.my_frame.geometry("%dx%d+%d+%d" % (width, height, x, y))

        self.imagelb1=Label(self.my_frame,text="Add Ctegory",font=("georgia",50,"bold"))
        self.lb1 = Label(self.my_frame, text="Category")

        self.ment=StringVar()
        self.t1 = Entry(self.my_frame, width=20,textvariable=self.ment)

        self.t1.focus()

        self.btn1 = Button(self.my_frame, text="Save", padx=10, command=self.savedata, width=30)

        self.lb1.place(x=50, y=70)
        self.t1.place(x=150, y=70)

        self.btn1.place(x=100, y=100)

#btn2.place(x=170, y=500)

        self.myimage1 = PhotoImage(file='C:\\Users\\ashvi\PycharmProjects\\automobile_management\\costum.png')
        self.imagelabel2 = Label(self.my_frame, image=self.myimage1)
        self.imagelabel2.place(x=650,y=190)
        self.imagelb1.place(x=610,y=50)

        self.my_frame.mainloop()
    def savedata(self):

        try:
            myobj=pymysql.connect(host="localhost", user="root",
                              password="", db="automobile")

            with myobj.cursor() as myconn:

                myconn.execute("insert into "
                           "logmain(logintype) "
                           "values(%s)",

                     (self.t1.get()))

                if (self.t1.get() == "") :
                    messagebox.showerror("fill box", "complete the entries", parent=self.my_frame)
                #
                #
                else:

                    myobj.commit()
                    myobj.close()
                    messagebox.showinfo("Success", "Record Saved Successfully",parent=self.my_frame)
        except Exception as ex:
            messagebox.showerror("Error Occured", "Error occured due to " + str(ex),parent=self.my_frame)


"""
Interface to the Syncthing long polling Event API (https://docs.syncthing.net/dev/events.html)

"""

from . import BaseAPI

class Events(BaseAPI):
    prefix = '/rest/'

    def __init__(self, last_seen_id=0, *args, **kwargs):
        # If timeout is provided, use that.  Otherwise the provided value will be used for the
        # long-poll timeout
        if 'timeout' not in kwargs:
            kwargs['timeout'] = None

        super().__init__(*args, **kwargs)

        self._last_seen_id = last_seen_id

    def poll(self, limit=None):
        """
        Poll the Events API for new events.

        If no new events exist the API will block until new events are available.

        :param limit: Maximum number of events to return
        :return: list of events
        """

        params = {'since': self._last_seen_id}

        if limit is not None:
            params['limit'] = limit

        data = self.get('events', params=params)
        if data:
            self._last_seen_id = data[-1]['id']

        return data

    def generate(self):
        """
        Return a generator of events since
        :return:
        """
        while True:
            for event in self.poll():
                yield event

    def __iter__(self):
        return self.generate()


from argparse import ArgumentParser

import torch
from transformers.models.realm.modeling_realm import logger
from transformers.utils import logging

from model import get_searcher_reader_tokenizer_tf

logger.setLevel(logging.INFO)
torch.set_printoptions(precision=8)

def retrieve(args, searcher, tokenizer):
    with torch.no_grad():
        question = args.question
        question_ids = tokenizer([question], return_tensors='pt')

        output = searcher(**question_ids, return_dict=True)

    print(output)

    return output

def read(args, reader, tokenizer, searcher_output):
    with torch.no_grad():
        text = []
        text_pair = []
        for retrieved_block in searcher_output.retrieved_blocks:
            text.append(args.question)
            text_pair.append(retrieved_block.decode())

        concat_inputs = tokenizer(text, text_pair, return_tensors='pt', padding=True, truncation=True)

        output = reader(
            **concat_inputs,
            relevance_score=searcher_output.retrieved_logits,
            return_dict=True,
        )

    answer = tokenizer.decode(concat_inputs.input_ids[output.block_idx][output.start_pos: output.end_pos + 1])

    print(output)

    return output, answer

def main(args):
    searcher, reader, tokenizer = get_searcher_reader_tokenizer_tf(args)

    retriever_output = retrieve(args, searcher, tokenizer)
    reader_output, answer = read(args, reader, tokenizer, retriever_output)

    print(f"Question: {args.question}\nAnswer: {answer}")

if __name__ == "__main__":
    parser = ArgumentParser()

    # Question
    parser.add_argument("--question", type=str, required=True)
    # Retriever
    parser.add_argument("--block_emb_path", type=str, default=r"./data/cc_news_pretrained/embedder/encoded/encoded.ckpt")
    parser.add_argument("--block_records_path", type=str, default=r"./data/enwiki-20181220/blocks.tfr")
    parser.add_argument("--retriever_path", type=str, default=r"./data/orqa_nq_model_from_realm/export/best_default/checkpoint/model.ckpt-300000")
    # Reader
    parser.add_argument("--checkpoint_path", type=str, default=r"./data/orqa_nq_model_from_realm/export/best_default/checkpoint/model.ckpt-300000")

    args = parser.parse_args()

    main(args)


from models.user import User
from models.message import Message
from routes import *
from routes.user import current_user
import json
import redis

main = Blueprint('chat', __name__)

red = redis.Redis(host='localhost', port=6379, db=0)

# 发布聊天广播的 redis 频道
chat_channel = 'chat'

def stream():
    '''
    监听 redis 广播并 sse 到客户端
    '''
    # 对每一个用户 创建一个[发布订阅]对象
    pubsub = red.pubsub()
    # 订阅广播频道
    pubsub.subscribe(chat_channel)
    # 监听订阅的广播
    for message in pubsub.listen():
        print(message)
        if message['type'] == 'message':
            data = message['data'].decode('utf-8')
            # 用 sse 返回给前端
            yield 'data: {}\n\n'.format(data)

@main.route('/subscribe')
def subscribe():
    return Response(stream(), mimetype="text/event-stream")

@main.route('/')
def index_view():
    cu = current_user()
    ms = Message.query.all()
    return render_template('index.html', messages=ms, cu=cu)

def current_time():
    return int(time.time())

@main.route('/chat/add', methods=['POST'])
def chat_add():
    cu = current_user()
    if cu is None:
        abort(401)
    msg = request.get_json()
    msg['user_id'] = cu.id
    m = Message(msg)
    m.save()
    r = {
        'name': m.user.username,
        'avatar': m.user.avatar,
        'content': m.content,
        'channel': m.channel,
        'created_time': m.created_time.strftime("%Y-%m-%d %H:%M"),
    }
    message = json.dumps(r, ensure_ascii=False)
    print('debug', message)
    # 用 redis 发布消息
    red.publish(chat_channel, message)
    return 'OK'


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

"""
文件上传方法中的data参数
------------------------
诸如 :func:`put_object <Bucket.put_object>` 这样的上传接口都会有 `data` 参数用于接收用户数据。`data` 可以是下述类型
    - unicode类型（对于Python3则是str类型）：内部会自动转换为UTF-8的bytes
    - bytes类型：不做任何转换
    - file-like object：对于可以seek和tell的file object，从当前位置读取直到结束。其他类型，请确保当前位置是文件开始。
    - 可迭代类型：对于无法探知长度的数据，要求一定是可迭代的。此时会通过Chunked Encoding传输。

Bucket配置修改方法中的input参数
-----------------------------
诸如 :func:`put_bucket_cors <Bucket.put_bucket_cors>` 这样的Bucket配置修改接口都会有 `input` 参数接收用户提供的配置数据。
`input` 可以是下述类型
  - Bucket配置信息相关的类，如 `BucketCors`
  - unicode类型（对于Python3则是str类型）
  - 经过utf-8编码的bytes类型
  - file-like object
  - 可迭代类型，会通过Chunked Encoding传输
也就是说 `input` 参数可以比 `data` 参数多接受第一种类型的输入。

返回值
------
:class:`Service` 和 :class:`Bucket` 类的大多数方法都是返回 :class:`RequestResult <oss2.models.RequestResult>`
及其子类。`RequestResult` 包含了HTTP响应的状态码、头部以及OSS Request ID，而它的子类则包含用户真正想要的结果。例如，
`ListBucketsResult.buckets` 就是返回的Bucket信息列表；`GetObjectResult` 则是一个file-like object，可以调用 `read()` 来获取响应的
HTTP包体。

异常
----
一般来说Python SDK可能会抛出三种类型的异常，这些异常都继承于 :class:`OssError <oss2.exceptions.OssError>` ：
    - :class:`ClientError <oss2.exceptions.ClientError>` ：由于用户参数错误而引发的异常；
    - :class:`ServerError <oss2.exceptions.ServerError>` 及其子类：OSS服务器返回非成功的状态码，如4xx或5xx；
    - :class:`RequestError <oss2.exceptions.RequestError>` ：底层requests库抛出的异常，如DNS解析错误，超时等；
当然，`Bucket.put_object_from_file` 和 `Bucket.get_object_to_file` 这类函数还会抛出文件相关的异常。

.. _byte_range:

指定下载范围
------------
诸如 :func:`get_object <Bucket.get_object>` 以及 :func:`upload_part_copy <Bucket.upload_part_copy>` 这样的函数，可以接受
`byte_range` 参数，表明读取数据的范围。该参数是一个二元tuple：(start, last)。这些接口会把它转换为Range头部的值，如：
    - byte_range 为 (0, 99) 转换为 'bytes=0-99'，表示读取前100个字节
    - byte_range 为 (None, 99) 转换为 'bytes=-99'，表示读取最后99个字节
    - byte_range 为 (100, None) 转换为 'bytes=100-'，表示读取第101个字节到文件结尾的部分（包含第101个字节）

分页罗列
-------
罗列各种资源的接口，如 :func:`list_buckets <Service.list_buckets>` 、 :func:`list_objects <Bucket.list_objects>` 都支持
分页查询。通过设定分页标记（如：`marker` 、 `key_marker` ）的方式可以指定查询某一页。首次调用将分页标记设为空（缺省值，可以不设），
后续的调用使用返回值中的 `next_marker` 、 `next_key_marker` 等。每次调用后检查返回值中的 `is_truncated` ，其值为 `False` 说明
已经到了最后一页。

.. _progress_callback:

上传下载进度
-----------
上传下载接口，诸如 `get_object` 、 `put_object` 、`resumable_upload`，都支持进度回调函数，可以用它实现进度条等功能。

`progress_callback` 的函数原型如下 ::

    def progress_callback(bytes_consumed, total_bytes):
        '''进度回调函数。

        :param int bytes_consumed: 已经消费的字节数。对于上传，就是已经上传的量；对于下载，就是已经下载的量。
        :param int total_bytes: 总长度。
        '''

其中 `total_bytes` 对于上传和下载有不同的含义：
    - 上传：当输入是bytes或可以seek/tell的文件对象，那么它的值就是总的字节数；否则，其值为None
    - 下载：当返回的HTTP相应中有Content-Length头部，那么它的值就是Content-Length的值；否则，其值为None

.. _unix_time:

Unix Time
---------
OSS Python SDK会把从服务器获得时间戳都转换为自1970年1月1日UTC零点以来的秒数，即Unix Time。
参见 `Unix Time <https://en.wikipedia.org/wiki/Unix_time>`_

OSS中常用的时间格式有
    - HTTP Date格式，形如 `Sat, 05 Dec 2015 11:04:39 GMT` 这样的GMT时间。
      用在If-Modified-Since、Last-Modified这些HTTP请求、响应头里。
    - ISO8601格式，形如 `2015-12-05T00:00:00.000Z`。
      用在生命周期管理配置、列举Bucket结果中的创建时间、列举文件结果中的最后修改时间等处。

`http_date` 函数把Unix Time转换为HTTP Date；而 `http_to_unixtime` 则做相反的转换。如 ::

    >>> import oss2, time
    >>> unix_time = int(time.time())             # 当前UNIX Time，设其职为 1449313829
    >>> date_str = oss2.http_date(unix_time)     # 得到 'Sat, 05 Dec 2015 11:10:29 GMT'
    >>> oss2.http_to_unixtime(date_str)          # 得到 1449313829

.. note::

    生成HTTP协议所需的日期（即HTTP Date）时，请使用 `http_date` ， 不要使用 `strftime` 这样的函数。因为后者是和locale相关的。
    比如，`strftime` 结果中可能会出现中文，而这样的格式，OSS服务器是不能识别的。

`iso8601_to_unixtime` 把ISO8601格式转换为Unix Time；`date_to_iso8601` 和 `iso8601_to_date` 则在ISO8601格式的字符串和
datetime.date之间相互转换。如 ::

    >>> import oss2
    >>> d = oss2.iso8601_to_date('2015-12-05T00:00:00.000Z')  # 得到 datetime.date(2015, 12, 5)
    >>> date_str = oss2.date_to_iso8601(d)                    # 得到 '2015-12-05T00:00:00.000Z'
    >>> oss2.iso8601_to_unixtime(date_str)                    # 得到 1449273600
"""

from . import xml_utils
from . import http
from . import utils
from . import exceptions
from . import defaults

from .models import *
from .compat import urlquote, urlparse, to_unicode, to_string

import time
import shutil
import oss2.utils

class _Base(object):
    def __init__(self, auth, endpoint, is_cname, session, connect_timeout,
                 app_name=''):
        self.auth = auth
        self.endpoint = _normalize_endpoint(endpoint.strip())
        self.session = session or http.Session()
        self.timeout = defaults.get(connect_timeout, defaults.connect_timeout)
        self.app_name = app_name

        self._make_url = _UrlMaker(self.endpoint, is_cname)

    def _do(self, method, bucket_name, key, **kwargs):
        key = to_string(key)
        req = http.Request(method, self._make_url(bucket_name, key),
                           app_name=self.app_name,
                           **kwargs)
        self.auth._sign_request(req, bucket_name, key)

        resp = self.session.do_request(req, timeout=self.timeout)
        if resp.status // 100 != 2:
            raise exceptions.make_exception(resp)

        return resp

    def _parse_result(self, resp, parse_func, klass):
        result = klass(resp)
        parse_func(result, resp.read())
        return result

class Service(_Base):
    """用于Service操作的类，如罗列用户所有的Bucket。

    用法 ::

        >>> import oss2
        >>> auth = oss2.Auth('your-access-key-id', 'your-access-key-secret')
        >>> service = oss2.Service(auth, 'oss-cn-hangzhou.aliyuncs.com')
        >>> service.list_buckets()
        <oss2.models.ListBucketsResult object at 0x0299FAB0>

    :param auth: 包含了用户认证信息的Auth对象
    :type auth: oss2.Auth

    :param str endpoint: 访问域名，如杭州区域的域名为oss-cn-hangzhou.aliyuncs.com

    :param session: 会话。如果是None表示新开会话，非None则复用传入的会话
    :type session: oss2.Session

    :param float connect_timeout: 连接超时时间，以秒为单位。
    :param str app_name: 应用名。该参数不为空，则在User Agent中加入其值。
        注意到，最终这个字符串是要作为HTTP Header的值传输的，所以必须要遵循HTTP标准。
    """
    def __init__(self, auth, endpoint,
                 session=None,
                 connect_timeout=None,
                 app_name=''):
        super(Service, self).__init__(auth, endpoint, False, session, connect_timeout,
                                      app_name=app_name)

    def list_buckets(self, prefix='', marker='', max_keys=100):
        """根据前缀罗列用户的Bucket。

        :param str prefix: 只罗列Bucket名为该前缀的Bucket，空串表示罗列所有的Bucket
        :param str marker: 分页标志。首次调用传空串，后续使用返回值中的next_marker
        :param int max_keys: 每次调用最多返回的Bucket数目

        :return: 罗列的结果
        :rtype: oss2.models.ListBucketsResult
        """
        resp = self._do('GET', '', '',
                        params={'prefix': prefix,
                                'marker': marker,
                                'max-keys': str(max_keys)})
        return self._parse_result(resp, xml_utils.parse_list_buckets, ListBucketsResult)

class Bucket(_Base):
    """用于Bucket和Object操作的类，诸如创建、删除Bucket，上传、下载Object等。

    用法（假设Bucket属于杭州区域） ::

        >>> import oss2
        >>> auth = oss2.Auth('your-access-key-id', 'your-access-key-secret')
        >>> bucket = oss2.Bucket(auth, 'http://oss-cn-hangzhou.aliyuncs.com', 'your-bucket')
        >>> bucket.put_object('readme.txt', 'content of the object')
        <oss2.models.PutObjectResult object at 0x029B9930>

    :param auth: 包含了用户认证信息的Auth对象
    :type auth: oss2.Auth

    :param str endpoint: 访问域名或者CNAME
    :param str bucket_name: Bucket名
    :param bool is_cname: 如果endpoint是CNAME则设为True；反之，则为False。

    :param session: 会话。如果是None表示新开会话，非None则复用传入的会话
    :type session: oss2.Session

    :param float connect_timeout: 连接超时时间，以秒为单位。

    :param str app_name: 应用名。该参数不为空，则在User Agent中加入其值。
        注意到，最终这个字符串是要作为HTTP Header的值传输的，所以必须要遵循HTTP标准。
    """

    ACL = 'acl'
    CORS = 'cors'
    LIFECYCLE = 'lifecycle'
    LOCATION = 'location'
    LOGGING = 'logging'
    REFERER = 'referer'
    WEBSITE = 'website'

    def __init__(self, auth, endpoint, bucket_name,
                 is_cname=False,
                 session=None,
                 connect_timeout=None,
                 app_name=''):
        super(Bucket, self).__init__(auth, endpoint, is_cname, session, connect_timeout,
                                     app_name=app_name)
        self.bucket_name = bucket_name.strip()

    def sign_url(self, method, key, expires, headers=None, params=None):
        """生成签名URL。

        常见的用法是生成加签的URL以供授信用户下载，如为log.jpg生成一个5分钟后过期的下载链接::

            >>> bucket.sign_url('GET', 'log.jpg', 5 * 60)
            'http://your-bucket.oss-cn-hangzhou.aliyuncs.com/logo.jpg?OSSAccessKeyId=YourAccessKeyId\&Expires=1447178011&Signature=UJfeJgvcypWq6Q%2Bm3IJcSHbvSak%3D'

        :param method: HTTP方法，如'GET'、'PUT'、'DELETE'等
        :type method: str
        :param key: 文件名
        :param expires: 过期时间（单位：秒），链接在当前时间再过expires秒后过期

        :param headers: 需要签名的HTTP头部，如名称以x-oss-meta-开头的头部（作为用户自定义元数据）、
            Content-Type头部等。对于下载，不需要填。
        :type headers: 可以是dict，建议是oss2.CaseInsensitiveDict

        :param params: 需要签名的HTTP查询参数

        :return: 签名URL。
        """
        key = to_string(key)
        req = http.Request(method, self._make_url(self.bucket_name, key),
                           headers=headers,
                           params=params)
        return self.auth._sign_url(req, self.bucket_name, key, expires)

    def list_objects(self, prefix='', delimiter='', marker='', max_keys=100):
        """根据前缀罗列Bucket里的文件。

        :param str prefix: 只罗列文件名为该前缀的文件
        :param str delimiter: 分隔符。可以用来模拟目录
        :param str marker: 分页标志。首次调用传空串，后续使用返回值的next_marker
        :param int max_keys: 最多返回文件的个数，文件和目录的和不能超过该值

        :return: :class:`ListObjectsResult <oss2.models.ListObjectsResult>`
        """
        resp = self.__do_object('GET', '',
                                params={'prefix': prefix,
                                        'delimiter': delimiter,
                                        'marker': marker,
                                        'max-keys': str(max_keys),
                                        'encoding-type': 'url'})
        return self._parse_result(resp, xml_utils.parse_list_objects, ListObjectsResult)

    def put_object(self, key, data,
                   headers=None,
                   progress_callback=None):
        """上传一个普通文件。

        用法 ::
            >>> bucket.put_object('readme.txt', 'content of readme.txt')
            >>> with open(u'local_file.txt', 'rb') as f:
            >>>     bucket.put_object('remote_file.txt', f)

        :param key: 上传到OSS的文件名

        :param data: 待上传的内容。
        :type data: bytes，str或file-like object

        :param headers: 用户指定的HTTP头部。可以指定Content-Type、Content-MD5、x-oss-meta-开头的头部等
        :type headers: 可以是dict，建议是oss2.CaseInsensitiveDict

        :param progress_callback: 用户指定的进度回调函数。可以用来实现进度条等功能。参考 :ref:`progress_callback` 。

        :return: :class:`PutObjectResult <oss2.models.PutObjectResult>`
        """
        headers = utils.set_content_type(http.CaseInsensitiveDict(headers), key)

        if progress_callback:
            data = utils.make_progress_adapter(data, progress_callback)

        resp = self.__do_object('PUT', key, data=data, headers=headers)
        return PutObjectResult(resp)

    def put_object_from_file(self, key, filename,
                             headers=None,
                             progress_callback=None):
        """上传一个本地文件到OSS的普通文件。

        :param str key: 上传到OSS的文件名
        :param str filename: 本地文件名，需要有可读权限

        :param headers: 用户指定的HTTP头部。可以指定Content-Type、Content-MD5、x-oss-meta-开头的头部等
        :type headers: 可以是dict，建议是oss2.CaseInsensitiveDict

        :param progress_callback: 用户指定的进度回调函数。参考 :ref:`progress_callback`

        :return: :class:`PutObjectResult <oss2.models.PutObjectResult>`
        """
        headers = utils.set_content_type(http.CaseInsensitiveDict(headers), filename)

        with open(to_unicode(filename), 'rb') as f:
            return self.put_object(key, f, headers=headers, progress_callback=progress_callback)

    def append_object(self, key, position, data,
                      headers=None,
                      progress_callback=None):
        """追加上传一个文件。

        :param str key: 新的文件名，或已经存在的可追加文件名
        :param int position: 追加上传一个新的文件， `position` 设为0；追加一个已经存在的可追加文件， `position` 设为文件的当前长度。
            `position` 可以从上次追加的结果 `AppendObjectResult.next_position` 中获得。

        :param data: 用户数据
        :type data: str、bytes、file-like object或可迭代对象

        :param headers: 用户指定的HTTP头部。可以指定Content-Type、Content-MD5、x-oss-开头的头部等
        :type headers: 可以是dict，建议是oss2.CaseInsensitiveDict

        :param progress_callback: 用户指定的进度回调函数。参考 :ref:`progress_callback`

        :return: :class:`AppendObjectResult <oss2.models.AppendObjectResult>`

        :raises: 如果 `position` 和当前文件长度不一致，抛出 :class:`PositionNotEqualToLength <oss2.exceptions.PositionNotEqualToLength>` ；
                 如果当前文件不是可追加类型，抛出 :class:`ObjectNotAppendable <oss2.exceptions.ObjectNotAppendable>` ；
                 还会抛出其他一些异常
        """
        headers = utils.set_content_type(http.CaseInsensitiveDict(headers), key)

        if progress_callback:
            data = utils.make_progress_adapter(data, progress_callback)

        resp = self.__do_object('POST', key,
                                data=data,
                                headers=headers,
                                params={'append': '', 'position': str(position)})
        return AppendObjectResult(resp)

    def get_object(self, key,
                   byte_range=None,
                   headers=None,
                   progress_callback=None):
        """下载一个文件。

        用法 ::

            >>> result = bucket.get_object('readme.txt')
            >>> print(result.read())
            'hello world'

        :param key: 文件名
        :param byte_range: 指定下载范围。参见 :ref:`byte_range`

        :param headers: HTTP头部
        :type headers: 可以是dict，建议是oss2.CaseInsensitiveDict

        :param progress_callback: 用户指定的进度回调函数。参考 :ref:`progress_callback`

        :return: file-like object

        :raises: 如果文件不存在，则抛出 :class:`NoSuchKey <oss2.exceptions.NoSuchKey>` ；还可能抛出其他异常
        """
        headers = http.CaseInsensitiveDict(headers)

        range_string = _make_range_string(byte_range)
        if range_string:
            headers['range'] = range_string

        resp = self.__do_object('GET', key, headers=headers)
        return GetObjectResult(resp, progress_callback=progress_callback)

    def get_object_to_file(self, key, filename,
                           byte_range=None,
                           headers=None,
                           progress_callback=None):
        """下载一个文件到本地文件。

        :param key: 文件名
        :param filename: 本地文件名。要求父目录已经存在，且有写权限。
        :param byte_range: 指定下载范围。参见 :ref:`byte_range`

        :param headers: HTTP头部
        :type headers: 可以是dict，建议是oss2.CaseInsensitiveDict

        :param progress_callback: 用户指定的进度回调函数。参考 :ref:`progress_callback`

        :return: 如果文件不存在，则抛出 :class:`NoSuchKey <oss2.exceptions.NoSuchKey>` ；还可能抛出其他异常
        """
        with open(to_unicode(filename), 'wb') as f:
            result = self.get_object(key, byte_range=byte_range, headers=headers, progress_callback=progress_callback)
            shutil.copyfileobj(result, f)

            return result

    def head_object(self, key, headers=None):
        """获取文件元信息。

        HTTP响应的头部包含了文件元信息，可以通过 `RequestResult` 的 `headers` 成员获得。
        用法 ::

            >>> result = bucket.head_object('readme.txt')
            >>> print(result.content_type)
            text/plain

        :param key: 文件名

        :param headers: HTTP头部
        :type headers: 可以是dict，建议是oss2.CaseInsensitiveDict

        :return: :class:`HeadObjectResult <oss2.models.HeadObjectResult>`

        :raises: 如果Bucket不存在或者Object不存在，则抛出 :class:`NotFound <oss2.exceptions.NotFound>`
        """
        resp = self.__do_object('HEAD', key, headers=headers)
        return HeadObjectResult(resp)

    def object_exists(self, key):
        """如果文件存在就返回True，否则返回False。如果Bucket不存在，或是发生其他错误，则抛出异常。"""

        # 如果我们用head_object来实现的话，由于HTTP HEAD请求没有响应体，只有响应头部，这样当发生404时，
        # 我们无法区分是NoSuchBucket还是NoSuchKey错误。
        #
        # 下面的实现是通过if-modified-since头部，把date设为当前时间24小时后，这样如果文件存在，则会返回
        # 304 (NotModified)；不存在，则会返回NoSuchKey
        date = oss2.utils.http_date(int(time.time()) + 24 * 60 * 60)

        try:
            self.get_object(key, headers={'if-modified-since': date})
        except exceptions.NotModified:
            return True
        except exceptions.NoSuchKey:
            return False
        else:
            raise exceptions.ClientError('Client time varies too much from server?')  # pragma: no cover

    def copy_object(self, source_bucket_name, source_key, target_key, headers=None):
        """拷贝一个文件到当前Bucket。

        :param str source_bucket_name: 源Bucket名
        :param str source_key: 源文件名
        :param str target_key: 目标文件名

        :param headers: HTTP头部
        :type headers: 可以是dict，建议是oss2.CaseInsensitiveDict

        :return: :class:`PutObjectResult <oss2.models.PutObjectResult>`
        """
        headers = http.CaseInsensitiveDict(headers)
        headers['x-oss-copy-source'] = '/' + source_bucket_name + '/' + source_key

        resp = self.__do_object('PUT', target_key, headers=headers)

        return PutObjectResult(resp)

    def update_object_meta(self, key, headers):
        """更改Object的元数据信息，包括Content-Type这类标准的HTTP头部，以及以x-oss-meta-开头的自定义元数据。

        用户可以通过 :func:`head_object` 获得元数据信息。

        :param str key: 文件名

        :param headers: HTTP头部，包含了元数据信息
        :type headers: 可以是dict，建议是oss2.CaseInsensitiveDict

        :return: :class:`RequestResult <oss2.models.RequestResults>`
        """
        return self.copy_object(self.bucket_name, key, key, headers=headers)

    def delete_object(self, key):
        """删除一个文件。

        :param str key: 文件名

        :return: :class:`RequestResult <oss2.models.RequestResult>`
        """
        resp = self.__do_object('DELETE', key)
        return RequestResult(resp)

    def put_object_acl(self, key, permission):
        """设置文件的ACL。

        :param str key: 文件名
        :param str permission: 可以是oss2.OBJECT_ACL_DEFAULT、oss2.OBJECT_ACL_PRIVATE、oss2.OBJECT_ACL_PUBLIC_READ或
            oss2.OBJECT_ACL_PUBLIC_READ_WRITE。

        :return: :class:`RequestResult <oss2.models.RequestResult>`
        """
        resp = self.__do_object('PUT', key, params={'acl': ''}, headers={'x-oss-object-acl': permission})
        return RequestResult(resp)

    def get_object_acl(self, key):
        """获取文件的ACL。

        :return: :class:`GetObjectAclResult <oss2.models.GetObjectAclResult>`
        """
        resp = self.__do_object('GET', key, params={'acl': ''})
        return self._parse_result(resp, xml_utils.parse_get_object_acl, GetObjectAclResult)

    def batch_delete_objects(self, key_list):
        """批量删除文件。待删除文件列表不能为空。

        :param key_list: 文件名列表，不能为空。
        :type key_list: list of str

        :return: :class:`BatchDeleteObjectsResult <oss2.models.BatchDeleteObjectsResult>`
        """
        if not key_list:
            raise ClientError('key_list should not be empty')

        data = xml_utils.to_batch_delete_objects_request(key_list, False)
        resp = self.__do_object('POST', '',
                                data=data,
                                params={'delete': '', 'encoding-type': 'url'},
                                headers={'Content-MD5': utils.content_md5(data)})
        return self._parse_result(resp, xml_utils.parse_batch_delete_objects, BatchDeleteObjectsResult)

    def init_multipart_upload(self, key, headers=None):
        """初始化分片上传。

        返回值中的 `upload_id` 以及Bucket名和Object名三元组唯一对应了此次分片上传事件。

        :param str key: 待上传的文件名

        :param headers: HTTP头部
        :type headers: 可以是dict，建议是oss2.CaseInsensitiveDict

        :return: :class:`InitMultipartUploadResult <oss2.models.InitMultipartUploadResult>`
        """
        headers = utils.set_content_type(http.CaseInsensitiveDict(headers), key)

        resp = self.__do_object('POST', key, params={'uploads': ''}, headers=headers)
        return self._parse_result(resp, xml_utils.parse_init_multipart_upload, InitMultipartUploadResult)

    def upload_part(self, key, upload_id, part_number, data, progress_callback=None):
        """上传一个分片。

        :param str key: 待上传文件名，这个文件名要和 :func:`init_multipart_upload` 的文件名一致。
        :param str upload_id: 分片上传ID
        :param int part_number: 分片号，最小值是1.
        :param data: 待上传数据。
        :param progress_callback: 用户指定进度回调函数。可以用来实现进度条等功能。参考 :ref:`progress_callback` 。

        :return: :class:`PutObjectResult <oss2.models.PutObjectResult>`
        """
        if progress_callback:
            data = utils.make_progress_adapter(data, progress_callback)

        resp = self.__do_object('PUT', key,
                                params={'uploadId': upload_id, 'partNumber': str(part_number)},
                                data=data)
        return PutObjectResult(resp)

    def complete_multipart_upload(self, key, upload_id, parts, headers=None):
        """完成分片上传，创建文件。

        :param str key: 待上传的文件名，这个文件名要和 :func:`init_multipart_upload` 的文件名一致。
        :param str upload_id: 分片上传ID

        :param parts: PartInfo列表。PartInfo中的part_number和etag是必填项。其中的etag可以从 :func:`upload_part` 的返回值中得到。
        :type parts: list of `PartInfo <oss2.models.PartInfo>`

        :param headers: HTTP头部
        :type headers: 可以是dict，建议是oss2.CaseInsensitiveDict

        :return: :class:`PutObjectResult <oss2.models.PutObjectResult>`
        """
        data = xml_utils.to_complete_upload_request(sorted(parts, key=lambda p: p.part_number))
        resp = self.__do_object('POST', key,
                                params={'uploadId': upload_id},
                                data=data,
                                headers=headers)

        return PutObjectResult(resp)

    def abort_multipart_upload(self, key, upload_id):
        """取消分片上传。

        :param str key: 待上传的文件名，这个文件名要和 :func:`init_multipart_upload` 的文件名一致。
        :param str upload_id: 分片上传ID

        :return: :class:`RequestResult <oss2.models.RequestResult>`
        """
        resp = self.__do_object('DELETE', key,
                                params={'uploadId': upload_id})
        return RequestResult(resp)

    def list_multipart_uploads(self,
                               prefix='',
                               delimiter='',
                               key_marker='',
                               upload_id_marker='',
                               max_uploads=1000):
        """罗列正在进行中的分片上传。支持分页。

        :param str prefix: 只罗列匹配该前缀的文件的分片上传
        :param str delimiter: 目录分割符
        :param str key_marker: 文件名分页符。第一次调用可以不传，后续设为返回值中的 `next_key_marker`
        :param str upload_id_marker: 分片ID分页符。第一次调用可以不传，后续设为返回值中的 `next_upload_id_marker`
        :param int max_uploads: 一次罗列最多能够返回的条目数

        :return: :class:`ListMultipartUploadsResult <oss2.models.ListMultipartUploadsResult>`
        """
        resp = self.__do_object('GET', '',
                                params={'uploads': '',
                                        'prefix': prefix,
                                        'delimiter': delimiter,
                                        'key-marker': key_marker,
                                        'upload-id-marker': upload_id_marker,
                                        'max-uploads': str(max_uploads),
                                        'encoding-type': 'url'})
        return self._parse_result(resp, xml_utils.parse_list_multipart_uploads, ListMultipartUploadsResult)

    def upload_part_copy(self, source_bucket_name, source_key, byte_range,
                         target_key, target_upload_id, target_part_number,
                         headers=None):
        """分片拷贝。把一个已有文件的一部分或整体拷贝成目标文件的一个分片。

        :param byte_range: 指定待拷贝内容在源文件里的范围。参见 :ref:`byte_range`

        :param headers: HTTP头部
        :type headers: 可以是dict，建议是oss2.CaseInsensitiveDict

        :return: :class:`PutObjectResult <oss2.models.PutObjectResult>`
        """
        headers = http.CaseInsensitiveDict(headers)
        headers['x-oss-copy-source'] = '/' + source_bucket_name + '/' + source_key

        range_string = _make_range_string(byte_range)
        if range_string:
            headers['x-oss-copy-source-range'] = range_string

        resp = self.__do_object('PUT', target_key,
                                params={'uploadId': target_upload_id,
                                        'partNumber': str(target_part_number)},
                                headers=headers)

        return PutObjectResult(resp)

    def list_parts(self, key, upload_id,
                   marker='', max_parts=1000):
        """列举已经上传的分片。支持分页。

        :param str key: 文件名
        :param str upload_id: 分片上传ID
        :param str marker: 分页符
        :param int max_parts: 一次最多罗列多少分片

        :return: :class:`ListPartsResult <oss2.models.ListPartsResult>`
        """
        resp = self.__do_object('GET', key,
                                params={'uploadId': upload_id,
                                        'part-number-marker': marker,
                                        'max-parts': str(max_parts)})
        return self._parse_result(resp, xml_utils.parse_list_parts, ListPartsResult)

    def create_bucket(self, permission=None):
        """创建新的Bucket。

        :param str permission: 指定Bucket的ACL。可以是oss2.BUCKET_ACL_PRIVATE（推荐、缺省）、oss2.BUCKET_ACL_PUBLIC_READ或是
            oss2.BUCKET_ACL_PUBLIC_READ_WRITE。
        """
        if permission:
            headers = {'x-oss-acl': permission}
        else:
            headers = None
        resp = self.__do_bucket('PUT', headers=headers)
        return RequestResult(resp)

    def delete_bucket(self):
        """删除一个Bucket。只有没有任何文件，也没有任何未完成的分片上传的Bucket才能被删除。

        :return: :class:`RequestResult <oss2.models.RequestResult>`

        ":raises: 如果试图删除一个非空Bucket，则抛出 :class:`BucketNotEmpty <oss2.exceptions.BucketNotEmpty>`
        """
        resp = self.__do_bucket('DELETE')
        return RequestResult(resp)

    def put_bucket_acl(self, permission):
        """设置Bucket的ACL。

        :param str permission: 新的ACL，可以是oss2.BUCKET_ACL_PRIVATE、oss2.BUCKET_ACL_PUBLIC_READ或
            oss2.BUCKET_ACL_PUBLIC_READ_WRITE
        """
        resp = self.__do_bucket('PUT', headers={'x-oss-acl': permission}, params={Bucket.ACL: ''})
        return RequestResult(resp)

    def get_bucket_acl(self):
        """获取Bucket的ACL。

        :return: :class:`GetBucketAclResult <oss2.models.GetBucketAclResult>`
        """
        resp = self.__do_bucket('GET', params={Bucket.ACL: ''})
        return self._parse_result(resp, xml_utils.parse_get_bucket_acl, GetBucketAclResult)

    def put_bucket_cors(self, input):
        """设置Bucket的CORS。

        :param input: :class:`BucketCors <oss2.models.BucketCors>` 对象或其他
        """
        data = self.__convert_data(BucketCors, xml_utils.to_put_bucket_cors, input)
        resp = self.__do_bucket('PUT', data=data, params={Bucket.CORS: ''})
        return RequestResult(resp)

    def get_bucket_cors(self):
        """获取Bucket的CORS配置。

        :return: :class:`GetBucketCorsResult <oss2.models.GetBucketCorsResult>`
        """
        resp = self.__do_bucket('GET', params={Bucket.CORS: ''})
        return self._parse_result(resp, xml_utils.parse_get_bucket_cors, GetBucketCorsResult)

    def delete_bucket_cors(self):
        """删除Bucket的CORS配置。"""
        resp = self.__do_bucket('DELETE', params={Bucket.CORS: ''})
        return RequestResult(resp)

    def put_bucket_lifecycle(self, input):
        """设置生命周期管理的配置。

        :param input: :class:`BucketLifecycle <oss2.models.BucketLifecycle>` 对象或其他
        """
        data = self.__convert_data(BucketLifecycle, xml_utils.to_put_bucket_lifecycle, input)
        resp = self.__do_bucket('PUT', data=data, params={Bucket.LIFECYCLE: ''})
        return RequestResult(resp)

    def get_bucket_lifecycle(self):
        """获取生命周期管理配置。

        :return: :class:`GetBucketLifecycleResult <oss2.models.GetBucketLifecycleResult>`

        :raises: 如果没有设置Lifecycle，则抛出 :class:`NoSuchLifecycle <oss2.exceptions.NoSuchLifecycle>`
        """
        resp = self.__do_bucket('GET', params={Bucket.LIFECYCLE: ''})
        return self._parse_result(resp, xml_utils.parse_get_bucket_lifecycle, GetBucketLifecycleResult)

    def delete_bucket_lifecycle(self):
        """删除生命周期管理配置。如果Lifecycle没有设置，也返回成功。"""
        resp = self.__do_bucket('DELETE', params={Bucket.LIFECYCLE: ''})
        return RequestResult(resp)

    def get_bucket_location(self):
        """获取Bucket的数据中心。

        :return: :class:`GetBucketLocationResult <oss2.models.GetBucketLocationResult>`
        """
        resp = self.__do_bucket('GET', params={Bucket.LOCATION: ''})
        return self._parse_result(resp, xml_utils.parse_get_bucket_location, GetBucketLocationResult)

    def put_bucket_logging(self, input):
        """设置Bucket的访问日志功能。

        :param input: :class:`BucketLogging <oss2.models.BucketLogging>` 对象或其他
        """
        data = self.__convert_data(BucketLogging, xml_utils.to_put_bucket_logging, input)
        resp = self.__do_bucket('PUT', data=data, params={Bucket.LOGGING: ''})
        return RequestResult(resp)

    def get_bucket_logging(self):
        """获取Bucket的访问日志功能配置。

        :return: :class:`GetBucketLoggingResult <oss2.models.GetBucketLoggingResult>`
        """
        resp = self.__do_bucket('GET', params={Bucket.LOGGING: ''})
        return self._parse_result(resp, xml_utils.parse_get_bucket_logging, GetBucketLoggingResult)

    def delete_bucket_logging(self):
        """关闭Bucket的访问日志功能。"""
        resp = self.__do_bucket('DELETE', params={Bucket.LOGGING: ''})
        return RequestResult(resp)

    def put_bucket_referer(self, input):
        """为Bucket设置防盗链。

        :param input: :class:`BucketReferer <oss2.models.BucketReferer>` 对象或其他
        """
        data = self.__convert_data(BucketReferer, xml_utils.to_put_bucket_referer, input)
        resp = self.__do_bucket('PUT', data=data, params={Bucket.REFERER: ''})
        return RequestResult(resp)

    def get_bucket_referer(self):
        """获取Bucket的防盗链配置。

        :return: :class:`GetBucketRefererResult <oss2.models.GetBucketRefererResult>`
        """
        resp = self.__do_bucket('GET', params={Bucket.REFERER: ''})
        return self._parse_result(resp, xml_utils.parse_get_bucket_referer, GetBucketRefererResult)

    def put_bucket_website(self, input):
        """为Bucket配置静态网站托管功能。

        :param input: :class:`BucketWebsite <oss2.models.BucketWebsite>`
        """
        data = self.__convert_data(BucketWebsite, xml_utils.to_put_bucket_website, input)
        resp = self.__do_bucket('PUT', data=data, params={Bucket.WEBSITE: ''})
        return RequestResult(resp)

    def get_bucket_website(self):
        """获取Bucket的静态网站托管配置。

        :return: :class:`GetBucketWebsiteResult <oss2.models.GetBucketWebsiteResult>`

        :raises: 如果没有设置静态网站托管，那么就抛出 :class:`NoSuchWebsite <oss2.exceptions.NoSuchWebsite>`
        """
        resp = self.__do_bucket('GET', params={Bucket.WEBSITE: ''})
        return self._parse_result(resp, xml_utils.parse_get_bucket_websiste, GetBucketWebsiteResult)

    def delete_bucket_website(self):
        """关闭Bucket的静态网站托管功能。"""
        resp = self.__do_bucket('DELETE', params={Bucket.WEBSITE: ''})
        return RequestResult(resp)

    def _get_bucket_config(self, config):
        """获得Bucket某项配置，具体哪种配置由 `config` 指定。该接口直接返回 `RequestResult` 对象。
        通过read()接口可以获得XML字符串。不建议使用。

        :param str config: 可以是 `Bucket.ACL` 、 `Bucket.LOGGING` 等。

        :return: :class:`RequestResult <oss2.models.RequestResult>`
        """
        return self.__do_bucket('GET', params={config: ''})

    def __do_object(self, method, key, **kwargs):
        return self._do(method, self.bucket_name, key, **kwargs)

    def __do_bucket(self, method, **kwargs):
        return self._do(method, self.bucket_name, '', **kwargs)

    def __convert_data(self, klass, converter, data):
        if isinstance(data, klass):
            return converter(data)
        else:
            return data

def _normalize_endpoint(endpoint):
    if not endpoint.startswith('http://') and not endpoint.startswith('https://'):
        return 'http://' + endpoint
    else:
        return endpoint

_ENDPOINT_TYPE_ALIYUN = 0
_ENDPOINT_TYPE_CNAME = 1
_ENDPOINT_TYPE_IP = 2

def _make_range_string(range):
    if range is None:
        return ''

    start = range[0]
    last = range[1]

    if start is None and last is None:
        return ''

    return 'bytes=' + _range(start, last)

def _range(start, last):
    def to_str(pos):
        if pos is None:
            return ''
        else:
            return str(pos)

    return to_str(start) + '-' + to_str(last)

def _determine_endpoint_type(netloc, is_cname, bucket_name):
    if utils.is_ip_or_localhost(netloc):
        return _ENDPOINT_TYPE_IP

    if is_cname:
        return _ENDPOINT_TYPE_CNAME

    if utils.is_valid_bucket_name(bucket_name):
        return _ENDPOINT_TYPE_ALIYUN
    else:
        return _ENDPOINT_TYPE_IP

class _UrlMaker(object):
    def __init__(self, endpoint, is_cname):
        p = urlparse(endpoint)

        self.scheme = p.scheme
        self.netloc = p.netloc
        self.is_cname = is_cname

    def __call__(self, bucket_name, key):
        self.type = _determine_endpoint_type(self.netloc, self.is_cname, bucket_name)

        key = urlquote(key, '')

        if self.type == _ENDPOINT_TYPE_CNAME:
            return '{0}://{1}/{2}'.format(self.scheme, self.netloc, key)

        if self.type == _ENDPOINT_TYPE_IP:
            if bucket_name:
                return '{0}://{1}/{2}/{3}'.format(self.scheme, self.netloc, bucket_name, key)
            else:
                return '{0}://{1}/{2}'.format(self.scheme, self.netloc, key)

        if not bucket_name:
            assert not key
            return '{0}://{1}'.format(self.scheme, self.netloc)

        return '{0}://{1}.{2}/{3}'.format(self.scheme, bucket_name, self.netloc, key)


from flask import Blueprint, render_template
from stravalib import Client

from monolith.database import db, Run, Plan
from monolith.auth import current_user

home = Blueprint('home', __name__)

def _strava_auth_url(config):
	client = Client()
	client_id = config['STRAVA_CLIENT_ID']
	redirect = 'http://127.0.0.1:5000/strava_auth'
	url = client.authorization_url(client_id=client_id, redirect_uri=redirect)
	return url

@home.route('/')
def index():
	if current_user is not None and hasattr(current_user, 'id'):
		runs = db.session.query(Run).filter(Run.runner_id == current_user.id)
		plans = db.session.query(Plan.start_date,Plan.end_date, Plan.distance).filter(Plan.runner_id == current_user.id)
		stat1 = compare()
		left = distance_left()
		plans = [plan+(left[i],) for i, plan in enumerate(plans)]
		avg = average_speed()
	else:
		runs = None
		plans = None
		avg = None
		stat1 = None
	strava_auth_url = _strava_auth_url(home.app.config)
	return render_template("index.html", runs=runs, plans=plans, stat1=stat1, avg=avg, strava_auth_url=strava_auth_url)

@home.route('/run/<id>', methods=['GET'])
def run(id):
	name    = None
	date    = None
	values  = None

	run = db.session.query(Run).filter(Run.runner_id == current_user.id).filter(Run.id == id).first()
	if run is not None:
		name    = run.name
		date    = run.start_date.strftime('%A %d/%m/%y at %H:%M')
		values  = [run.distance, run.average_speed, run.elapsed_time, run.total_elevation_gain]

	return render_template("run.html",name=name,date=date,values=values,id=run.id)
	# return render_template("index.html", runs=runs, strava_auth_url=strava_auth_url)

def compare():
	runs = db.session.query(Run).filter(Run.runner_id == current_user.id)
	best=0
	run_id=0
	if runs is None:
		print("do nothing")
	else:
		for run in runs:
			if run.distance > best:
				best=run.distance
				run_id=run.id

		bwrd=db.session.query(Run).filter(Run.id == run_id)

	return bwrd

def distance_left():
	runs = db.session.query(Run).filter(Run.runner_id == current_user.id)
	plans = db.session.query(Plan).filter(Plan.runner_id == current_user.id)
	distances_left = ()
	for plan in plans:
		yet_toRun = plan.distance
		for run in runs:
			if run.start_date.date() >= plan.start_date and run.start_date.date() <= plan.end_date:
				yet_toRun -= run.distance/1000
		if yet_toRun < 0:
			yet_toRun = 0
		distances_left+=(yet_toRun,)

	return distances_left

def average_speed():
	runs = db.session.query(Run).filter(Run.runner_id == current_user.id)
	avg = 0
	i = 0
	for run in runs:
		avg += run.average_speed*3.6
		i+=1

	if i == 0:
		return 0

	return avg/i


#!/usr/bin/python3
import qgl
import qgl_util
import qgl_plotting
from math import sin, cos, pi
import numpy as np
#from mpi4py import MPI
import os
import sys
import random
sys.path.append(os.getcwd())
def function_creator(a):
    def linear_potential(x): return a*x
    return linear_potential

# QGL Simulation
# ==============

post_processing = True
gradient_simulation = False
restart = False

# Simulation Parameters
# ---------------------

L_list      = [10]# 12, 14, 16, 18, 20]
dt_list     = [0.1]
t_span_list = [(0, 100.)]
IC_list = []
output_dir  = 'qgol-exactrun115'
tasks       = ['t', 'n', 'nn', 'MI', 'localObs', 'EC-Center', 'SvN']
l = None

hamiltonian_types =['NN_0']#['NN_0','NN_1','NN_2','NN_01','NN_02','NN_12']
R_list = [12, 7, 2, 6, 14, 4, 21, 17, 10, 3, 23, 15, 28]
V_list = ['HP2_'+str(theta) for theta in [0,15,30,45,60,75,90]]
gradient_operator = '1'
gradients = []

if restart == True:
    lmax = 100
else:
    lmax = None

if gradient_simulation == True:
    for a in np.linspace(0, 1, 100):
        gradients.append([gradient_operator, function_creator(a), a])
else:
    gradients.append([gradient_operator, function_creator(0.0), 0.0])

# gradients = gradients[0:3]

def f(n):
    return 'c' + str(n) + 'd' + str(2**(n-1) + 2**(n-3) + 4 + 1)

for L in L_list:
    IC_list.append([L, ('c3d5', 1.0)])
    IC_list.append([L, ('c2d3', 1.0)])
    IC_list.append([L, ('c5d27', 1.0)])
    IC_list.append([L, ('c6d51', 1.0)])
    IC_list.append([L, ('c7d99', 1.0)])
    IC_list.append([L, ('c8d195', 1.0)])
    IC_list.append([L, ('c3d7', 1.0)])
    IC_list.append([L, ('E' + str((L//2) - 1) + '_' + str((L//2)) + '_3', 1.0)])
    IC_list.append([L, ('E' + str((L//2) - 1) + '_' + str((L//2)) + '_4', 1.0)])
    IC_list.append([L, ('S', 1.0)])
    IC_list.append([L, ('r3', 1.0)])
    IC_list.append([L, ('R3', 1.0)])
    IC_list.append([L, ('C', 1.0)])

    # IC_list.append([L, ('G', 1.0)])
    # for n in range(9,17):
    #     IC_list.append([L, (f(n), 1.0)])
    # IC_list.append([L, ('c8d165', 1.0)])
    # IC_list.append([L, ('B1', 1.0)])
    # IC_list.append([L, ('B2', 1.0)])
    # IC_list.append([L, ('E' + str((L//2)+1) + '_' + str((L//2) + 2) + '_3', 1.0)])
    # IC_list.append([L, ('E' + str((L//2)+1) + '_' + str((L//2) + 2) + '_4', 1.0)])
    # IC_list.append([L, ('c4d15', 1.0)])

# Simulations to Run
# ------------------
simulations = [ (tasks,  IC[0], t_span, dt, IC[1:], output_dir, gradient[0], gradient[1] , gradient[2], l, restart, lmax, hamiltonian_type)
                     for dt       in dt_list
                     for t_span   in t_span_list
                     for IC       in IC_list
                     for gradient in gradients
                     for hamiltonian_type in hamiltonian_types
                     for V        in V_list]

# Run them
# --------
if not post_processing:
    comm = MPI.COMM_WORLD
    rank = comm.Get_rank()
    nprocs = comm.Get_size()
    for i, simulation in enumerate(simulations):
        if i % nprocs == rank:
            sim = qgl.Simulation (*simulation)
            del sim

# Post Processing
# ===============
if post_processing:
    qgl_plotting.main(output_dir, tasks, dt_list, \
                      t_span_list, IC_list, hamiltonian_types, V_list, gradients = gradients)


import argparse
import logging
import pickle
import sys
from io import StringIO
from os import getenv
from os.path import abspath, join

import pandas as pd  # type: ignore
import xgboost as xgb  # type: ignore

from preprocess import preprocess

random_state = 42
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)

target_columns = [
    "incident",
]

# Work around for a SageMaker path issue
# (see https://github.com/aws/sagemaker-python-sdk/issues/648)
# WARNING - removing this may cause the submission process to fail
if abspath("/opt/ml/code") not in sys.path:
    sys.path.append(abspath("/opt/ml/code"))

def train(args):
    """Train

    Your model code goes here.
    """
    logger.info("calling training function")

    # If you require any particular preprocessing to create features then this
    # *must* be contained in the preprocessing function for the Unearthed pipeline
    # apply it to the private data
    df = preprocess(join(args.data_dir, "public.csv.gz"))

    # the target variable is incident
    y_train = df[target_columns]
    logger.info(f"training target shape is {y_train.shape}")
    X_train = df.drop(columns=target_columns)
    logger.info(f"training input shape is {X_train.shape}")

    model = xgb.XGBClassifier()
    print(model.fit(X_train, y_train))

    # save the model to disk
    save_model(model, args.model_dir)

def save_model(model, model_dir):
    """Save model to a binary file.

    This function must write the model to disk in a format that can
    be loaded from the model_fn.

    WARNING - modifying this function may cause the submission process to fail.
    """
    logger.info(f"saving model to {model_dir}")
    with open(join(model_dir, "model.pkl"), "wb") as model_file:
        pickle.dump(model, model_file)

def model_fn(model_dir):
    """Load model from binary file.

    This function loads the model from disk. It is called by SageMaker.

    WARNING - modifying this function may case the submission process to fail.
    """
    logger.info("loading model")
    with open(join(model_dir, "model.pkl"), "rb") as file:
        return pickle.load(file)

def input_fn(input_data, content_type):
    """Take request data and de-serialize the data into an object for prediction.

    In the Unearthed submission pipeline the data is passed as "text/csv". This
    function reads the CSV into a Pandas dataframe ready to be passed to the model.

    WARNING - modifying this function may cause the submission process to fail.
    """
    logger.info("receiving preprocessed input")

    # this call must result in a dataframe or nparray that matches your model
    input = pd.read_csv(StringIO(input_data), index_col=0, parse_dates=True)
    logger.info(f"preprocessed input has shape {input.shape}")
    return input

if __name__ == "__main__":
    """Training Main

    The main function is called by both Unearthed's SageMaker pipeline and the
    Unearthed CLI's "unearthed train" command.

    WARNING - modifying this function may cause the submission process to fail.

    The main function must call preprocess, arrange th
    """
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--model_dir", type=str, default=getenv("SM_MODEL_DIR", "/opt/ml/models")
    )
    parser.add_argument(
        "--data_dir",
        type=str,
        default=getenv("SM_CHANNEL_TRAINING", "/opt/ml/input/data/training"),
    )
    train(parser.parse_args())


from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.http import JsonResponse
from django.views.generic import ListView

from core.models import Category, Product
from .models import Favorite

@login_required(login_url='login')
def save_favorite(request):
    """
    This function is used to register a product in the Favorite table.
    The user must be logged in to register a product.
    This function interacts with an AJAX script.
    """
    product_code = request.POST['product_code']
    product_selected = Product.objects.get(code=product_code)

    name_substitute_for = request.POST['product_sub_for']
    code_substitute_for = [p.code for p in Product.objects.filter(name__iexact=name_substitute_for)][0]

    user = request.user

    Favorite(name=product_selected.name,
             code=product_selected.code,
             nutrition_grade=product_selected.nutrition_grade,
             code_substitute_for=code_substitute_for,
             name_substitute_for=name_substitute_for,
             url=product_selected.url,
             category=product_selected.category,
             user=user).save()

    response_data = {
        'status': 'ok'
    }
    return JsonResponse(response_data)

@method_decorator(login_required, name='dispatch')
class FavoriteList(ListView):
    """
    This view retrieves the user's registered products
    and displays them using the template favorite-list.html.
    The user must be logged in to access his registered products.
    """
    model = Favorite
    context_object_name = "favorite_list"
    template_name = "favorite-list.html"

    def get_queryset(self):
        user = self.request.user
        return Favorite.objects.filter(nutrition_grade="good").order_by('nutrition_grade')


#!/usr/bin/env python3

import random
import sys
import math

def get_random_direction():
    direction = {}
    probability = random.random()

    if probability < 0.5:
        direction['west']=(-1, 0)
    elif probability < 0.7:
        direction['east'] = (1, 0)
    elif probability < 0.9:
        direction['north']=(0, 1)
    else:
        direction['south']=(0, -1)
    val=list(direction.values())[0]
    return val

def take_walk(steps):
    current_location = [0, 0]
    for step_index in range(steps):
        direction = get_random_direction()
        change_in_x = direction[0]
        change_in_y = direction[1]
        current_location[0] += change_in_x
        current_location[1] += change_in_y
    return current_location

def take_all_walks(steps, runs):
    endpoints = []
    for run_index in range(runs):
        end_location = take_walk(steps)
        endpoints.append(end_location)
    return endpoints

def average_final_distance(endpoints):
    total_distance = 0
    for coords in endpoints:
        dx = coords[0]
        dy = coords[1]
        distance = math.sqrt(dx*dx + dy*dy)
        total_distance += distance
    return total_distance / len(endpoints)

if __name__ == "__main__":

    if len(sys.argv) > 1:
        steps = int(sys.argv[1])
    runs = 1
    if len(sys.argv) > 2:
        runs = int(sys.argv[2])

    current_location = take_walk(steps)
    end_locations = take_all_walks(steps, runs)
    average_displacement = average_final_distance(end_locations)

    print("Done with walk, printing end location: ")
    print(current_location)
    print(end_locations)
    print(average_displacement)


# coding: utf-8

# In[1]:

def sum(a, b):
    return a + b

def safe_sum(a, b):
    if type(a)!=type(b): #객체 a와 객체 b의 자료형이 같지 않으면
        print("더할수 있는 것이 아닙니다.") #메세지 출력
        return #메세지 출력
    else:#객체 a와 객체 b의 자료형이 같다면
        result = sum(a,b)#두 객체를 더한다
        return result

print(safe_sum('a', 1))
print(safe_sum(1, 4))
print(sum(10, 10.4))