import io
import scipy.io as matio
import os
import os.path
import numpy as np
from PIL import Image
import time
import re

import torch
import torch.utils.data
import torch.nn.parallel as para
from torch import nn, optim
from torch.autograd import Variable
from torch.nn import functional as F
from torchvision import datasets, transforms
from torchvision.utils import save_image
import torch.utils.model_zoo as model_zoo

#load environmental settings
import opts
opt = opts.opt_algorithm()

# —Path settings———————————————————————————————————————————————————————————————————————————————————————————————————————

#file paths
def prepare_intermediate_folders(path):
    if not os.path.exists(path):
        os.makedirs(path)

dataset_indicator = opt.dataset
root_path = opt.root_path
image_path = opt.img_path
data_path = opt.data_path
result_path = opt.result_path

#train type
train_stage = opt.stage
mode = opt.mode
img_net_type = opt.img_net
ingre_net_type = opt.ingre_net

#data info
num_class = opt.dataset_num_class
num_word = opt.dataset_num_ingre
avg_word = opt.dataset_avg_ingre
if dataset_indicator is 'vireo':
    if mode is 'test':
        max_seq = opt.dataset_max_seq_test
    else:
        max_seq = opt.dataset_max_seq_val
else:
    max_seq = opt.dataset_max_seq

#Used in performance estimation for ingredient prediction
if dataset_indicator is 'vireo':
    with io.open(opt.food_class_name_path, encoding='utf-8') as file:
        class_names = file.read().split('\n')[:-1]
    class_names = np.array(class_names)

    ingre_names = matio.loadmat(opt.ingre_term_list_path)['ingreList']
    for i in range(len(ingre_names)):
        ingre_names[i] = ingre_names[i].strip()
else:
    with io.open(opt.food_class_name_path, encoding='utf-8') as file:
        class_names = file.read().split('\n')
    class_names = np.array(class_names)

    ingre_names = matio.loadmat(opt.ingre_term_list_path)['ingreList']
    for i in range(len(ingre_names)):
        ingre_names[i] = ingre_names[i].strip()

# Image Info
no_of_channels = 3
image_size = [256, 256]  # [64,64]

#algorithm hyperparameter
CUDA = 1  # 1 for True; 0 for False
SEED = 1
BATCH_SIZE = 2
LOG_INTERVAL = 1

if img_net_type is 'vgg19bn':
    latent_len = 4096
else:
    latent_len = 2048
blk_len = int(latent_len * 3 / 8)

lr_decay = 4
EPOCHS = lr_decay * 3 + 1

torch.manual_seed(SEED)
if CUDA:
    torch.cuda.manual_seed(SEED)
kwargs = {'num_workers': 2, 'pin_memory': True} if CUDA else {}

#--Create dataset and dataloader----------------------------------------------------------------------------------

#define transform ops
normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                 std=[0.229, 0.224, 0.225])

transform_img = transforms.Compose([
            transforms.ToTensor(),
            normalize,
        ])

#create dataset
from build_dataset import build_dataset
dataset = build_dataset(train_stage, image_path, data_path, transform_img, mode, dataset_indicator)

#dataloader
test_loader = torch.utils.data.DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=False, **kwargs)

#--Model----------------------------------------------------------------------------------

from mymodel import build_mymodel
model, _ = build_mymodel(mode, data_path, CUDA, image_size, latent_len, blk_len, num_class, max_seq, num_word,\
                                 None, None, train_stage, [img_net_type, ingre_net_type], None, None)
model.eval()

# --performance----------------------------------------------------------------

from compute_performance import compute_performance

# --test & validation----------------------------------------------------------------

def print_state(state):
    print(state + '/n')
    # records current progress for tracking purpose
    with io.open(result_path + 'model_batch_test_performance.txt', 'a', encoding='utf-8') as file:
        file.write(state + '/n')

def compute_distribution_similarity(predicted_distribution, true_distribution):
    if len(predicted_distribution.shape) > 1:
        predicted_distribution = predicted_distribution.flatten()
        true_distribution = true_distribution.flatten()

    concated_distributions = np.stack([predicted_distribution, true_distribution],0)
    min = np.min(concated_distributions, 0)
    max = np.max(concated_distributions, 0)
    return np.sum(min) / np.sum(max) if np.sum(max) else 0

# def predicts_normalization(predicts):
#     #perform min-max normalization and then sum-to-one
#     for i in range(len(predicts)):# for each batch item
#         min = predicts[i].min()
#         max = predicts[i].max()
#         predicts[i] = (predicts[i] - min) / (max - min)
#         predicts[i] = predicts[i] / predicts[i].sum()
#     return predicts

def compute_ingre_presence(x2y_ingre_recon, Top_n_for_predicts, top_n_ingredients, num_ingredients):
    # create matrix for intermediate results
    matrix_for_presence = np.zeros([top_n_ingredients, num_ingredients])

    for j in range(top_n_ingredients):  # for each of top-n seq entry
        # get the top-3 predicts and convert to ingre prediction vector
        sorted_predicts = (-x2y_ingre_recon[j, :]).argsort()  # ranked list of indexes, value:low -> high
        Top_predicts_index = sorted_predicts[:Top_n_for_predicts]
        Top_predicts_prob = x2y_ingre_recon[j, Top_predicts_index]

        matrix_for_presence[j, Top_predicts_index] = Top_predicts_prob
    return np.max(matrix_for_presence, 0)

def compute_cls_predicts_presence(top_cls_index, Pr_cls, top_n_class, Pr_ingre_presence):
    # get top-10 predicted classes
    top_cls_ingre_statistics = Pr_cls[top_cls_index]

    # compute probabilities of matching between Pr_ingre_presence and top-10 cls statistics
    ingre_infer_predicts_presence = np.zeros(top_n_class)
    for k in range(top_n_class):
        ingre_infer_predicts_presence[k] = compute_distribution_similarity(Pr_ingre_presence, top_cls_ingre_statistics[k])
    return ingre_infer_predicts_presence

def compute_ingre_coocurrence(Pr_ingre_presence):
    #compute coocurrence
    indicator_a = np.expand_dims(Pr_ingre_presence, axis=1)
    indicator_b = np.expand_dims(Pr_ingre_presence, axis=0)
    indicator_matrix = np.matmul(indicator_a, indicator_b)
    return indicator_matrix

def compute_cls_predicts_coocurrence(top_cls_index, Pr_co, top_n_class, Pr_ingre_coocurrence):
    # get top-10 predicted classes
    top_cls_ingre_statistics = Pr_co[top_cls_index]

    # compute probabilities of matching between Pr_ingre_presence and top-10 cls statistics
    ingre_infer_predicts_coocurrence = np.zeros(top_n_class)
    for k in range(top_n_class):
        ingre_infer_predicts_coocurrence[k] = compute_distribution_similarity(Pr_ingre_coocurrence, top_cls_ingre_statistics[k])

    return ingre_infer_predicts_coocurrence

def compute_ingre_sequence(x2y_ingre_recon, Top_n_for_predicts, top_n_ingredients, num_ingredients):
    # create matrix for intermediate results
    matrix_for_ingre_order = np.zeros([top_n_ingredients, num_ingredients, num_ingredients])

    for j in range(top_n_ingredients-1):  # for each of top-(n-1) seq entry
        # get the top-3 predicts from current hidden vector
        sorted_predicts = (-x2y_ingre_recon[j, :]).argsort()  # ranked list of indexes, value:low -> high
        Top_predicts_index_cur = sorted_predicts[:Top_n_for_predicts]
        Top_predicts_prob_cur = x2y_ingre_recon[j, Top_predicts_index_cur]

        # get the top-3 predicts from next hidden vector
        sorted_predicts = (-x2y_ingre_recon[j+1, :]).argsort()
        Top_predicts_index_next = sorted_predicts[:Top_n_for_predicts]
        Top_predicts_prob_next = x2y_ingre_recon[j, Top_predicts_index_next]

        #fill the matrix for ingredient orders
        for p in range(Top_n_for_predicts):
            for q in range(Top_n_for_predicts):
                matrix_for_ingre_order[j,Top_predicts_index_cur[p],Top_predicts_index_next[q]] = Top_predicts_prob_cur[p] * Top_predicts_prob_next[q]

    #get class wise ingre order
    class_wise_ingre_order = np.max(matrix_for_ingre_order, 0)
    for k in range(num_ingredients):
        denominator = class_wise_ingre_order[k].sum()
        if denominator > 0:
            class_wise_ingre_order[k] = class_wise_ingre_order[k] / denominator

    return class_wise_ingre_order

def compute_cls_predicts_sequence(top_cls_index, Pr_pairN, top_n_class, Pr_ingre_sequence):
    # get top-10 predicted classes
    top_cls_ingre_statistics = Pr_pairN[top_cls_index]

    # compute probabilities of matching between Pr_ingre_presence and top-10 cls statistics
    ingre_infer_predicts_sequence = np.zeros(top_n_class)
    for k in range(top_n_class):
        ingre_infer_predicts_sequence[k] = compute_distribution_similarity(Pr_ingre_sequence, top_cls_ingre_statistics[k])
    return ingre_infer_predicts_sequence

def get_softmax(numpy_vector):
    tensor = torch.from_numpy(numpy_vector).unsqueeze(0)
    tensor = F.softmax(tensor, dim=1)
    return tensor.numpy()[0]

def get_fused_predicts(predicts_t, predicts_v):
    predicts_t = get_softmax(predicts_t)
    predicts_v = get_softmax(predicts_v)

    return np.min(np.stack([predicts_t,predicts_v],0), 0)

def get_top_n_predicts_nn(x2y_ingre_recon, top_n_ingredients):
    sorted_predicts = (-x2y_ingre_recon).argsort()  # ranked list of indexes, value:low -> high
    Top_predicts_index = sorted_predicts[:top_n_ingredients]
    Top_predicts_prob = x2y_ingre_recon[Top_predicts_index]

    filtered_ingre_predicts = np.zeros(len(x2y_ingre_recon))
    filtered_ingre_predicts[Top_predicts_index] = Top_predicts_prob
    return filtered_ingre_predicts

def get_fused_decision(predicts_v, x2y_ingre_recon, avg_word, ingre_net_type, data_path):
    #user-defined n of top-n predicts for matching
    top_n_class = 10 # match top-10 classes in predicts_v
    top_n_ingredients = avg_word # consider first avg_word words in seq

    #batch info
    batch_size = len(predicts_v)

    #load dataset class-ingredient and ingredient-ingredient statistics
    Pr_cls = matio.loadmat(data_path + 'Pr_cls.mat')['Pr_cls']
    Pr_co = matio.loadmat(data_path + 'Pr_co.mat')['Pr_co']
    Pr_pairN = matio.loadmat(data_path + 'Pr_pairN.mat')['Pr_pairN']

    #get the info of top-n predicted classes
    predicts_v = predicts_v.detach().cpu().numpy()
    top_cls_indexes = (-predicts_v).argsort()[:, :top_n_class] #order: high -> low
    top_cls_probs = np.zeros([batch_size, top_n_class])
    for i in range(batch_size):
        top_cls_probs[i] = predicts_v[i][top_cls_indexes[i]]

    #initialize final results
    fused_predicts = np.zeros([batch_size, top_n_class])

    #get ingre-inferred decisions
    if ingre_net_type is 'gru': #x2y_ingre_recon: (seq, batch, number_of_ingredients)
        # consider top-3 of the predictions for each seq entry
        Top_n_for_predicts = 3

        #manual settings for decision fusion
        alpha = 0.5
        beta = 0.5
        gammar = 0.2

        # make x2y_ingre_recon of (batch, seq, number_of_ingredients)
        x2y_ingre_recon = x2y_ingre_recon.transpose(0, 1)
        x2y_ingre_recon = x2y_ingre_recon.cpu().data.numpy()

        num_ingredients = x2y_ingre_recon.shape[2]

        #process each batch item
        for i in range(batch_size):
            # compute Pr_cls-inferred class distribution
                # compute ingre presence from ingre predicts
            Pr_ingre_presence = compute_ingre_presence(x2y_ingre_recon[i], Top_n_for_predicts, top_n_ingredients, num_ingredients)
                # compute Pr_cls-inferred top-10 class predicts
            ingre_infer_predicts_presence = compute_cls_predicts_presence(top_cls_indexes[i], Pr_cls, top_n_class, Pr_ingre_presence)

            #compute Pr_co-inferred class distribution
                # compute ingre coocurrence from ingre predicts
            Pr_ingre_coocurrence = compute_ingre_coocurrence(Pr_ingre_presence)
                # compute Pr_co-inferred top-10 class predicts
            ingre_infer_predicts_coocurrence = compute_cls_predicts_coocurrence(top_cls_indexes[i], Pr_co, top_n_class, Pr_ingre_coocurrence)

            # compute Pr_pairN-inferred class distribution
                # compute ingre sequential dependency from ingre predicts
            Pr_ingre_sequence = compute_ingre_sequence(x2y_ingre_recon[i], Top_n_for_predicts, top_n_ingredients, num_ingredients)
                # compute Pr_co-inferred top-10 class predicts
            ingre_infer_predicts_sequence = compute_cls_predicts_sequence(top_cls_indexes[i], Pr_pairN, top_n_class, Pr_ingre_sequence)

            #get fused decision
            ingre_infer_predicts_fused =alpha * get_softmax(ingre_infer_predicts_presence) + beta * get_softmax(ingre_infer_predicts_coocurrence) + gammar * get_softmax(ingre_infer_predicts_sequence)
            ingre_infer_predicts_fused = ingre_infer_predicts_fused / (alpha + beta + gammar)

            #get final fused predicts
            fused_predicts[i] = get_fused_predicts(ingre_infer_predicts_fused, top_cls_probs[i])

        return fused_predicts, top_cls_indexes

    else: # ingre_net_type is 'nn'
        #manual settings for decision fusion
        alpha = 0.4
        beta = 1-alpha

        x2y_ingre_recon = x2y_ingre_recon.cpu().data.numpy()

        #process each batch item
        for i in range(batch_size):
            #filter out top-n words for later matching
            filtered_ingre_predicts = get_top_n_predicts_nn(x2y_ingre_recon[i], top_n_ingredients)

            # compute Pr_cls-inferred class distribution
            ingre_infer_predicts_presence = compute_cls_predicts_presence(top_cls_indexes[i], Pr_cls, top_n_class, filtered_ingre_predicts)

            #compute Pr_co-inferred class distribution
                # compute ingre coocurrence from ingre predicts
            Pr_ingre_coocurrence = compute_ingre_coocurrence(filtered_ingre_predicts)
                # compute Pr_co-inferred top-10 class predicts
            ingre_infer_predicts_coocurrence = compute_cls_predicts_coocurrence(top_cls_indexes[i], Pr_co, top_n_class, Pr_ingre_coocurrence)

            #get fused decision
            ingre_infer_predicts_fused =alpha * get_softmax(ingre_infer_predicts_presence) + beta * get_softmax(ingre_infer_predicts_coocurrence)

            #get final fused predicts
            fused_predicts[i] = get_fused_predicts(ingre_infer_predicts_fused, top_cls_probs[i])

        return fused_predicts, top_cls_indexes

def compute_cls_precision_for_fused_predicts(predicts, cls_indexes, labels):
    sorted_predicts = (-predicts).argsort()
    top1_labels = sorted_predicts[:, 0].copy()
    batch_size = len(labels)
    for i in range(batch_size):
        top1_labels[i] = cls_indexes[i,top1_labels[i]]
    match = float(sum(top1_labels - labels + 1 == 0))

    top5_labels = sorted_predicts[:, :5]
    hit = 0
    for i in range(batch_size):
        hit += (labels[i] - 1) in cls_indexes[i, top5_labels[i]]

    return match, hit

def test_epoch(epoch):

    print('Testing starts..')

    #for model performance
    top1_accuracy_total_V = 0
    top5_accuracy_total_V = 0
    top1_accuracy_total_T = 0
    top5_accuracy_total_T = 0

    top1_accuracy_total_fuse = 0
    top5_accuracy_total_fuse = 0

    if ingre_net_type is 'gru':
        ingre_pred_precision_total = np.zeros(max_seq)
        ingre_pred_recall_total = np.zeros(max_seq)
        ingre_pred_word_total = np.zeros(5)
    else:
        ingre_pred_precision_total = np.zeros(avg_word)
        ingre_pred_recall_total = np.zeros(avg_word)

    total_time = time.time()

    for batch_idx, (data) in enumerate(test_loader):

        start_time = time.time()
        if batch_idx == 2:
            break

        # load data
        [imgs, indexVectors, ingredients, labels] = data
        if CUDA:
            imgs = imgs.cuda()
            indexVectors = indexVectors.cuda()
            ingredients = ingredients.cuda()

        # perform model inference
        if ingre_net_type is 'gru':
            predicts, _, _, cross_vectors = model(imgs, indexVectors)
        else:
            predicts, _, _, cross_vectors = model(imgs, ingredients)

        # perform decision fusion
        predicts_v = predicts[0]
        x2y_ingre_recon = cross_vectors[1]
        fused_predicts, top_cls_indexes = get_fused_decision(predicts_v, x2y_ingre_recon, avg_word, ingre_net_type, data_path)

        # compute performance
        [top1_hit_V, top5_hit_V], [top1_hit_T, top5_hit_T], ingre_pred_performance = compute_performance(mode, predicts, x2y_ingre_recon, labels, indexVectors, ingredients, ingre_net_type, avg_word, class_names, ingre_names, result_path)

        top1_hit_fuse, top5_hit_fuse = compute_cls_precision_for_fused_predicts(fused_predicts, top_cls_indexes, labels)

        # img cls precision
        top1_accuracy_total_V += top1_hit_V
        top1_accuracy_cur_V = top1_hit_V / float(len(labels))

        top5_accuracy_total_V += top5_hit_V
        top5_accuracy_cur_V = top5_hit_V / float(len(labels))

        # ingre cls precision
        top1_accuracy_total_T += top1_hit_T
        top1_accuracy_cur_T = top1_hit_T / float(len(labels))

        top5_accuracy_total_T += top5_hit_T
        top5_accuracy_cur_T = top5_hit_T / float(len(labels))

        # fused cls precision
        top1_accuracy_total_fuse += top1_hit_fuse
        top1_accuracy_cur_fuse = top1_hit_fuse / float(len(labels))

        top5_accuracy_total_fuse += top5_hit_fuse
        top5_accuracy_cur_fuse = top5_hit_fuse / float(len(labels))

        #ingredient prediction performance
        if ingre_net_type is 'gru':
            [avg_precision, avg_recall, avg_precision_word] = ingre_pred_performance
            ingre_pred_precision_total += avg_precision
            ingre_pred_recall_total += avg_recall
            ingre_pred_word_total += avg_precision_word
        else: #if ingre_net_type is 'nn'
            [avg_precision, avg_recall] = ingre_pred_performance
            ingre_pred_precision_total += avg_precision
            ingre_pred_recall_total += avg_recall

        #print batch-level performance
        if batch_idx % LOG_INTERVAL == 0:
            state = 'Test Epoch: {} [{}/{} ({:.0f}%)] | Top1_v: {:.4f} | Top5_v: {:.4f} | Top1_f: {:.4f} | Top5_f: {:.4f} | Top1_t: {:.4f} | Top5_t: {:.4f} | Time:{} | Total_Time:{}'.format(
                epoch, (batch_idx + 1) * len(ingredients), len(test_loader.dataset), 100. * (batch_idx + 1) / len(test_loader),
                top1_accuracy_cur_V, top5_accuracy_cur_V,
                top1_accuracy_cur_fuse, top5_accuracy_cur_fuse,
                avg_precision[0],
                top1_accuracy_cur_T, top5_accuracy_cur_T,
                round((time.time() - start_time), 4) * LOG_INTERVAL,
                round((time.time() - total_time), 4))
            print_state(state)

    #record epoch-level results
    print('====> Epoch: {} | Top1_Acc_V: {:.4f} | Top5_Acc_V: {:.4f} | Top1_Acc_F: {:.4f} | Top5_Acc_F: {:.4f} | Ingre_Pred_P: {:.4f} | Top1_Acc_T: {:.4f} | Top5_Acc_T: {:.4f} | Total_Time:{}\n'.format(
        epoch,
        top1_accuracy_total_V / len(test_loader.dataset), top5_accuracy_total_V / len(test_loader.dataset),
        top1_accuracy_total_fuse / len(test_loader.dataset), top1_accuracy_total_fuse / len(test_loader.dataset),
        ingre_pred_precision_total[0] / len(test_loader),
        top1_accuracy_total_T / len(test_loader.dataset), top5_accuracy_total_T / len(test_loader.dataset),
        round((time.time() - total_time), 4)))

    with io.open(result_path + 'test_performance.txt', 'a', encoding='utf-8') as file:
        file.write('Epoach {}:\n Top1_Acc_V={:.4f}, Top5_Acc_V={:.4f},\n Top1_Acc_F={:.4f}, Top5_Acc_F={:.4f},\n ingre_p={},\n ingre_r={}'.format(
            epoch,
            top1_accuracy_total_V / len(test_loader.dataset), top5_accuracy_total_V / len(test_loader.dataset),
            top1_accuracy_total_fuse / len(test_loader.dataset), top1_accuracy_total_fuse / len(test_loader.dataset),
            ingre_pred_precision_total / len(test_loader),
            ingre_pred_recall_total / len(test_loader),
        ))

        if ingre_net_type is 'gru':
            file.write(',\n ingre_w = {}\n\n'.format(ingre_pred_word_total / len(test_loader)))
        else:
            file.write('\n\n')

    return top1_accuracy_total_fuse / len(test_loader.dataset), top1_accuracy_total_fuse / len(test_loader.dataset)

# excute main
max_index = 0
max_top1 = 0
max_top5 = 0

start_epoch = lr_decay + 1

for i in range(start_epoch, EPOCHS+1):

    path = result_path + 'model' + str(i) + '.pt'
    trained_model = torch.load(path, map_location='cpu')
    model.load_state_dict(trained_model)

    top1, top5 = test_epoch(i)

    if top1 > max_top1:
        max_top1 = top1
        max_top5 = top5
        max_index = i
print('Max is achieved by model{} with Top1:{} | Top5:{} |'.format(max_index, max_top1, max_top5))


from flask import Flask,render_template
from flask_sqlalchemy import SQLAlchemy
from myform import LoginForm
from flask_bootstrap import Bootstrap

app=Flask(__name__)
Bootstrap(app)
app.config['DEBUG']=True
app.config['SQLALCHEMY_DATABASE_URI']='sqlite:///index.db'
app.config['SECRET_KEY']='secret_key'

db=SQLAlchemy(app)

class Member(db.Model):

    id = db.Column(db.Integer(),primary_key=True)
    name = db.Column(db.String(20))
    email = db.Column(db.String(20))
    random = db.Column(db.String(20))

@app.route('/login')
def login():
    form=LoginForm()
    return render_template('index.html',form=form)

if __name__=='__main__':
    app.run(debug=True)


"""
滑块
"""
from PyQt5.QtGui import QFont

"""
滑块
根据滑块的变化，动态改变 label 标签的字体大小

"""
import sys

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QVBoxLayout, QApplication, QWidget, QHBoxLayout, QCheckBox, QPushButton, QComboBox, QLabel, \
    QSpinBox, QSlider

class MainWindow(QWidget):
    count = 0
    def __init__(self):
        super().__init__()

        self.resize(400, 300)
        self.setWindowTitle("QSlider")
        self.init_ui()

    def init_ui(self):

        self.font = QFont()
        self.font.setFamily('微软雅黑')
        self.font.setPointSize(16)

        label = QLabel('hello')
        label.setFont(self.font)
        slider = QSlider(Qt.Horizontal)
        # 设置范围
        slider.setRange(1, 48)
        # 设置步长
        slider.setPageStep(2)

        slider.setSingleStep(2)
        # 设置默认值为 16
        slider.setValue(16)

        # 设置刻度线
        ## 下方刻度线
        # slider.setTickPosition(QSlider.TicksBelow)
        ## 兩方刻度綫
        slider.setTickPosition(QSlider.TicksBothSides)

        slider.valueChanged.connect(lambda: self.value_changed(label, slider))

        layout = QVBoxLayout()
        layout.addWidget(label)
        layout.addWidget(slider)

        self.setLayout(layout)
    def value_changed(self, label, slider):
        value = slider.value()
        self.font.setPointSize(value)
        label.setFont(self.font)

def main():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()


# step size; this will also be random
#n = 600

# this number is the max that a class' member count might have
number_to_iterate = 4321

# the number to start the iteration
#rand_start = 100

max_iter_count = -1
hardest_n = -1
hardest_start = -1

for n in range(1, number_to_iterate):
    for rand_start in range(1, number_to_iterate):
        selected = []
        i = rand_start
        iteration_count = 0
        last_n = n
        while len(selected) < number_to_iterate // 2:
            iteration_count += 1
            if i in selected:
                # increase the step size with one (n, n+1 are always relatively prime)
                n = (n + 1) % number_to_iterate
                last_n = n
                i = (i + n) % number_to_iterate
                continue
            selected.append(i)
            i = (i + n) % number_to_iterate
        if iteration_count > max_iter_count:
            max_iter_count = iteration_count
            hardest_start = rand_start
            hardest_n = last_n
        print(rand_start)
    print(n)

print(hardest_n)
print(hardest_start)
print(max_iter_count)


import cv2
import numpy as np
from shapely.geometry import Polygon
import classes.system_utilities.image_utilities.ImageUtilities as IU
import classes.test_classes.tracker as T

# Global variable declarations
yolo_net = 0
yolo_class_names = 0
yolo_output_layer_names = 0
yolo_net_input_size = 0
tracker = T.EuclideanDistTracker()

def OnLoad():
    # All models and internal/external dependencies should be both loaded and initialized here

    # Initialize YOLOv3 model
    global yolo_net
    global yolo_class_names
    global yolo_output_layer_names
    global yolo_net_input_size

    classes_file = 'modules\\YOLOv3\\coco.names'

    with open(classes_file, 'rt') as f:
        yolo_class_names = f.read().rstrip('\n').split('\n')

    model_config = 'modules\\YOLOv3\\yolov3-320.cfg'
    model_weights = 'modules\\YOLOv3\\yolov3-320.weights'
    yolo_net_input_size = 256

    yolo_net = cv2.dnn.readNetFromDarknet(model_config, model_weights)
    # Set the target device for computation
    yolo_net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
    yolo_net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)

    yolo_layer_names = yolo_net.getLayerNames()
    yolo_output_layer_names = [yolo_layer_names[i[0] - 1] for i in yolo_net.getUnconnectedOutLayers()]

# This function executes when the class loads
OnLoad()

def DetectObjectsInImage(image):
    # The function takes an input image and outputs all of the objects it detects in the image.
    # The bounding box is output in the format of [TL, BR] with points [x, y]

    height, width, _ = image.shape

    # Convert image to blob for the yolo network
    blob = IU.ImageToBlob(image=image,
                          input_size=yolo_net_input_size)

    yolo_net.setInput(blob)

    yolo_outputs = yolo_net.forward(yolo_output_layer_names)

    # Declare variables
    bounding_boxes = []
    class_ids = []
    confidence_scores = []
    # Higher confidence threshold means that the detections with confidence above threshold will be shown
    # Lower nms means that the threshold for overlapping bounding boxes is lowering meaning they filter out more
    confidence_threshold = 0.8
    nms_threshold = 0.3

    # Obtain bounding boxes of the detections that are over the threshold value
    is_one_detection_above_threshold = False
    for output in yolo_outputs:
        for detection in output:
            scores = detection[5:]
            class_id = np.argmax(scores)
            confidence = scores[class_id]

            if confidence > confidence_threshold:
                is_one_detection_above_threshold = True

                w, h = int(detection[2] * width), int(detection[3] * height)
                x, y = int((detection[0] * width) - w/2), int((detection[1] * height) - h/2)

                bounding_boxes.append([x, y, w, h])
                class_ids.append(class_id)
                confidence_scores.append(float(confidence))

    # Determines indices to keep by filtering overlapping bounding boxes with the same object against the threshold
    indices_to_keep = cv2.dnn.NMSBoxes(bounding_boxes, confidence_scores, confidence_threshold, nms_threshold)

    # Filter detections based on indices to keep
    temp_bounding_boxes = []
    class_names = []
    temp_confidence_scores = []
    for index in indices_to_keep:
        index = index[0]

        # Filter detections by classes
        if yolo_class_names[class_ids[index]] != 'car' and yolo_class_names[class_ids[index]] != 'truck':
            continue

        box = bounding_boxes[index]

        x, y, w, h = box[0], box[1], box[2], box[3]

        temp_bounding_boxes.append([x, y, w, h])

        # Get class ids corresponding class name
        temp_class_name = yolo_class_names[class_ids[index]].upper()
        class_names.append(temp_class_name)

        # Convert confidence score to percentage out of 100
        temp_confidence_scores.append(confidence_scores[index] * 100)

    bounding_boxes = temp_bounding_boxes
    confidence_scores = temp_confidence_scores

    # Convert bounding boxes to [TL, BR] format
    for i in range(len(bounding_boxes)):
        bounding_boxes[i][2] = bounding_boxes[i][0] + bounding_boxes[i][2]
        bounding_boxes[i][3] = bounding_boxes[i][1] + bounding_boxes[i][3]

        bounding_boxes[i] = [[bounding_boxes[i][0], bounding_boxes[i][1]], [bounding_boxes[i][2], bounding_boxes[i][3]]]

    return is_one_detection_above_threshold, class_names, bounding_boxes, confidence_scores

def CreateInvertedMask(img, bbox):
    # Takes a full image and the bounding box of the object of interest within it.
    # Returns the inverted mask of the object contained within the bounding box

    # Increase bounding box
    increased_bbox = IU.GetIncreasedBB(img_dimensions=img.shape[:2],
                                       bbox=bbox)

    # Get a cropped image based on the increased bounding box
    increased_bbox_img = IU.CropImage(img=img,
                                      bounding_set=increased_bbox)

    # Get the bounding box points in respect to increased bounding box (irt = in respect to)
    bbox_irt_increased_bbox = IU.GetBBInRespectTo(bbox=bbox,
                                                  bbox_of_new_parent=increased_bbox)

    # Create a black mask based on the cropped image dimensions
    mask = np.zeros(increased_bbox_img.shape[:2], dtype=np.uint8)

    # Create bg and fg standard model arrays. These are for grabcut's internal use.
    bg_model = np.zeros((1, 65), np.float64)
    fg_model = np.zeros((1, 65), np.float64)

    # Apply grabcut to image
    # Convert bb to opencv format
    increased_bbox_converted = [bbox_irt_increased_bbox[0][0],
                                bbox_irt_increased_bbox[0][1],
                                bbox_irt_increased_bbox[1][0],
                                bbox_irt_increased_bbox[1][1]]

    # mask outputs 0 (definite bg), 1 (definite fg), 2 (probable bg), 3 (probable fg)
    (mask, _, _) = cv2.grabCut(img=increased_bbox_img,
                               mask=mask,
                               rect=increased_bbox_converted,
                               bgdModel=bg_model,
                               fgdModel=fg_model,
                               iterCount=1,
                               mode=cv2.GC_INIT_WITH_RECT)

    # Define output mask based on mask output values from grabcut
    output_mask = (np.where((mask == cv2.GC_BGD) | (mask == cv2.GC_PR_BGD), 1, 0)*255).astype("uint8")
    cv2.imshow("EEE", increased_bbox_img)
    return output_mask

def IsCarInParkingBB(parking_bounding_box, car_bounding_box):
    #takes two full bounding boxes
    #uses the TL, BR points to get intersection, area and IoU

    acceptable_threshold = 0.6

    #get x,y coordinates of TL, BR
    xA = max(parking_bounding_box[0][0], car_bounding_box[0][0])                                                #A = [[283, 330], [346,329], [309, 365], [381,360]]                                                                                #B = [[282, 331], [347, 330], [307, 366], [379, 364]]
    yA = max(parking_bounding_box[0][1], car_bounding_box[0][1])

    xB = min(parking_bounding_box[3][0], car_bounding_box[3][0])
    yB = min(parking_bounding_box[3][1], car_bounding_box[3][1])

    #check whether intersection exists first
    if (xA > xB or yA > yB):
        print ("No intersection")
        return -1

    #get area of intersecting width and height
    intersecting_area = max(0, xB - xA) * max(0, yB - yA)

    #get the area of both bounding boxes separately
    parking_bounding_box_area = (parking_bounding_box[3][0] - parking_bounding_box[0][0]) * (parking_bounding_box[3][1] - parking_bounding_box[0][1])
    car_bounding_box_area = (car_bounding_box[3][0] - car_bounding_box[0][0]) * (car_bounding_box[3][1] - car_bounding_box[0][1])

    #calculate IoU
    #union = area - intersecting area
    iou = intersecting_area / float(parking_bounding_box_area + car_bounding_box_area - intersecting_area)

    # return the intersection over union value
    print(iou)

    if (iou > acceptable_threshold):
        return True
    else:
        return False

def IsCarInParkingBBN(parking_bounding_box, car_bounding_box):
    # Takes 2 bounding boxes, one for the car, one for the parking spot
    # It should be noted that the parking bounding box must be in the format [TL, TR, BL, BR] while the car box should
    # be in the format of [TL, BR]
    # Returns true if overlapping, false otherwise

    acceptable_threshold = 0.08

    # Define each polygon
    temp_parking_bb = [parking_bounding_box[0], parking_bounding_box[1], parking_bounding_box[3], parking_bounding_box[2]]
    temp_car_bb = IU.GetFullBoundingBox(car_bounding_box)
    temp_car_bb = [temp_car_bb[0], temp_car_bb[1], temp_car_bb[3], temp_car_bb[2]]
    polygon1_shape = Polygon(temp_parking_bb)
    polygon2_shape = Polygon(temp_car_bb)

    # Calculate intersection and union, and the IOU
    polygon_intersection = polygon1_shape.intersection(polygon2_shape).area
    polygon_union = polygon1_shape.area + polygon2_shape.area - polygon_intersection

    iou = polygon_intersection / polygon_union

    if (iou > acceptable_threshold):
        return True
    else:
        return False

# EXPERIMENTAL CODE

class SubtractionModel:
    # As this form of object detection builds on the output of a stationary camera, it is essentially tied to that
    # camera. Due to this, we must treat it as a class of its own so that we may create multiple copies of it to be
    # used on a per stationary camera basis.

    def __init__(self, detectShadows=False, history=100):
        # Initialize subtraction object detection model

        self.subtraction_model = cv2.createBackgroundSubtractorMOG2(history=history)
        self.subtraction_model.setDetectShadows(detectShadows)
        self.subtraction_model_output_mask = 0

    def FeedSubtractionModel(self, image, learningRate=-1):
        # This function is to be called once per iteration if DetectMovingObjects is being used.

        self.subtraction_model_output_mask = self.subtraction_model.apply(image, learningRate=learningRate)

        # Filter out shadows
        # _, self.subtraction_model_output_mask = cv2.threshold(self.subtraction_model_output_mask, 254, 255, cv2.THRESH_BINARY)

    def DetectMovingObjects(self, area_threshold=100):
        # Detect objects that have moved within the image.
        # Returns the bounding boxes of all the objects detected.
        # It is to be noted that if this function is to be used, FeedSubtractionModel must be called at least once beforehand

        contours, _ = cv2.findContours(self.subtraction_model_output_mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

        bounding_boxes = []
        for contour in contours:
            area = cv2.contourArea(contour)

            if area > area_threshold:
                x, y, w, h = cv2.boundingRect(contour)

                bounding_boxes.append([x, y, w, h])

        # Convert bounding boxes to [TL, BR] format
        for i in range(len(bounding_boxes)):
            bounding_boxes[i][2] = bounding_boxes[i][0] + bounding_boxes[i][2]
            bounding_boxes[i][3] = bounding_boxes[i][1] + bounding_boxes[i][3]

            bounding_boxes[i] = [[bounding_boxes[i][0], bounding_boxes[i][1]],
                                 [bounding_boxes[i][2], bounding_boxes[i][3]]]

        self.subtraction_model_output_mask = IU.DrawBoundingBoxes(self.subtraction_model_output_mask, bounding_boxes)
        cv2.imshow("Subtraction Mask", self.subtraction_model_output_mask)

        return bounding_boxes

    def GetOutput(self):
        return self.subtraction_model_output_mask


import keyboard  # using module keyboard

from Crafter import Crafter
from Disassembler import Disassembler

if __name__ == '__main__':
    clicker = Crafter(delay=.85)
    presser = Disassembler(delay=.85, key='z', confirm='tab')
    keyboard.add_hotkey('f6', clicker.toggle)
    keyboard.add_hotkey('f7', presser.toggle)
    clicker.start()
    presser.start()
    print("Ready to go!")
    while True:
        continue


class Solution:
    def lengthOfLongestSubstring(self, s):
        """
        :type s: str
        :rtype: int
        """
        mmap = dict()
        loc = 0
        max_len = 0

        rstart = 0
        rend = 0

        while loc < len(s):
            if s[loc] in mmap.keys():
                if len(mmap) > max_len:
                    max_len = len(mmap)
                    rend = loc - 1

                loc = mmap[s[loc]]
                mmap.clear()
            else:
                mmap[s[loc]] = loc

            loc += 1

        if max_len < len(mmap):
            max_len = len(mmap)
            rend = len(s) - 1

        for ri in range(0, max_len):
            print(s[rend - (max_len - ri - 1)])

        return max_len

if __name__ == '__main__':
    solution = Solution()
    print(solution.lengthOfLongestSubstring('abbce'))


#!/usr/bin/python3
"""
A program that solves the N queens problem.

The N queens puzzle is the challenge of placing
N non-attacking queens on an N×N chessboard.
"""

import sys

def solution():
    """
    Solution to the N-queens prob
    """

    if len(sys.argv) != 2:
        print("Usage: nqueens N")
        sys.exit(1)

    N = sys.argv[1]
    try:
        N = int(N)
    except ValueError:
        print('N must be a number')
        sys.exit(1)

    if N < 4:
        print('N must be at least 4')
        sys.exit(1)

    board = [-1] * N
    place_queens(board, 0)

def place_queens(board, row):
    """
    Place the queens
    """

    N = len(board)

    if row == N:
        print_solution(board)
        return

    for col in range(N):
        if is_safe(board, row, col):
            board[row] = col
            place_queens(board, row + 1)

def is_safe(board, row, col):
    """
    Is it safe to print and fill row
    """

    for i in range(row):
        if board[i] == col:
            return False
        if board[i] - i == col - row or board[i] + i == col + row:
            return False
    return True

def print_solution(board):
    """
    Print the solution
    """

    sol = []
    for i, col in enumerate(board):
        sol.append([i, col])
    print(sol)

if __name__ == '__main__':
    solution()


##################### PROBLEM 3 ##########

"""

Write a Python Program that prints the reversed version of a string.

The program must preserve uppercase and lowercase letters.

If the string is empty, print it intact.

"""
"""

OUTPUT
 "Hello" --> Print out  "olleh"
 "Woo"  --> Print out "oow"
 ""   --> ""
"""

######## OPTION ONE SOLUTIONS ####################

from audioop import reverse

s= "hello"

#  Solving using String slicing
print(s[::-1])  # strings = start:stop:step indications
                # string =            :: step means returns data in reverse directions

################# OPTION TWO SOLUTIONS ##################

string="Amazing solution"

# Assign reverse values in certain variable

reverse_string= string[::-1]
print(reverse_string)

################# OPTION THREE SOLUTIONS ##################

# Reverse string will join built function

stringr_join="Delicious"

# return reversed("hello")--> return ["o","l","l","e","h"]
# join built fn come to action "".join(["o","l","l","e","h"])--> return "olleh"

reverse_stringr_join="".join(reversed(stringr_join))

print(reverse_stringr_join)


# coding:utf-8

import math
from functools import reduce
from pathlib import Path
from operator import itemgetter
import numpy as np
from numpy.linalg import inv

def get_coordinates(file_name):
    """获取点的坐标, 返回一个list
    """
    with open(file_name) as fp:
        coordinates = fp.readlines()
        coordinates = list(filter(lambda x: x, [coord.strip('\n') for coord in coordinates]))  # noqa
        coordinates = [eval(c) for c in coordinates]
        return coordinates

def get_distance(x, y, x0, y0):
    """
    计算(x0, y0)到(x, y)之间的距离
    """
    return math.sqrt(pow(x - x0, 2) + pow(y - y0, 2))

def write(out_file, result_coords):
    Path(out_file).parent.mkdir(exist_ok=True)
    with open(out_file, 'w') as f:
        f.writelines((str(i) + '\n' for i in result_coords))

def main(yizhi_file, weizhi_file, out_file):
    """反距离权重插值法"""
    ori_points = get_coordinates(yizhi_file)
    z_points = get_coordinates(weizhi_file)
    result_dict = {}
    for x, y in z_points:
        distances = []
        member = 0         # 分子
        for x0, y0, z0 in ori_points:
            distance = get_distance(x, y, x0, y0)
            distances.append(distance)
            member += z0 / distance

        denominator = reduce(lambda x, y: x + y, (1 /(i * i) for i in distances))       # 分母   # noqa
        z = member / denominator
        result_dict[(x, y)] = z         # z坐标
    result_coords = []
    for (x, y), z in result_dict.items():
        result_coords.append((x, y, z))
    write(out_file, result_coords)                 # 写入

def main2(yizhi_file, weizhi_file, out_file):
    """最邻近插值法"""
    ori_points = get_coordinates(yizhi_file)
    z_points = get_coordinates(weizhi_file)
    distances = {}
    result_dict = {}
    for x, y in z_points:
        for x0, y0, z0 in ori_points:
            distance = get_distance(x, y, x0, y0)
            distances[z0] = distance
        z = min(zip(distances.values(), distances.keys()))[1]
        result_dict[(x, y)] = z
    result_coords = []
    for (x, y), z in result_dict.items():
        result_coords.append((x, y, z))
    write(out_file, result_coords)

def main3(yizhi_file, weizhi_file, out_file):
    """线性内插法"""
    ori_points = get_coordinates(yizhi_file)
    z_points = get_coordinates(weizhi_file)
    result_dict = {}
    for x, y in z_points:
        # 取最近三个点
        distance_coord = {get_distance(x, y, x0, y0): (x0, y0, z0) for x0, y0, z0 in ori_points}    # noqa
        # 最近三个点(最近定为(x1, y1))
        [(_, _, z1), (x2, y2, z2), (x3, y3, z3)] = [item[1] for item in sorted(distance_coord.items(), key=itemgetter(0))[:3]]  # noqa
        a = 1 / (x2 * y3 - x3 * y2)
        b1 = x2 * y3 - x3 * y2
        b2 = (y2 - y3) * x
        b3 = (x3 - x2) * y
        c1 = (b1 + b2 + b3) * z1
        c2 = (y3 * x - x3 * y) * z2
        c3 = (x2 * y - y2 * x) * z3
        z = a * (c1 + c2 + c3)
        result_dict[(x, y)] = z
    result_coords = []
    for (x, y), z in result_dict.items():
        result_coords.append((x, y, z))
    write(out_file, result_coords)

def main4(yizhi_file, weizhi_file, out_file, n=6):
    """移动曲面拟合法"""
    ori_points = get_coordinates(yizhi_file)
    z_points = get_coordinates(weizhi_file)
    result_dict = {}
    if n > len(ori_points):
        print('已知点没有{}个!!'.format(n))
        return
    for x, y in z_points:
        # 取最近n个点
        distance_coord = {get_distance(x, y, x0, y0): (x0, y0, z0) for x0, y0, z0 in ori_points}  # noqa
        # 最近n个点的list
        list_n = [item[1] for item in sorted(distance_coord.items(), key=itemgetter(0))[:n]]  # noqa
        # 求权(1/距离的平方)
        list_p = [1 / (pow(x0 - x, 2) + pow(y0 - y, 2)) for x0, y0, _ in list_n]  # noqa
        # 将list转换为对角矩阵
        p = np.diag(np.array(list_p))
        # 已知点构造出的z坐标构造出一个n * 1的矩阵
        z0 = np.array([y0 for _, _, y0 in list_n]).reshape(n, 1)
        # 求M
        list_m = []
        for x0, y0, _ in list_n:
            # d = math.sqrt(pow(x - x0, 2), pow(y - y0, 2))
            d_x = x0 - x
            d_y = y0 - y
            list_m += [pow(d_x, 2), d_x * d_y, pow(d_y, 2), d_x, d_y, 1]
        m = np.array(list_m).reshape(n, 6)

        # 求结果矩阵
        result = inv(m.T.dot(p).dot(m)).dot(m.T).dot(p).dot(z0)
        # 取最后一个
        z = float(list(result)[-1])

        result_dict[(x, y)] = z

    result_coords = []
    for (x, y), z in result_dict.items():
        result_coords.append((x, y, z))
    write(out_file, result_coords)

def main5(yizhi_file, weizhi_file, out_file):
    """双线性多项式内插法"""
    ori_points = get_coordinates(yizhi_file)
    z_points = get_coordinates(weizhi_file)
    result_dict = {}
    if 4 > len(ori_points):
        print('已知点没有{}个!!'.format(4))
        return
    for x, y in z_points:
        # 取最近n个点
        distance_coord = {get_distance(x, y, x0, y0): (x0, y0, z0) for x0, y0, z0 in ori_points}  # noqa
        # 最近n个点的list
        list_n = [item[1] for item in sorted(distance_coord.items(), key=itemgetter(0))[:4]]  # noqa
        [(x1, y1, z1), (x2, y2, z2), (x3, y3, z3), (x4, y4, z4)] = list_n
        # 已知点的x坐标矩阵
        z0 = np.array([z1, z2, z3, z4]).reshape(4, 1)

        list_xy = []
        for x0, y0, _ in list_n:
            list_xy += [1, x0, y0, x0 * y0]
        matrix_tem = np.array(list_xy).reshape(4, 4)
        # 求a的矩阵
        matrix_a = np.dot(inv(matrix_tem), z0)
        # 求a0, a1, a2, a3
        a0, a1, a2, a3 = [float(i) for i in list(matrix_a)]

        # 求z
        z = a0 + a1 * x + a2 * y + a3 * x * y
        result_dict[(x, y)] = z

    result_coords = []
    for (x, y), z in result_dict.items():
        result_coords.append((x, y, z))
    write(out_file, result_coords)

if __name__ == '__main__':
    main(r'D:\test\已知点.txt', r'D:\test\未知点.txt', r'D:\test\result1.txt')
    main2(r'D:\test\已知点.txt', r'D:\test\未知点.txt', r'D:\test\result2.txt')
    main3(r'D:\test\已知点.txt', r'D:\test\未知点.txt', r'D:\test\result3.txt')
    main4(r'D:\test\已知点.txt', r'D:\test\未知点.txt', r'D:\test\result4.txt')
    main5(r'D:\test\已知点.txt', r'D:\test\未知点.txt', r'D:\test\result5.txt')


import re
import json
import psycopg2

class NumericRangeEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, psycopg2.extras.NumericRange):
            return [
                    None if o.lower is None else o.lower if o.lower_inc else o.lower + 1,
                    None if o.upper is None else o.upper if o.upper_inc else o.upper - 1
                    ]
        return json.JSONEncoder.default(self, o)

geoloc = re.compile(R"\((.*),\s*(.*)\)", re.ASCII)
def parse_geoloc(s):
    if s is None:
        return None
    else:
        m = geoloc.fullmatch(s)
        return dict(lat=float(m[1]), lng=float(m[2]))

def parse_place(place):
    d = dict(id=place.id, location_confidence=place.location_confidence, name=place.name, place_type=place.place_type)
    d['geoloc'] = parse_geoloc(place.geoloc)

    return d

def format_geoloc(g):
    return None if g is None else F'({g["lat"]},{g["lng"]})'

def parse_evidence(record):
    d = dict(
            tuple_id=record.tuple_id,
            place_id=record.place_id,
            religion_id=record.religion_id,
            time_confidence=record.time_confidence,
            location_confidence=record.location_confidence,
            place_attribution_confidence=record.place_attribution_confidence,
            source_confidences=record.source_confidences,
            interpretation_confidence=record.interpretation_confidence,
            religion_confidence=record.religion_confidence,
            source_ids=record.source_ids
            )
    ts = record.time_span
    start_time = None if ts is None else ts.lower if (ts.lower is None or ts.lower_inc) else ts.lower+1
    end_time = None if ts is None else ts.upper if (ts.upper is None or ts.upper_inc) else ts.upper-1
    d['time_span'] = dict(start=start_time, end=end_time)

    return d

def istime(t):
    if isinstance(t, psycopg2.extras.NumericRange):
        if t.isempty:
            return False

        if t.lower_inf and t.upper_inf:
            return False

        return True

    return False


from collections import OrderedDict
from copy import copy, deepcopy
import numbers

from bson import ObjectId

import eta.core.utils as etau

import fiftyone.core.aggregations as foa
import fiftyone.core.collections as foc
import fiftyone.core.media as fom
import fiftyone.core.odm as foo
import fiftyone.core.sample as fos
import fiftyone.core.stages as fost

class DatasetView(foc.SampleCollection):
    """A view into a :class:`fiftyone.core.dataset.Dataset`.

    Dataset views represent ordered collections of subsets of samples in a
    dataset.

    Operations on dataset views are designed to be chained together to yield
    the desired subset of the dataset, which is then iterated over to directly
    access the sample views. Each stage in the pipeline defining a dataset view
    is represented by a :class:`fiftyone.core.stages.ViewStage` instance.

    The stages of a dataset view specify:

    -   what subset of samples (and their order) should be included
    -   what "parts" (fields and their elements) of the sample should be
        included

    Samples retrieved from dataset views are returns as
    :class:`fiftyone.core.sample.SampleView` objects, as opposed to
    :class:`fiftyone.core.sample.Sample` objects, since they may contain a
    subset of the sample's content.

    See https://voxel51.com/docs/fiftyone/user_guide/using_views.html for an
    overview of working with dataset views.

    Args:
        dataset: a :class:`fiftyone.core.dataset.Dataset`
    """

    def __init__(self, dataset, _stages=None):
        if _stages is None:
            _stages = []

        self.__dataset__ = dataset
        self._stages = _stages

    def __eq__(self, other_view):
        if not isinstance(other_view, DatasetView):
            return False

        if self._dataset != other_view._dataset:
            return False

        # Two views into the same dataset are equal if their stage definitions
        # are equal, excluding their UUIDs
        d = self._serialize(include_uuids=False)
        other_d = other_view._serialize(include_uuids=False)
        return d == other_d

    def __len__(self):
        return self.count()

    def __getitem__(self, id_filepath_slice):
        if isinstance(id_filepath_slice, numbers.Integral):
            raise KeyError(
                "Accessing samples by numeric index is not supported. "
                "Use sample IDs, filepaths, or slices"
            )

        if isinstance(id_filepath_slice, slice):
            return self._slice(id_filepath_slice)

        try:
            oid = ObjectId(id_filepath_slice)
            query = {"_id": oid}
        except:
            oid = None
            query = {"filepath": id_filepath_slice}

        view = self.match(query)

        try:
            return next(iter(view))
        except StopIteration:
            field = "ID" if oid is not None else "filepath"
            raise KeyError(
                "No sample found with %s '%s'" % (field, id_filepath_slice)
            )

    def __copy__(self):
        _stages = deepcopy(self._stages)
        return self.__class__(self._dataset, _stages=_stages)

    @property
    def _dataset(self):
        return self.__dataset__

    @property
    def media_type(self):
        """The media type of the underlying dataset."""
        return self._dataset.media_type

    @property
    def name(self):
        """The name of the view."""
        return self.dataset_name + "-view"

    @property
    def dataset_name(self):
        """The name of the underlying dataset."""
        return self._dataset.name

    @property
    def info(self):
        """The info dict of the underlying dataset.

        See :meth:`fiftyone.core.dataset.Dataset.info` for more information.
        """
        return self._dataset.info

    @info.setter
    def info(self, info):
        self._dataset.info = info

    @property
    def classes(self):
        """The classes of the underlying dataset.

        See :meth:`fiftyone.core.dataset.Dataset.classes` for more information.
        """
        return self._dataset.classes

    @classes.setter
    def classes(self, classes):
        self._dataset.classes = classes

    @property
    def default_classes(self):
        """The default classes of the underlying dataset.

        See :meth:`fiftyone.core.dataset.Dataset.default_classes` for more
        information.
        """
        return self._dataset.default_classes

    @default_classes.setter
    def default_classes(self, classes):
        self._dataset.default_classes = classes

    @property
    def mask_targets(self):
        """The mask targets of the underlying dataset.

        See :meth:`fiftyone.core.dataset.Dataset.mask_targets` for more
        information.
        """
        return self._dataset.mask_targets

    @mask_targets.setter
    def mask_targets(self, targets):
        self._dataset.mask_targets = targets

    @property
    def default_mask_targets(self):
        """The default mask targets of the underlying dataset.

        See :meth:`fiftyone.core.dataset.Dataset.default_mask_targets` for more
        information.
        """
        return self._dataset.default_mask_targets

    @default_mask_targets.setter
    def default_mask_targets(self, targets):
        self._dataset.default_mask_targets = targets

    @property
    def stages(self):
        """The list of :class:`fiftyone.core.stages.ViewStage` instances in
        this view's pipeline.
        """
        return self._stages

    def summary(self):
        """Returns a string summary of the view.

        Returns:
            a string summary
        """
        aggs = self.aggregate(
            [foa.Count(), foa.Distinct("tags")], _attach_frames=False
        )
        elements = [
            "Dataset:        %s" % self.dataset_name,
            "Media type:     %s" % self.media_type,
            "Num samples:    %d" % aggs[0],
            "Tags:           %s" % aggs[1],
            "Sample fields:",
            self._dataset._to_fields_str(self.get_field_schema()),
        ]

        if self.media_type == fom.VIDEO:
            elements.extend(
                [
                    "Frame fields:",
                    self._dataset._to_fields_str(
                        self.get_frame_field_schema()
                    ),
                ]
            )

        elements.extend(["View stages:", self._make_view_stages_str()])

        return "\n".join(elements)

    def _make_view_stages_str(self):
        if not self._stages:
            return "    ---"

        return "    " + "\n    ".join(
            [
                "%d. %s" % (idx, str(stage))
                for idx, stage in enumerate(self._stages, 1)
            ]
        )

    def view(self):
        """Returns a copy of this view.

        Returns:
            a :class:`DatasetView`
        """
        return copy(self)

    def iter_samples(self):
        """Returns an iterator over the samples in the view.

        Returns:
            an iterator over :class:`fiftyone.core.sample.SampleView` instances
        """
        selected_fields, excluded_fields = self._get_selected_excluded_fields()
        filtered_fields = self._get_filtered_fields()

        for d in self._aggregate(detach_frames=True):
            try:
                doc = self._dataset._sample_dict_to_doc(d)
                sample = fos.SampleView(
                    doc,
                    self,
                    selected_fields=selected_fields,
                    excluded_fields=excluded_fields,
                    filtered_fields=filtered_fields,
                )

                yield sample
            except Exception as e:
                raise ValueError(
                    "Failed to load sample from the database. This is likely "
                    "due to an invalid stage in the DatasetView"
                ) from e

    def get_field_schema(
        self, ftype=None, embedded_doc_type=None, include_private=False
    ):
        """Returns a schema dictionary describing the fields of the samples in
        the view.

        Args:
            ftype (None): an optional field type to which to restrict the
                returned schema. Must be a subclass of
                :class:`fiftyone.core.fields.Field`
            embedded_doc_type (None): an optional embedded document type to
                which to restrict the returned schema. Must be a subclass of
                :class:`fiftyone.core.odm.BaseEmbeddedDocument`
            include_private (False): whether to include fields that start with
                ``_`` in the returned schema

        Returns:
             an ``OrderedDict`` mapping field names to field types
        """
        field_schema = self._dataset.get_field_schema(
            ftype=ftype,
            embedded_doc_type=embedded_doc_type,
            include_private=include_private,
        )

        return self._get_filtered_schema(field_schema)

    def get_frame_field_schema(
        self, ftype=None, embedded_doc_type=None, include_private=False
    ):
        """Returns a schema dictionary describing the fields of the frames of
        the samples in the view.

        Only applicable for video datasets.

        Args:
            ftype (None): an optional field type to which to restrict the
                returned schema. Must be a subclass of
                :class:`fiftyone.core.fields.Field`
            embedded_doc_type (None): an optional embedded document type to
                which to restrict the returned schema. Must be a subclass of
                :class:`fiftyone.core.odm.BaseEmbeddedDocument`
            include_private (False): whether to include fields that start with
                ``_`` in the returned schema

        Returns:
            a dictionary mapping field names to field types, or ``None`` if
            the dataset is not a video dataset
        """
        field_schema = self._dataset.get_frame_field_schema(
            ftype=ftype,
            embedded_doc_type=embedded_doc_type,
            include_private=include_private,
        )

        return self._get_filtered_schema(field_schema, frames=True)

    def clone_sample_field(self, field_name, new_field_name):
        """Clones the given sample field of the view into a new field of the
        dataset.

        You can use dot notation (``embedded.field.name``) to clone embedded
        fields.

        .. note::

            This method is not a :class:`fiftyone.core.stages.ViewStage`;
            it immediately writes the requested changes to the underlying
            dataset.

        .. warning::

            If ``new_field_name`` is an embedded field, be aware that this
            operation will save the entire top-level field of
            ``new_field_name`` after performing the clone, which may result in
            data modification/loss if this view modifies this field in any
            other ways.

        Args:
            field_name: the field name or ``embedded.field.name``
            new_field_name: the new field name or ``embedded.field.name``
        """
        self._dataset._clone_sample_fields(
            {field_name: new_field_name}, view=self
        )

    def clone_sample_fields(self, field_mapping):
        """Clones the given sample fields of the view into new fields of the
        dataset.

        You can use dot notation (``embedded.field.name``) to clone embedded
        fields.

        .. note::

            This method is not a :class:`fiftyone.core.stages.ViewStage`;
            it immediately writes the requested changes to the underlying
            dataset.

        .. warning::

            If any of the new field names to specify are embedded fields, be
            aware that this operation will save the entire top-level new
            fields after performing the clone, which may result in data
            modification/loss if this view modifies these fields in any other
            ways.

        Args:
            field_mapping: a dict mapping field names to new field names into
                which to clone each field
        """
        self._dataset._clone_sample_fields(field_mapping, view=self)

    def clone_frame_field(self, field_name, new_field_name):
        """Clones the frame-level field of the view into a new field.

        You can use dot notation (``embedded.field.name``) to clone embedded
        frame fields.

        Only applicable to video datasets.

        .. note::

            This method is not a :class:`fiftyone.core.stages.ViewStage`;
            it immediately writes the requested changes to the underlying
            dataset.

        .. warning::

            If ``new_field_name`` is an embedded field, be aware that this
            operation will save the entire top-level field of
            ``new_field_name`` after performing the clone, which may result in
            data modification/loss if this view modifies this field in any
            other ways.

        Args:
            field_name: the field name or ``embedded.field.name``
            new_field_name: the new field name or ``embedded.field.name``
        """
        self._dataset._clone_frame_fields(
            {field_name: new_field_name}, view=self
        )

    def clone_frame_fields(self, field_mapping):
        """Clones the frame-level fields of the view into new frame-level
        fields of the dataset.

        You can use dot notation (``embedded.field.name``) to clone embedded
        frame fields.

        Only applicable to video datasets.

        .. note::

            This method is not a :class:`fiftyone.core.stages.ViewStage`;
            it immediately writes the requested changes to the underlying
            dataset.

        .. warning::

            If any of the new field names to specify are embedded fields, be
            aware that this operation will save the entire top-level new
            fields after performing the clone, which may result in data
            modification/loss if this view modifies these fields in any other
            ways.

        Args:
            field_mapping: a dict mapping field names to new field names into
                which to clone each field
        """
        self._dataset._clone_frame_fields(field_mapping, view=self)

    def clear_sample_field(self, field_name):
        """Clears the values of the field from all samples in the view.

        The field will remain in the dataset's schema, and all samples in the
        view will have the value ``None`` for the field.

        You can use dot notation (``embedded.field.name``) to clear embedded
        fields.

        .. note::

            This method is not a :class:`fiftyone.core.stages.ViewStage`;
            it immediately writes the requested changes to the underlying
            dataset.

        .. warning::

            If the field name you specify is an embedded field, be aware that
            this operation will save the entire top-level field after clearing
            the field, which may result in data modification/loss if this view
            modifies the field in any other ways.

        Args:
            field_name: the field name or ``embedded.field.name``
        """
        self._dataset._clear_sample_fields(field_name, view=self)

    def clear_sample_fields(self, field_names):
        """Clears the values of the fields from all samples in the view.

        The fields will remain in the dataset's schema, and all samples in the
        view will have the value ``None`` for the fields.

        You can use dot notation (``embedded.field.name``) to clear embedded
        fields.

        .. note::

            This method is not a :class:`fiftyone.core.stages.ViewStage`;
            it immediately writes the requested changes to the underlying
            dataset.

        .. warning::

            If any of the field names you specify are embedded fields, be aware
            that this operation will save the entire top-level field after
            clearing the fields, which may result in data modification/loss if
            this view modifies these fields in any other ways.

        Args:
            field_names: the field name or iterable of field names
        """
        self._dataset._clear_sample_fields(field_names, view=self)

    def clear_frame_field(self, field_name):
        """Clears the values of the frame-level field from all samples in the
        view.

        The field will remain in the dataset's frame schema, and all frames in
        the view will have the value ``None`` for the field.

        You can use dot notation (``embedded.field.name``) to clear embedded
        frame fields.

        Only applicable to video datasets.

        .. note::

            This method is not a :class:`fiftyone.core.stages.ViewStage`;
            it immediately writes the requested changes to the underlying
            dataset.

        .. warning::

            If the field name you specify is an embedded field, be aware that
            this operation will save the entire top-level field after clearing
            the field, which may result in data modification/loss if this view
            modifies the field in any other ways.

        Args:
            field_name: the field name or ``embedded.field.name``
        """
        self._dataset._clear_frame_fields(field_name, view=self)

    def clear_frame_fields(self, field_names):
        """Clears the values of the frame-level fields from all samples in the
        view.

        The fields will remain in the dataset's frame schema, and all frames in
        the view will have the value ``None`` for the fields.

        You can use dot notation (``embedded.field.name``) to clear embedded
        frame fields.

        Only applicable to video datasets.

        .. note::

            This method is not a :class:`fiftyone.core.stages.ViewStage`;
            it immediately writes the requested changes to the underlying
            dataset.

        .. warning::

            If any of the field names you specify are embedded fields, be aware
            that this operation will save the entire top-level field after
            clearing the fields, which may result in data modification/loss if
            this view modifies these fields in any other ways.

        Args:
            field_names: the field name or iterable of field names
        """
        self._dataset._clear_frame_fields(field_names, view=self)

    def save(self, fields=None):
        """Overwrites the underlying dataset with the contents of the view.

        .. warning::

            This will permanently delete any omitted, filtered, or otherwise
            modified contents of the dataset.

        Args:
            fields (None): an optional field or list of fields to save. If
                specified, only these fields are overwritten
        """
        if etau.is_str(fields):
            fields = [fields]

        self._dataset._save(view=self, fields=fields)

    def clone(self, name=None):
        """Creates a new dataset containing only the contents of the view.

        Args:
            name (None): a name for the cloned dataset. By default,
                :func:`get_default_dataset_name` is used

        Returns:
            the new :class:`Dataset`
        """
        return self._dataset._clone(name=name, view=self)

    def list_indexes(self, include_private=False):
        """Returns the fields of the dataset that are indexed.

        Args:
            include_private (False): whether to include private fields that
                start with ``_``

        Returns:
            a list of field names
        """
        return self._dataset.list_indexes(include_private=include_private)

    def create_index(self, field_name, unique=False, sphere2d=False):
        """Creates an index on the given field.

        If the given field already has a unique index, it will be retained
        regardless of the ``unique`` value you specify.

        If the given field already has a non-unique index but you requested a
        unique index, the existing index will be dropped.

        Indexes enable efficient sorting, merging, and other such operations.

        Args:
            field_name: the field name or ``embedded.field.name``
            unique (False): whether to add a uniqueness constraint to the index
            sphere2d (False): whether the field is a GeoJSON field that
                requires a sphere2d index
        """
        self._dataset.create_index(
            field_name, unique=unique, sphere2d=sphere2d
        )

    def drop_index(self, field_name):
        """Drops the index on the given field.

        Args:
            field_name: the field name or ``embedded.field.name``
        """
        self._dataset.drop_index(field_name)

    def to_dict(self, rel_dir=None, frame_labels_dir=None, pretty_print=False):
        """Returns a JSON dictionary representation of the view.

        Args:
            rel_dir (None): a relative directory to remove from the
                ``filepath`` of each sample, if possible. The path is converted
                to an absolute path (if necessary) via
                ``os.path.abspath(os.path.expanduser(rel_dir))``. The typical
                use case for this argument is that your source data lives in
                a single directory and you wish to serialize relative, rather
                than absolute, paths to the data within that directory
            frame_labels_dir (None): a directory in which to write per-sample
                JSON files containing the frame labels for video samples. If
                omitted, frame labels will be included directly in the returned
                JSON dict (which can be quite quite large for video datasets
                containing many frames). Only applicable to video datasets
            pretty_print (False): whether to render frame labels JSON in human
                readable format with newlines and indentations. Only applicable
                to video datasets when a ``frame_labels_dir`` is provided

        Returns:
            a JSON dict
        """
        d = super().to_dict(
            rel_dir=rel_dir,
            frame_labels_dir=frame_labels_dir,
            pretty_print=pretty_print,
        )
        samples = d.pop("samples")  # hack so that `samples` is last in JSON
        d["stages"] = [s._serialize() for s in self._stages]
        d["samples"] = samples
        return d

    def _needs_frames(self):
        for stage in self._stages:
            if stage._needs_frames(self):
                return True

        return False

    def _pipeline(
        self,
        pipeline=None,
        attach_frames=True,
        detach_frames=False,
        frames_only=False,
    ):
        _view = self._dataset.view()
        _pipeline = []
        for stage in self._stages:
            _view = _view.add_stage(stage)
            _pipeline.extend(stage.to_mongo(_view))

        if pipeline is not None:
            _pipeline.extend(pipeline)

        if not attach_frames:
            attach_frames = self._needs_frames()

        return self._dataset._pipeline(
            pipeline=_pipeline,
            attach_frames=attach_frames,
            detach_frames=detach_frames,
            frames_only=frames_only,
        )

    def _aggregate(
        self,
        pipeline=None,
        attach_frames=True,
        detach_frames=False,
        frames_only=False,
    ):
        _pipeline = self._pipeline(
            pipeline=pipeline,
            attach_frames=attach_frames,
            detach_frames=detach_frames,
            frames_only=frames_only,
        )
        return foo.aggregate(self._dataset._sample_collection, _pipeline)

    @property
    def _doc(self):
        return self._dataset._doc

    def _serialize(self, include_uuids=True):
        return [
            stage._serialize(include_uuid=include_uuids)
            for stage in self._stages
        ]

    @staticmethod
    def _build(dataset, stage_dicts):
        view = dataset.view()
        for stage_dict in stage_dicts:
            stage = fost.ViewStage._from_dict(stage_dict)
            view = view.add_stage(stage)

        return view

    def _slice(self, s):
        if s.step is not None and s.step != 1:
            raise ValueError(
                "Unsupported slice '%s'; step is not supported" % s
            )

        _len = None

        start = s.start
        if start is not None:
            if start < 0:
                _len = len(self)
                start += _len

            if start <= 0:
                start = None

        stop = s.stop
        if stop is not None and stop < 0:
            if _len is None:
                _len = len(self)

            stop += _len

        if start is None:
            if stop is None:
                return self

            return self.limit(stop)

        if stop is None:
            return self.skip(start)

        return self.skip(start).limit(stop - start)

    def _add_view_stage(self, stage):
        stage.validate(self)

        view = copy(self)
        view._stages.append(stage)
        return view

    def _get_filtered_schema(self, schema, frames=False):
        selected_fields, excluded_fields = self._get_selected_excluded_fields(
            frames=frames
        )
        if selected_fields is not None:
            schema = OrderedDict(
                {fn: f for fn, f in schema.items() if fn in selected_fields}
            )

        if excluded_fields is not None:
            schema = OrderedDict(
                {
                    fn: f
                    for fn, f in schema.items()
                    if fn not in excluded_fields
                }
            )

        return schema

    def _get_selected_excluded_fields(self, frames=False):
        selected_fields = None
        excluded_fields = set()

        for stage in self._stages:
            _selected_fields = stage.get_selected_fields(self, frames=frames)
            if _selected_fields:
                if selected_fields is None:
                    selected_fields = set(_selected_fields)
                else:
                    selected_fields.intersection_update(_selected_fields)

            _excluded_fields = stage.get_excluded_fields(self, frames=frames)
            if _excluded_fields:
                excluded_fields.update(_excluded_fields)

        if selected_fields is not None:
            selected_fields.difference_update(excluded_fields)
            excluded_fields = None

        return selected_fields, excluded_fields

    def _get_missing_fields(self, frames=False):
        if frames:
            dataset_schema = self._dataset.get_frame_field_schema()
            view_schema = self.get_frame_field_schema()
        else:
            dataset_schema = self._dataset.get_field_schema()
            view_schema = self.get_field_schema()

        return set(dataset_schema.keys()) - set(view_schema.keys())

    def _get_filtered_fields(self):
        filtered_fields = set()
        for stage in self._stages:
            _filtered_fields = stage.get_filtered_list_fields()
            if _filtered_fields:
                filtered_fields.update(_filtered_fields)

        return filtered_fields


from django.contrib.auth.models import User
from rest_framework import routers, serializers, viewsets, decorators, response
from api.permissions import IsSelfOrSuperUser
from rest_framework.permissions import IsAuthenticated, AllowAny
# Serializers define the API representation.

class UserSerializer(serializers.HyperlinkedModelSerializer):

    class Meta:
        model = User
        fields = ('url', 'username', 'email', 'is_staff', 'first_name', 'last_name')
        partial = True

# ViewSets define the view behavior.

class UserViewSet(viewsets.ModelViewSet):

    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = (IsSelfOrSuperUser, )

    def list(self, request, *args, **kwargs):
        """
        List all users.

        **Notes:**

        * Requires authenticated user

        **Example usage:**

            import requests
            response = requests.get('/users/')

        **Example response:**

            [
              {
                "url": "http://192.168.99.100:8000/users/1/",
                "username": "admin",
                "email": "a@b.com",
                "is_staff": true,
                "first_name": "",
                "last_name": ""
              }
            ]

        ---
        responseMessages:
        - code: 403
          message: Not authenticated

        consumes:
            - application/json
        produces:
            - application/json
        """
        return super(UserViewSet, self).list(request, *args, **kwargs)

class HealthViewSet(viewsets.ViewSet):

    permission_classes = (AllowAny, )

    def list(self, request, format=None):

        ## make sure we can connect to the database
        all_statuses = []
        status = "up"

        db_status = self.__can_connect_to_db()

        all_statuses.append(db_status)

        if "down" in all_statuses:
            status = "down"

        data = {
            "data": {
                "explorer" : "/api-explorer",
            },
            "status": {
                "db": db_status,
                "status": status
            }
        }
        return response.Response(data)

    def __can_connect_to_db(self):
        try:
            user = User.objects.first()
            return "up"
        except Exception:
            return "down"

# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r'health', HealthViewSet, base_name='health')
router.register(r'users', UserViewSet)


import pytest
from freezegun import freeze_time
from datetime import datetime
import device  # noqa: F401
import gps

def roundCoords(coords, ndigits=0):
    return {
        'latitude': round(coords['latitude'], ndigits),
        'longitude': round(coords['longitude'], ndigits)
    }

class Test_getGPSCoords:
    @pytest.mark.parametrize('timeIntvl, expectedLat, expectedLng', [
        (0, 5, 10),
        (1, 15, 30),
        (2, 25, 50),
        (3, 7.5, 15),
        (3.99, -10, -20),
        (4, 5, 10),
        (5, 15, 30),
        (6, 25, 50),
        (7, 7.5, 15),
        (7.99, -10, -20)
    ])
    def test_timeIntvlInterpolation(self, timeIntvl, expectedLat, expectedLng):
        with freeze_time(datetime.fromtimestamp(gps.startTime + timeIntvl)):
            assert roundCoords(gps.getGPSCoords()) == {
                'latitude': round(expectedLat),
                'longitude': round(expectedLng)
            }