"""
******************
Orient Edges from
Structure Learning
******************

[1] Chickering. "Learning Equivalence Classes of Bayesian
Network Structues"
http://www.jmlr.org/papers/volume2/chickering02a/chickering02a.pdf

[2] Pellet and Elisseff, "Using Markov Blankets for Causal Structure Learning"
"""

__author__ = """Nicholas Cullen <ncullen.th@dartmouth.edu>"""

from pyBN.utils.independence_tests import mi_test

import itertools
from copy import copy

def orient_edges_MB(edge_dict, Mb, data, alpha):
	"""
	Orient edges from a Markov Blanket based on the rules presented
	in Margaritis' Thesis pg. 35. This method is used
	for structure learning algorithms that return/resolve
	a markov blanket - i.e. growshrink and iamb.

	Also, see [2] for good full pseudocode.

	# if there exists a variable Z in N(X)-N(Y)-{Y}
	# such that Y and Z are dependent given S+{X} for
	# all S subset of T, where
	# T is smaller of B(Y)-{X,Z} and B(Z)-{X,Y}

	Arguments
	---------
	*edge_dict* : a dictionary, where
		key = node and value = list
		of neighbors for key. Note: there
		MUST BE duplicates in edge_dict ->
		i.e. each edge should be in edge_dict
		twice since Y in edge_dict[X] and
		X in edge_dict[Y]

	*blanket* : a dictionary, where
		key = node and value = list of
		nodes in the markov blanket of node

	*data* : a nested numpy array

	*alpha* : a float
		Probability of Type II error.

	Returns
	-------
	*d_edge_dict* : a dictionary
		Dictionary of directed edges, so
		there are no duplicates

	Effects
	-------
	None

	Notes
	-----

	"""
	for X in edge_dict.keys():
		for Y in edge_dict[X]:
			nxy = set(edge_dict[X]) - set(edge_dict[Y]) - {Y}

			for Z in nxy:
				by = set(Mb[Y]) - {X} - {Z}
				bz = set(Mb[Z]) - {X} - {Y}
				T = min(by,bz)
				if len(T)>0:
					for i in range(len(T)):
						for S in itertools.combinations(T,i):
							cols = (Y,Z,X) + tuple(S)
							pval = mi_test(data[:,cols])
							if pval < alpha:
								if Y in edge_dict[X]:
									edge_dict[X].remove(Y)
							else:
								if Y in edge_dict[X]:
									edge_dict[Y].remove(X)
				else:
					cols = (Y,Z,X)
					pval = mi_test(data[:,cols])
					if pval < alpha:
						if Y in edge_dict[X]:
							edge_dict[X].remove(Y)
					else:
						if X in edge_dict[Y]:
							edge_dict[Y].remove(X)
	return edge_dict

def orient_edges_gs2(edge_dict, Mb, data, alpha):
	"""
	Similar algorithm as above, but slightly modified for speed?
	Need to test.
	"""
	d_edge_dict = dict([(rv,[]) for rv in edge_dict])
	for X in edge_dict.keys():
		for Y in edge_dict[X]:
			nxy = set(edge_dict[X]) - set(edge_dict[Y]) - {Y}
			for Z in nxy:
				if Y not in d_edge_dict[X]:
					d_edge_dict[X].append(Y) # SET Y -> X
				B = min(set(Mb[Y]) - {X} - {Z},set(Mb[Z]) - {X} - {Y})
				for i in range(len(B)):
					for S in itertools.combinations(B,i):
						cols = (Y,Z,X) + tuple(S)
						pval = mi_test(data[:,cols])
						if pval < alpha and X in d_edge_dict[Y]: # Y IS independent of Z given S+X
							d_edge_dict[Y].remove(X)
				if X in d_edge_dict[Y]:
					break
	return d_edge_dict

def orient_edges_CS(edge_dict, block_dict):
	"""
	Orient edges from Collider Sets. This is a little
	different than orienting edges from a markov blanket.
	The collider set-based algorithm is used for the
	Path-Condition (PC) algorithm.

	See [2] for	good full pseudocode.

	The orientation step will proceed by looking
	for sets of three variables {X, Y,Z} such that
	edges X - Z, Y - Z are in the graph by not the
	edge X - Y . Then, if Z not in block_dict[x][y] , it orients the
	edges from X to Z and from Y to Z creating a
	v-structure: X -> Z <- Y

	Arguments
	---------
	*edge_dict* : a dictionary, where
		key = vertex and value = list of its neighbors.
		NOTE: this is undirected, so the edges are duplicated.

	*block_dict* : a dictionary, where
		key = X, value = another dictionary where
		key = Y, value = set S such that I(X,Y|S)

	Returns
	-------
	*d_edge_dict* : an directed version of original edge_dict

	Effects
	-------
	None

	Notes
	-----
	"""
	d_edge_dict = dict([(rv,[]) for rv in edge_dict.keys()])
	for x in edge_dict.keys():
		for z in edge_dict[x]:
			for y in edge_dict[z]:
				if y!=x and x not in edge_dict[y] and y not in edge_dict[x]:
					if y in block_dict[x]:
						if z not in block_dict[x][y]:
							if z not in d_edge_dict[x]:
								d_edge_dict[x].append(z)
							if z not in d_edge_dict[y]:
								d_edge_dict[y].append(z)
						else:
							if x not in d_edge_dict[z]:
								d_edge_dict[z].append(x)
							if y not in d_edge_dict[z]:
								d_edge_dict[z].append(y)

	return d_edge_dict


from . import views
from django.urls import path
urlpatterns=[
             path('',views.index,name='home'),
             # path('navbar/',views.products,name=''),
             # path('categories/',views.categories,name='categories'),
             # path('products/',views.products_1,name=''),
             path('categories/''<int:cat_id>/',views.products,name='products'),
             # path('categories/''<int:cat_id>/<int:pd_id>/',views.order,name='order'),
             path('box/',views.box,name='box'),
             # path('update_details/',views.update_details,name=''),
             # path('form/',views.form,name=''),
             # path('add_product/',views.add_product,name=''),
             # path('user/',views.user_form,name=''),
             path('login/',views.log_in,name='login'),
             path('order/''<int:pd_id>/',views.order,name='order'),
             path('register/',views.create_user,name='register'),
             # path('customer/', views.customer, name='login'),
             ]


import pyfirmata
import time

board = pyfirmata.Arduino("COM4")
N = [2,3,5,7,6,4,8,9]

def DARKall():
    for i in range (len(N)):
        board.digital[N[i]].write(0)
DARKall()

def LIGHTall():
    for i in range (len(N)):
        board.digital[N[i]].write(1)

print ('ЗДАДАНИЕ №1 - lightUp')
print ('Свечение заданной лампочки заданное время.')
def lightUp(ledNumber, period):
    board.digital[N[ledNumber]].write(1)
    time.sleep(period)
    board.digital[N[ledNumber]].write(0)
ledNumber = int(input('Введи номер светодиода: '))
period = float(input('Время: '))
lightUp(ledNumber, period)
print()

print ('ЗДАДАНИЕ №2 - blink')
print ('Мигание заданной лампочки заданное кол-во раз.')
def blink(ledNumber, blinkCount, blinkPeriod):
    for i in range (blinkCount):
        board.digital[N[ledNumber]].write(1)
        time.sleep(blinkPeriod)
        board.digital[N[ledNumber]].write(0)
        time.sleep(blinkPeriod)
ledNumber = int(input('Введи номер светодиода: '))
blinkCount = int(input('Количество миганий: '))
blinkPeriod = float(input('Интервал: '))
blink(ledNumber, blinkCount, blinkPeriod)
print()

print ('ЗДАДАНИЕ №3 - runningLight')
print ('"Бегущий огонек" с заданного номера светодиода')
def runningLight(count, period):
    ledNumber = int(input('С какого светодиода начинать? '))
    while count>0:
        board.digital[N[ledNumber]].write(1)
        time.sleep(period)
        board.digital[N[ledNumber]].write(0)
        count = count-1
        ledNumber=ledNumber+1
        if ledNumber == 8:
            ledNumber = ledNumber-8
count = int(input('Сколько раз переключить светодиод? '))
period = float(input('Интервал: '))
runningLight(count, period)
print()

print ('ЗДАДАНИЕ №4 - runningDark')
print ('"Бегущий огонек" с заданного номера светодиода')
def runningDark(count, period):
    ledNumber = int(input('С какого светодиода начинать? '))
    LIGHTall()
    while count>0:
        board.digital[N[ledNumber]].write(0)
        time.sleep(period)
        board.digital[N[ledNumber]].write(1)
        count = count-1
        ledNumber=ledNumber+1
        if ledNumber == 8:
            ledNumber = ledNumber-8
count = int(input('Сколько раз переключить светодиод? '))
period = float(input('Интервал: '))
runningDark(count, period)
DARKall()
print()

print ('ЗДАДАНИЕ №5 - decToBinList')
def decToBinList(decNumber):
    a = bin(decNumber)
    b = a[2::]
    number = list(b)
    c = len(number)
    for i in range (8-c):
        number.insert(i, '0')
    print(number)
for i in range (8):
    board.digital[N[i]].write(0)
decNumber = int(input('Введите число от 0 до 255: '))
decToBinList(decNumber)
print()

print ('ЗДАДАНИЕ №6 - lightNumber')
print ('Светящееся бинарное число')
def lightNumber(number):
    a = bin(decNumber)
    b = a[2::]
    print(b)
    number = list(b)
    c = len(number)
    for i in range (8-c):
        number.insert(i, '0')
    print(number)
    for i in range (len(number)):
        if number[i] == '0':
            board.digital[N[i]].write(0)
        elif number[i] == '1':
            board.digital[N[i]].write(1)
decNumber = int(input('Введите число от 0 до 255: '))
lightNumber(decNumber)
DARKall()
print()

print ('ЗДАДАНИЕ №7 - runningPattern')
print ('"Бегущий узор" заданное число раз')
def runningPattern(pattern, direction):
    a = bin(pattern)
    b = a[2::]
    number = list(b)
    c = len(number)
    for i in range (8-c):
        number.insert(i, '0')
    print(number)
    if direction == 1:
        for i in range(count):
            number.insert(0, number[7])
            number.pop(8)
            for i in range (8):
                if number[i] == '0':
                    board.digital[N[i]].write(0)
                elif number[i] == '1':
                    board.digital[N[i]].write(1)
            time.sleep(period)
    elif direction == 0:
        for i in range(count):
            number.insert(8, number[0])
            number.pop(0)
            for i in range (8):
                if number[i] == '0':
                    board.digital[N[i]].write(0)
                elif number[i] == '1':
                    board.digital[N[i]].write(1)
            time.sleep(period)

pattern = int(input('Введите число от 0 до 255: '))
count = int(input('Сколько раз передвинуть? '))
period = float(input('Интервал: '))
direction = int(input('В какую сторону? (1-вправо; 0-влево) '))
runningPattern(pattern, direction)
DARKall()


import os
import sys
import csv
from typing import List, Dict, Any
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
import pandas as pd
from sklearn import preprocessing
from sklearn.cluster import KMeans

def read_playlist_names(input_playlist_names_path: str) -> List[str]:
    """
    Reads playlist names from headerless csv and returns a list of playlists.
    """
    playlist_names = []
    with open(input_playlist_names_path) as f:
        playlist_reader = csv.reader(
            f,
        )
        for row in playlist_reader:
            playlist_names.append(*row)
    return playlist_names

def get_songs(
    playlist_names: List[str], user: str, sp: spotipy.client.Spotify
) -> List[Dict[str, Any]]:
    """
    Expects environment variables for SPOTIPY_CLIENT_ID and
    SPOTIPY_CLIENT_SECRET from a user-created spotipy developer app.

    Loops over all user playlists in playlist_names and adds all songs to a
    songs list, requesting from spotify in batches of 50 songs, where each
    song is a dict containing various features and metadata.
    """
    playlists = sp.user_playlists(user)
    playlist_ids = []
    for playlist in playlists["items"]:
        if playlist["name"] in playlist_names:
            playlist_ids.append(playlist["id"])
    songs = []
    for playlist_id in playlist_ids:
        offset = 0
        while True:
            content = sp.user_playlist_tracks(
                user=user, playlist_id=playlist_id, limit=50, offset=offset
            )
            songs += content["items"]
            if content["next"] is not None:
                offset += 50
            else:
                break
        offset = 0
    return songs

def extract_features(
    songs: List[Dict[str, Any]], sp: spotipy.client.Spotify
) -> pd.DataFrame:
    """
    Extracts the audio features from the songs list using the spotipy API and
    returns a pandas dataframe of the song features.
    """
    song_metadata = {}
    ids = []
    for i in songs:
        track = i["track"]
        ids.append(track["id"])
        song_metadata[track["id"]] = {
            "name": track["name"],
            "artists": [x["name"] for x in track["artists"]],
        }
    index = 0
    audio_features = []
    # NOTE: If there are duplicate tracks, len(songs) != len(song_metadata)
    while index < len(song_metadata.keys()):
        # NOTE: I have no idea why but I had one track with no id...
        audio_features += sp.audio_features(
            [x for x in ids[index:index + 50] if x is not None]
        )
        index += 50
    features_list = []
    for features in audio_features:
        features_list.append(
            [
                song_metadata[features["id"]]["name"],
                song_metadata[features["id"]]["artists"],
                features["energy"],
                features["liveness"],
                features["tempo"],
                features["speechiness"],
                features["mode"],
                features["uri"],
            ]
        )
    return pd.DataFrame(
        features_list,
        columns=[
            "name",
            "artists",
            "energy",
            "liveness",
            "tempo",
            "speechiness",
            "mode",
            "uri",
        ],
    )

def scale_features(features: pd.DataFrame) -> pd.DataFrame:
    """
    Scales the audio features and returns a new dataframe adding the scaled
    features columns.
    """
    x = features.loc[
        :, ~features.columns.isin(["name", "artists", "uri"])
    ].values
    min_max_scaler = preprocessing.MinMaxScaler()
    features_scaled = min_max_scaler.fit_transform(x)
    features_scaled = pd.DataFrame(
        features_scaled,
        columns=[
            "energy_scaled",
            "liveness_scaled",
            "tempo_scaled",
            "speechiness_scaled",
            "mode_scaled",
        ],
    )
    return features.loc[:, ["name", "artists", "uri"]].join(features_scaled)

def find_clusters(
    features: pd.DataFrame,
    features_scaled: pd.DataFrame,
    n_clusters: int,
    max_iter: int,
) -> pd.DataFrame:
    """
    Creates clusters using KMeans clustering from the scaled audio features
    and adds columns of the clusters to the original features dataframe.
    """
    kmeans = KMeans(
        init="k-means++", n_clusters=n_clusters, max_iter=max_iter
    ).fit(
        features_scaled.loc[
            :, ~features_scaled.columns.isin(["name", "artists", "uri"])
        ]
    )
    features["kmeans"] = kmeans.labels_
    return features

# NOTE: Can't get write access from API for some reason so this is untested
def create_playlists(
    user: str,
    cluster_names: List[str],
    sp: spotipy.client.Spotify,
    public: bool = False,
    create: bool = False,
) -> None:
    """
    Loop over clusters in cluster_names and create playlists from user account.
    """
    if create:
        for cluster in cluster_names:
            sp.user_playlist_create(user, cluster, public)

# NOTE: Can't get write access from API for some reason so this is untested
def add_tracks_to_playlists(
    user: str,
    cluster_playlist_ids: List[str],
    clusters_df: pd.DataFrame,
    sp: spotipy.client.Spotify,
) -> None:
    """
    For each cluster in cluster_playlist_ids, upload all tracks for that
    cluster to the user account.
    """
    kmean_idx = 0
    for cluster_playlist_id in cluster_playlist_ids:
        tracks = clusters_df.query("kmeans=={kmean_idx}")["uri"]
        sp.user_playlist_add_tracks(
            user, cluster_playlist_id, tracks, position=None
        )
        kmean_idx += 1

if __name__ == "__main__":
    auth_manager = SpotifyClientCredentials()
    sp = spotipy.Spotify(auth_manager=auth_manager)
    playlist_names = read_playlist_names(sys.argv[2])
    songs = get_songs(playlist_names, sys.argv[1], sp)
    features = extract_features(songs, sp)
    features.to_csv(
        f"{os.getcwd()}/user_data/spotify_features.csv", index=False
    )
    features_scaled = scale_features(features)
    features_clustered = find_clusters(
        features, features_scaled, len(playlist_names), 500
    )
    features.to_csv(
        f"{os.getcwd()}/user_data/spotify_clusters.csv", index=False
    )


#!/usr/bin/env python3

import argparse, os, sys
from pathlib import Path
import re
import atexit

# Test if this is a PyInstaller executable or a .py file
if getattr(sys, 'frozen', False):
    IS_EXE = True
    PROG_FILE = sys.executable
    PROG_PATH = os.path.dirname(PROG_FILE)
    PATH = sys._MEIPASS
else:
    IS_EXE = False
    PROG_FILE = os.path.realpath(__file__)
    PROG_PATH = os.path.dirname(PROG_FILE)
    PATH = PROG_PATH

PROG_FILE_NAME = os.path.splitext(os.path.basename(PROG_FILE))[0]

# Get path that the command was called from
COMMAND_PATH = Path().absolute()

# Setup exit handler (used for cleanup)
job_complete = None
exit_funcs = list()
def exit_handler():
    global job_complete
    if len(exit_funcs) > 0:
        print()
        print("-------- CLEANUP --------")
        for f in exit_funcs:
            f()
    if job_complete != None: # defined at the very end only
        job_complete()
atexit.register(exit_handler)

# Parse arguments before running main program
def dir_path(string):
    if os.path.isdir(string):
        return string
    else:
        raise NotADirectoryError(string)

#TODO: Add usage examples
ap = argparse.ArgumentParser(description="Merge nested image directory into PDF with nested bookmarks.")
ap.add_argument("-i", "--input_dir", type=dir_path, required=True,
    help="path to nested image directory to merge")
ap.add_argument("-o", "--output_file", type=str, default=None,
    help="output file path ( defaults to [input_dir].pdf )")
ap.add_argument("-s", "--order_number_separator", type=str, default=None,
    help="the character used to separate the directory ordering numbers from the bookmark names ( like '.' or ')' )")
ap.add_argument("-n", "--no_pdf", action="store_true",
    help="just scan directory and print table of contents")
ap.add_argument("-p", "--purify", action="store", default=None, nargs="*", type=str,
    help="purify scanned B&W page ( greyscale, sharpen, threshold ), named sub-arguments: (sharpen|s) (threshold|t)")
ap.add_argument("-d", "--dpi", type=int, default=None,
    help="dots-per-inch of the input images")
ap.add_argument("-t", "--title", type=str, default=None,
    help="the PDF title ( defaults to the directory basename )")
ap.add_argument("-a", "--author", type=str, default=None,
    help="the PDF author")
ap.add_argument("-f", "--table_of_contents_format", action="store", default=None, nargs="*", type=str,
    help="formatting options for the table of contents, named sub-arguments: (break_limit|b) (number_prefix|p) (number_postfix|a) (indent|i)")
args = vars(ap.parse_args())

print()

# Resolve input dir into absolute path (relative to working directory!)
input_dir = args["input_dir"]
input_dir = os.path.realpath(input_dir)
if not os.path.isabs(input_dir):
    input_dir_split = input_dir.split(os.path.sep)
    if input_dir_split[0] == os.path.curdir:
        input_dir = os.path.sep.join(input_dir_split[1:])
    input_dir = os.path.join(COMMAND_PATH, input_dir)

input_dir_name = input_dir.strip(os.path.sep).split(os.path.sep)[-1]

# Get main directory
main_dir = os.path.sep.join(input_dir.rstrip(os.path.sep).split(os.path.sep)[:-1])

# Test if/which purify flavor is being used
if args["purify"] != None:
    purify = True
    purify_args = args["purify"]
else:
    purify = False
    purify_args = ()

# Do not purify if --no_pdf is set
if args["no_pdf"]:
    purify = False

# Parse purify sub-arguments (values)
if purify:
    # Defaults
    sharpen_factor = 2
    thresh_setting = 170

    for p_arg in purify_args:
        p_arg_split = p_arg.split("=")

        # Only allow [string]=[string]
        if len(p_arg_split) != 2:
            raise argparse.ArgumentTypeError("Invalid argument format. Use arg_name=arg_value.")

        # Get name and value separately
        p_arg_name, p_arg_value = [x.lower().strip() for x in p_arg_split]

        # Parse purify named sub-arguments
        if p_arg_name in ["sharpen", "s"]:
            # Test if it's a float and set
            worked = True
            try:
                sharpen_factor = float(p_arg_value)
            except(ValueError):
                worked = False

            # Test if it's greater than 0
            if sharpen_factor <= 0:
                worked = False

            if not worked:
                raise argparse.ArgumentTypeError("(--purify | -p) sharpness must be a float greater than 0.")
        elif p_arg_name in ["threshold", "t"]:
            # Test if it's a float and set
            worked = True
            try:
                thresh_setting = float(p_arg_value)
            except(ValueError):
                worked = False

            # Test if it's positive
            if thresh_setting < 0:
                worked = False

            # Test if it's <= 255
            if thresh_setting > 255:
                worked = False

            if not worked:
                raise argparse.ArgumentTypeError("(--purify | -p) threshold must be a positive float <= 255.")
        else:
            raise argparse.ArgumentTypeError("'{}' is not a valid option for (--purify | -p).".format(p_arg_name))

# Fuction to read the first line in a text file
def read_string_from_file(string_file_name):
    with open(string_file_name, 'r', encoding='utf-8') as f:
        result = f.read()
    return result.strip().split("\n")[0].strip()

# Function to make any string into a valid filename
def get_valid_filename(s):
    s = str(s).strip()
    return re.sub(r'(?u)[\/\\\:\*\?\"\<\>\|]', '_', s)

# Function to turn a path into the name after the os.path.extsep (even if no name)
def path_to_ext(path_in):
    path_in_path, path_in_filename = os.path.split(path_in)
    path_in_basename, path_in_ext = os.path.splitext(path_in_filename)
    path_in_ext = path_in_ext.lower()
    if path_in_ext == "":
        # No name, just extention
        path_in_ext = path_in_basename
    if len(path_in_ext) <= 0:
        # Must be a directory
        return None
    elif path_in_ext[0] != ".":
        # No actual extention
        return ""
    else:
        return path_in_ext

# Set file extentions to ignore
ignored_file_exts = [".ignore", ".db"]

# Set valid image extentions
page_exts = [".jpg", ".jpeg", ".png", ".gif"]

# Set rename extentions
rename_exts = [".name", ".title"]

# Set author extentions
author_exts = [".author"]

# Set DPI extentions
dpi_exts = [".dpi"]

# Set blank page extentions
blank_exts = [".blank"]
page_exts += blank_exts

metadata_file_exts = rename_exts + author_exts + dpi_exts
valid_exts = ignored_file_exts + page_exts + metadata_file_exts + blank_exts

# Get files in main input directory
input_dir_files = [str(p) for p in Path(input_dir).glob("*") if os.path.isfile(p)]

# Set/limit DPI
dpi_files = [p for p in input_dir_files if path_to_ext(p) in dpi_exts]
if len(dpi_files) > 1:
    raise argparse.ArgumentTypeError("Multiple DPI files found in the main directory! Please use at most 1.")
if args["dpi"] != None:
    pdf_dpi = args["dpi"]
    if len(dpi_files) > 0:
        print("[WARNING]: A DPI file exists in the main directory, but the --dpi argument overrides this.")
elif len(dpi_files) > 0:
    try:
        pdf_dpi = int(read_string_from_file(dpi_files[0]))
    except ValueError:
        argparse.ArgumentTypeError("DPI setting from DPI file must be a valid integer.")
else:
    pdf_dpi = int(300)
if (pdf_dpi < 72) or (pdf_dpi > 4800):
    raise argparse.ArgumentTypeError("DPI must be 72 <= DPI <= 4800. Current setting: '{}'".format(pdf_dpi))

# Set PDF title
title_files = [p for p in input_dir_files if path_to_ext(p) in rename_exts]
if len(title_files) > 1:
    raise argparse.ArgumentTypeError("Multiple title/name files found in the main directory! Please use at most 1.")
use_pdf_title = True
if args["title"] != None:
    if len(title_files) > 0:
        print("[WARNING]: A title/name file exists in the main directory, but the --title argument overrides this.")
    pdf_title = args["title"]
elif len(title_files) > 0:
    pdf_title = read_string_from_file(title_files[0])
else:
    pdf_title = input_dir_name
    use_pdf_title = False

# Set PDF author
author_files = [p for p in input_dir_files if path_to_ext(p) in author_exts]
if len(author_files) > 1:
    raise argparse.ArgumentTypeError("Multiple author files found in the main directory! Please use at most 1.")
if args["author"] != None:
    if len(author_files) > 0:
        print("[WARNING]: An author file exists in the main directory, but the --author argument overrides this.")
    pdf_author = args["author"].strip()
elif len(author_files) > 0:
    pdf_author = read_string_from_file(author_files[0])
else:
    pdf_author = ""

# Resolve output filename
if not args["no_pdf"]:
    if args["output_file"] == None:
        # Default to title
        pdf_title_safe_filename = get_valid_filename(pdf_title)
        output_file = os.path.join(main_dir, pdf_title_safe_filename) + os.path.extsep + "pdf"
    else:
        out_dir, out_name = os.path.split(args["output_file"])
        out_name_split = out_name.split(os.path.extsep)
        if len(out_name_split) >= 2:
            # There is an extension
            output_file = args["output_file"]
            if out_name_split[-1].lower() != "pdf":
                output_file += os.path.extsep + "pdf"
        else:
            # No extension provided
            output_file = args["output_file"] + os.path.extsep + "pdf"

    output_file = os.path.realpath(output_file)
    output_file_dir, output_file_name = os.path.split(output_file)

# Test if special ToC formatting is used
if args["table_of_contents_format"] != None:
    tocf_args = args["table_of_contents_format"]
else:
    tocf_args = ()

# Defaults
toc_line_break_limit = None
pagenum_pre = "Page #"
pagenum_post = "  "
ident_str = "--- "

# Parse ToC formatting sub arguments
for tocf_arg in tocf_args:
    tocf_arg_split = tocf_arg.split("=")

    # Only allow [string]=[string]
    if len(tocf_arg_split) != 2:
        raise argparse.ArgumentTypeError("Invalid argument format. Use arg_name=arg_value.")

    # Get name and value separately
    tocf_arg_name, tocf_arg_value = [x.lower() for x in tocf_arg_split]

    # Parse ToC formatting named sub-arguments
    if tocf_arg_name in ["break_limit", "b"]:
        # Test if it's an int and set
        worked = True
        try:
            toc_line_break_limit = int(tocf_arg_value.strip())
        except(ValueError):
            worked = False

        # Test if it's greater than min_toc_line_break_limit
        min_toc_line_break_limit = 10
        if toc_line_break_limit == 0:
            toc_line_break_limit = None
            workied=True
        elif toc_line_break_limit <= min_toc_line_break_limit:
            worked = False

        if not worked:
            raise argparse.ArgumentTypeError("(--break_limit | -b) length must be an integer greater than {}, or 0 for no limit.".format(min_toc_line_break_limit))
    elif tocf_arg_name in ["number_prefix", "p"]:
        pagenum_pre = tocf_arg_value
    elif tocf_arg_name in ["number_postfix", "a"]:
        pagenum_post = tocf_arg_value
    elif tocf_arg_name in ["indent", "i"]:
        ident_str = tocf_arg_value
    else:
        raise argparse.ArgumentTypeError("'{}' is not a valid option for (--break_limit | -b).".format(tocf_arg_name))

# Parse purify sub-arguments (values)
if purify:
    # Defaults
    sharpen_factor = 2
    thresh_setting = 170

    for p_arg in purify_args:
        p_arg_split = p_arg.split("=")

        # Only allow [string]=[string]
        if len(p_arg_split) != 2:
            raise argparse.ArgumentTypeError("Invalid argument format. Use arg_name=arg_value.")

        # Get name and value separately
        p_arg_name, p_arg_value = [x.lower().strip() for x in p_arg_split]

        # Parse purify named sub-arguments
        if p_arg_name in ["sharpen", "s"]:
            # Test if it's a float and set
            worked = True
            try:
                sharpen_factor = float(p_arg_value)
            except(ValueError):
                worked = False

            # Test if it's greater than 0
            if sharpen_factor <= 0:
                worked = False

            if not worked:
                raise argparse.ArgumentTypeError("(--purify | -p) sharpness must be a float greater than 0.")
        elif p_arg_name in ["threshold", "t"]:
            # Test if it's a float and set
            worked = True
            try:
                thresh_setting = float(p_arg_value)
            except(ValueError):
                worked = False

            # Test if it's positive
            if thresh_setting < 0:
                worked = False

            # Test if it's <= 255
            if thresh_setting > 255:
                worked = False

            if not worked:
                raise argparse.ArgumentTypeError("(--purify | -p) threshold must be a positive float <= 255.")
        else:
            raise argparse.ArgumentTypeError("'{}' is not a valid option for (--purify | -p).".format(p_arg_name))

# Print main program warnings
if args["no_pdf"] and args["purify"] != None:
    print("[WARNING]: Both (--purify|-p) and (--no_pdf|-n) arguments were passed, will not make PDF.")

# Print settings
print()
print("-------- PDF SETTINGS --------")
pdf_no_title_string = "PDF will have no title."
if use_pdf_title:
    if len(pdf_title) <= 0:
        print(pdf_no_title_string)
    else:
        print("PDF title: {}".format(pdf_title))
else:
    print(pdf_no_title_string)

if len(pdf_author) <= 0:
    print("PDF will have no author.")
else:
    print("PDF author: {}".format(pdf_author))

if not args["no_pdf"]:
    print("PDF resolution: {} DPI".format(pdf_dpi))

print("Input directory: {}".format(input_dir))

if args["no_pdf"]:
    print("Will only print the Table of Contents, will NOT process images or save PDF.")
else:
    # Print target filename
    print("Output filename: {}".format(output_file))

if purify:
    print("Will purify documents:")
    print("\tSharpening factor: {}".format(sharpen_factor))
    print("\tThreshold: {}.".format(thresh_setting))

if args["table_of_contents_format"] != None:
    print("Table of Contents formatting:")
    print("\tName length break limit: {}".format(toc_line_break_limit))
    print("\tPage number prefix: '{}'".format(pagenum_pre))
    print("\tPage number postfix: '{}'".format(pagenum_post))
    print("\tIndent text: '{}'".format(ident_str))

# We will be catching KeyboardInterrupts
try:
    # Do main imports
    import img2pdf
    from PIL import Image, ImageEnhance
    from collections import OrderedDict
    from PyPDF2 import PdfFileWriter, PdfFileReader
    import shutil
    import textwrap

    print()
    print("-------- DIRECTORY SCANNING --------")
    print("Scanning directory: '{}'...".format(input_dir))

    # Walk though folder structure (recursive alphabetical, include all files/folders)
    input_dir_list = [str(p) for p in sorted(Path(input_dir).glob('**/*'))]

    # Prefix to prepend to temporary file/folder names
    temp_name_prepend = "__temp__"

    # The temporary directory name
    temp_dir_name = temp_name_prepend + input_dir_name
    temp_dir = os.path.join(main_dir, temp_dir_name)

    if not args["no_pdf"]:
        # Delete temp dir if it exists
        def clean_temp_dir():
            if os.path.exists(temp_dir):
                if os.path.isdir(temp_dir):
                    shutil.rmtree(temp_dir)
                elif os.path.isfile(temp_dir):
                    os.remove(temp_dir)
        clean_temp_dir()

        # Make temp directory
        os.mkdir(temp_dir)

        # Set up exit function to clean up if exited early
        def exit_clean_temp_dir():
            print("Delete temporary directory: {}".format(temp_dir))
            clean_temp_dir()
            print("\tDone!")
        exit_funcs.append(exit_clean_temp_dir)

    # Make final input dir names
    if purify:
        # Temp directory
        final_input_dir = temp_dir
    else:
        # The original directory
        final_input_dir = input_dir

    # Save image paths (and empty/ignored-file dirs) paths to ordered list
    page_list = list()
    # Make dict with rename (dir, bm_name)
    page_dir_rename_dict = dict()
    for p in input_dir_list:
        if os.path.isfile(p):
            # Check if it's an invalid extention, and if so, fully ignore it
            p_ext = path_to_ext(p)

            if p_ext not in valid_exts:
                print("[UNSUPPORTED]: {}".format(p))
                continue

            # Test if it's a metadata file, and if so, fully ignore
            if p_ext in metadata_file_exts:
                continue

            # Test if it should be ignored, and if so, fully ignore it
            if p_ext in ignored_file_exts:
                print("[IGNORING]: {}".format(p))
                continue

            # Test if the path length is nearing the Windows limit
            windows_path_limit = 260
            unix_path_limit = 4096
            wiggle_room = 40
            p_path_length = len(p)
            min_path_length = min(windows_path_limit, unix_path_limit)
            if p_path_length > min_path_length - wiggle_room:
                print("[WARNING] Dangerously long pathname: {}".format(p))
                print("\tPath length: {} characters".format(p_path_length))
                print("\tWindows maximum path length: {} characters".format(windows_path_limit))
                print("\tUnix maximum path length: {} characters".format(unix_path_limit))
                print("\tRenaming, moving, or downloading this folder may cause errors unless you shorten the names of the file/folders.")
                print("\Recommended action: Use '.name' files (instead of the folder names) to define bookmark names.")

            page_list.append(p)
        elif os.path.isdir(p):
            # Get files and directories inside p
            p_list = [os.path.realpath(str(x)) for x in sorted(Path(p).glob('*'))]
            p_dir_list = [x for x in p_list if os.path.isdir(x)]
            p_file_list = [x for x in p_list if os.path.isfile(x)]

            # Make file list without ignored files
            p_file_list_ignored = list()
            # Also update page_dir_rename_dict
            for x in p_file_list:
                x_path, x_filename = os.path.split(x)
                x_ext = path_to_ext(x)

                # Check if it's a valid extention
                if x_ext in valid_exts:
                    # If not ignored file, append to file list
                    if x_ext not in ignored_file_exts + metadata_file_exts:
                        p_file_list_ignored.append(x)
                    # If it's a rename file, parse and append to rename dict
                    if x_ext in rename_exts:
                        # If purify, make sure it's referencing the final input dir
                        if purify:
                            relative_x_path = os.path.relpath(x_path, input_dir)
                            rename_dir = os.path.join(final_input_dir, relative_x_path)
                        else:
                            rename_dir = x_path

                        rename_name = read_string_from_file(x)

                        page_dir_rename_dict[rename_dir] = rename_name

            # Test if it's empty or contains only ignored files
            if len(p_dir_list) <= 0 and len(p_file_list_ignored) <= 0:
                # Add path (used to make "empty" bookmarks)
                page_list.append(p)
    print("\tDone scanning directory!")

    # Get number of pages
    num_pages = len([p for p in page_list if os.path.isfile(p)])
    num_image_pages = len([p for p in page_list if os.path.isfile(p) and os.path.splitext(p)[-1] not in blank_exts])
    num_pages_len = len(str(num_pages))
    num_image_pages_len = len(str(num_image_pages))
    print("\tPage count: {}".format(num_pages))

    # Run purification (save to temporary directory)
    if purify and num_image_pages > 0:
        print()
        print("-------- PURIFICATION --------")
        print("Saving purified images to temporary directory: {}".format(final_input_dir))

        # Delete temp dir (or file with same name) if it already exists
        clean_temp_dir()

        # Create new page_list
        new_page_list = list()
        new_page_list_dirs = list()
        new_page_list_files = list()
        for p in page_list:
            # Make final_p (replace input_dir with final_input_dir)
            rel_p = os.path.relpath(p, input_dir)
            final_p = os.path.join(final_input_dir, rel_p)
            new_page_list.append(final_p)
            if os.path.isdir(p):
                new_page_list_dirs.append(final_p)
            else:
                new_page_list_files.append(final_p)

        # Make all directories first
        for p in new_page_list_dirs:
            Path(p).mkdir(parents=True, exist_ok=True)
        for p in new_page_list_files:
            Path(os.path.dirname(p)).mkdir(parents=True, exist_ok=True)

        # Process image files
        curr_page = 0 # Will go to 1 before first print
        for x, p in enumerate(page_list):
            final_p = new_page_list[x]
            if os.path.isfile(p):
                # It's a page file
                if os.path.splitext(p)[-1] in blank_exts:
                    # It's a blank page file, just copy
                    shutil.copy(p, final_p)
                else:
                    # It's an image file, process
                    curr_page += 1
                    with Image.open(p) as page_im:
                        if purify:
                            curr_page_str = str(curr_page).rjust(num_image_pages_len)
                            print("[PURIFY] ({}/{}): {}".format(curr_page_str, num_image_pages, p))
                            # Make greyscale
                            gray = page_im.convert('L')

                            # Sharpen
                            enhancer = ImageEnhance.Sharpness(gray)
                            sharpen = enhancer.enhance(sharpen_factor)

                            # Apply threshold
                            thresh = sharpen.point(lambda p: p > thresh_setting and 255)

                            # Make 1 bit
                            final_page_im = thresh.convert('1')
                        else:
                            final_page_im = page_im

                    # Save image
                    final_page_im.save(final_p, "PNG", dpi=(pdf_dpi, pdf_dpi))

        # Update page_list with new images/paths
        page_list = new_page_list

        print("\tDone purifying images!")

    # Get size from first image
    pl_files_images = [p for p in page_list if os.path.isfile(p) and os.path.splitext(p)[-1] not in blank_exts]
    if num_image_pages > 0:
        with Image.open(pl_files_images[0]) as cover:
            width, height = cover.size
    else:
        width = int(pdf_dpi * 8.5) # Assume 8.5in
        height = int(pdf_dpi * 11) # Assume 11in

    # Get number of blank pages
    num_blank_pages = num_pages - num_image_pages

    # Test if blank pages are used
    if num_blank_pages > 0:
        blanks_used = True
    else:
        blanks_used = False

    if blanks_used and not args["no_pdf"]:
        print()
        print("-------- BLANK PAGES --------")
        print("Saving blank page images to temporary directory: {}".format(final_input_dir))

        # Make pure white images for .blank files
        #TODO: Figure out how to make truly blank pages (or at least smaller file sizes)
        new_page_list = list()
        blank_page_num = 0 # Goes to 1 on first page
        for p in page_list:
            page_basename = os.path.relpath(p, input_dir)
            page_temp = os.path.realpath(os.path.join(temp_dir, page_basename))

            if os.path.isfile(p):
                if os.path.splitext(p)[-1] in blank_exts:
                    blank_page_num += 1

                    # Make a white PNG in the temporary folder
                    blank_page_name = os.path.splitext(page_basename)[0] + ".png"
                    blank_page_filename = os.path.realpath(os.path.join(temp_dir, blank_page_name))

                    curr_page_str = str(blank_page_num).rjust(len(str(num_blank_pages)))
                    print("[BLANK] ({}/{}): {}".format(curr_page_str, num_blank_pages, blank_page_filename))
                    blank_page = Image.new('1', (width, height), color = "white")

                    Path(os.path.dirname(blank_page_filename)).mkdir(parents=True, exist_ok=True)
                    blank_page.save(blank_page_filename, "PNG", dpi=(pdf_dpi, pdf_dpi))
                    new_page_list.append(blank_page_filename)
                else:
                    # Copy image to temp (fix extra bookmark due to relative directory error)
                    if p != page_temp:
                        Path(os.path.dirname(page_temp)).mkdir(parents=True, exist_ok=True)
                        shutil.copy(p, page_temp)
                    new_page_list.append(page_temp)
            else:
                # Make directory in temp (fixes error mentioned above)
                Path(page_temp).mkdir(parents=True, exist_ok=True)
                new_page_list.append(page_temp)
        page_list = new_page_list

        print("\tDone creating blank pages!")

    # Make page_list but with only files
    page_list_files = [p for p in page_list if os.path.isfile(p)]

    # Create nested ordered dictionary from list
    page_dict = OrderedDict()
    for p in page_list:
        p = os.path.relpath(p, final_input_dir) # Make relative

        current_level = page_dict
        for part in p.split(os.path.sep):
            if part not in current_level:
                current_level[part] = OrderedDict()
            current_level = current_level[part]

    # Create PDF from page_list(no bookmarks)
    if not args["no_pdf"]:
        print()
        print("-------- PDF CREATION --------")
        temp_pdf = os.path.join(output_file_dir, temp_name_prepend + output_file_name)
        print("Creating PDF document from image files...")
        temp_pdf_file_binary = img2pdf.convert(page_list_files, dpi=pdf_dpi, x=None, y=None)
        print("\tDone!")

        # Setup exit handler to delete temp PDF
        input_pdf_file = None
        def exit_clean_temp_pdf():
            print("Delete temporary PDF: {}".format(temp_pdf))
            if input_pdf_file != None:
                input_pdf_file.close()
            os.remove(temp_pdf)
            print("\tDone!")
        exit_funcs.append(exit_clean_temp_pdf)

        print("Saving temporary PDF: {}".format(temp_pdf))
        with open(temp_pdf, "wb") as f:
            f.write(temp_pdf_file_binary)
        print("\tDone!")

        # Load PDF into PyPDF2
        print("Loading temporary PDF into editing library...")
        output_pdf = PdfFileWriter()
        input_pdf_file = open(temp_pdf, 'rb')
        input_pdf = PdfFileReader(input_pdf_file)
        output_pdf.appendPagesFromReader(input_pdf)
        print("\tDone!")

    print()
    print("-------- BOOKMARKS --------")
    print("Creating bookmark hierarchy from directory structure...")
    # Save ToC lines to list
    #TODO: Save ToC to file option
    toc_dict_list = list()
    if len([p for p in page_dict.values() if p != OrderedDict()]) <= 0:
        print()
        if not args["no_pdf"]:
            print("[WARNING]: No subdirectories found, not creating bookmarks.")
        else:
            print("[ERROR]: No subdirectories found, no table of contents to generate.")
    else:
        # Add nested bookmarks from page_dict
        ident_level = 0
        last_page_index = -1 # Because we want the next page to be 0
        path_list = list()
        bookmark_list = list()
        page_ref = None
        def iterdict(d, base_path="", empty_parents_in=list()):
            global ident_level
            global path_list
            global last_page_index
            global page_ref
            global toc_dict_list

            for k, v in d.items():
                filepath = os.path.join(base_path, os.path.sep.join(path_list))
                filename = os.path.join(filepath, k)
                filename = os.path.realpath(filename)

                # Get parent bookmark
                if len(bookmark_list) > 0:
                    bm_parent = bookmark_list[-1]
                else:
                    bm_parent = None

                # Get bookmark name
                if filename in page_dir_rename_dict:
                    # Name is defined in a rename file
                    bm_name = page_dir_rename_dict[filename]
                elif args["order_number_separator"] != None:
                    # Remove leading order numbers from dir name
                    k_split = k.split(args["order_number_separator"])
                    if len(k_split) <= 1:
                        bm_name = k
                    else:
                        bm_name = args["order_number_separator"].join(k_split[1:]).strip(" ")
                else:
                    bm_name = k

                page_ref = last_page_index + 1

                # Test if it's a file or a directory
                if len(v) > 0:
                    # It's a not-fully-empty dir (pages/folders)

                    # Get recursive list of files and folders
                    v_list = [str(x) for x in Path(filename).glob('**/*')]
                    v_dir_list = [x for x in v_list if os.path.isdir(x)]
                    v_file_list = [x for x in v_list if os.path.isfile(x) and os.path.splitext(x)[-1] in page_exts]

                    # Deal with recursively empty folders
                    empty_parents = list()
                    if len(v_file_list) <= 0:
                        # Test if is not a subdir of an empty_parent
                        is_subdir_of_empty_parent = False
                        for empty_parent in empty_parents:
                            if os.path.commonpath([filename, empty_parent]) == empty_parent:
                                is_subdir_of_empty_parent = True
                        if not is_subdir_of_empty_parent:
                            # This is the main parent
                            page_ref += 1
                            empty_parents.append(filename)

                    # Prevent referencing non-existent pages
                    page_ref = min(page_ref, num_pages - 1)

                    # Save to toc_dict_list
                    toc_dict_list.append({
                        "name": bm_name,
                        "level": ident_level,
                        "page": page_ref + 1
                        })

                    ident_level += 1

                    if not args["no_pdf"]:
                        # Add bookmark w/ parent, save as potential parent
                        bm = output_pdf.addBookmark(bm_name, page_ref, parent=bm_parent)

                        # Add to bookmarks list
                        bookmark_list.append(bm)

                    path_list.append(k)

                    # Do recursion
                    iterdict(v, base_path=base_path, empty_parents_in=empty_parents)

                    if not args["no_pdf"]:
                        temp = bookmark_list.pop()
                    temp = path_list.pop()

                    ident_level -= 1
                else:
                    # Either it's a file or an empty (placeholder) dir
                    if os.path.isdir(filename):
                        # It's a totally empty directory, make an "empty" bookmark (no pages/children, references next page)

                        # Deal with children of empty parents
                        empty_parents = empty_parents_in
                        is_subdir_of_empty_parent = False
                        for empty_parent in empty_parents:
                            if os.path.commonpath([filename, empty_parent]) == empty_parent:
                                is_subdir_of_empty_parent = True
                        if is_subdir_of_empty_parent:
                            # Adjust page number forward
                            page_ref += 1

                        # Prevent referencing non-existent pages
                        page_ref = min(page_ref, num_pages - 1)

                        # Save to toc_dict_list
                        toc_dict_list.append({
                            "name": bm_name,
                            "level": ident_level,
                            "page": page_ref + 1
                            })

                        if not args["no_pdf"]:
                            # Add bookmark w/ parent, abandon as potential parent
                            temp = output_pdf.addBookmark(bm_name, page_ref, parent=bm_parent)
                    else:
                        # It's a file
                        page_index = page_list_files.index(filename)
                        last_page_index = page_index
        iterdict(page_dict, base_path=final_input_dir)
        print("\tDone!")

    if not args["no_pdf"]:
        print("Saving bookmarked PDF: {}".format(output_file))

        # Create PDF metadata
        pdf_metadata_dict = dict()
        if use_pdf_title:
            pdf_metadata_dict['/Title'] = pdf_title

        pdf_metadata_dict['/Author'] = pdf_author

        pdf_metadata_dict['/Producer'] = PROG_FILE_NAME

        # Add metadata to PDF
        output_pdf.addMetadata(pdf_metadata_dict)

        # Save final PDF
        with open(output_file, 'wb') as f:
            output_pdf.write(f)

        print("\tDone!")

    print()
    print("-------- TABLE OF CONTENTS --------")
    print("Building Table of Contents from bookmark hierarchy...")

    # Make row of ToC function
    def make_toc_row(bm_dict_in):
        ident = "".join([ident_str for x in range(bm_dict_in["level"])])
        page_toc_prefix = pagenum_pre + str(bm_dict_in["page"]).ljust(num_pages_len) + pagenum_post
        page_toc_base = page_toc_prefix + ident

        final_row = ""
        # Break name into multiple lines if it's too long
        if toc_line_break_limit != None:
            if len(bm_dict_in["name"]) > toc_line_break_limit:
                nm_lines = textwrap.fill(bm_dict_in["name"], width=toc_line_break_limit, break_long_words=False).split("\n")
                final_row += page_toc_base + nm_lines[0]
                for l in nm_lines[1:]:
                    base_space = "".join([" " for x in range(len(page_toc_base))])
                    final_row += "\n" + base_space + l
                return final_row

        # If we didn't break it up, just return it
        final_row += page_toc_base + bm_dict_in["name"]
        return final_row

    # Make rows of ToC
    toc_row_list = [make_toc_row(r) for r in toc_dict_list]

    # Get max line width
    toc_header_width = max([max([len(y) for y in x.split("\n")]) for x in toc_row_list])

    # Get ToC title
    toc_title_lines = textwrap.fill(pdf_title, width=toc_header_width, break_long_words=False).split("\n")
    toc_title = ["\n".join([x.center(toc_header_width) for x in toc_title_lines])]

    # Get ToC author (if applicable)
    toc_author = list()
    if len(pdf_author) > 0:
        toc_header_author = "by " + pdf_author
        toc_author_lines = textwrap.fill(toc_header_author, width=toc_header_width, break_long_words=False).split("\n")
        toc_author = ["\n".join([x.center(toc_header_width) for x in toc_author_lines])]

    # Get ToC label
    toc_label_lines = textwrap.fill("Table of Contents", width=toc_header_width, break_long_words=False).split("\n")
    toc_label = ["\n".join([x.center(toc_header_width) for x in toc_label_lines])]

    # Get ToC seperator
    toc_sep = ["".join(["-" for x in range(toc_header_width)])]

    # Get page count text
    toc_pagecount = ["\tPage count: {}".format(num_pages)]

    # Make list with all ToC lines
    final_toc_list = toc_title + toc_author + toc_label + toc_sep + toc_row_list + toc_pagecount

    print("\tDone!")

    # Print ToC lines
    print()
    for r in final_toc_list:
        print(r)

    # Set to print after other exit tasks
    def job_complete():
        print()
        print("-------- JOB COMPLETE --------")
        print("Page count: {}".format(num_pages))
        if not args["no_pdf"]:
            print("Final PDF location: {}".format(output_file))
            print("File size: {} bytes".format(os.path.getsize(output_file)))
except KeyboardInterrupt:
    print()
    print()
    print()
    print("[CTRL-C] Exiting...")


from django.urls import path
from . import views
from blog.views import ProfilePage
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('', views.index, name='index'),
    path('post/<int:post_id>', views.details, name='details'),
    path('post/new/', views.post_new, name='post_new'),
    path('post/<int:post_id>/edit/', views.post_edit, name='post_edit'),
    path('post/delete/<int:post_id>/', views.post_delete, name='post_delete'),
    path('accounts/login/', views.user_login,  name='login'),
    path('accounts/logout/', views.user_logout, name='logout'),
    path('accounts/register/', views.register, name='register'),
    path('accounts/profile/edit/', views.profile_edit, name='profile_edit'),
    path('accounts/profile/', ProfilePage.as_view(), name='profile'),
    # path('accounts/change-password/', views.change_password, name='change_password'),
    path('disciplines/', views.disciplines, name="disciplines"),
    path('history/', views.history, name="history")

]
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)


from django.urls import path, register_converter, re_path
from .converters import FourDigitYearConverter
from . import views

register_converter(FourDigitYearConverter, 'yyyy') #'yyyy'라는 수를 FourDigitYearConverter에 넣어 4자리로 변환

app_name = 'shop'

urlpatterns = [
    path('archives/<yyyy:year>/', views.archives_year), #'yyyy'의 변환된 형태를 year라는 변수에 저장하고, 'archives/year'라는 url이 호출되면 views.py에서 archives_year함수를 호출
    path('', views.item_list),
    path('<int:pk>/', views.item_detail),
    # re_path(r'^(?P<pk>\d+)/$', views.item_detail),
]


import logging
import oauth2.web
import oauth2.grant

from zoe_api.auth.oauth2.postgresql import AccessTokenStorePg, ClientStorePg
from zoe_api.auth.oauth2.tokengenerator import Uuid4

log = logging.getLogger(__name__)

class OAuthSiteAdapter(oauth2.web.ResourceOwnerGrantSiteAdapter):
    """OAuth Simple SiteAdapter"""
    def authenticate(self, request, environ, scopes, client):
        return {}

client_store = ClientStorePg() #pylint: disable=invalid-name
token_store = AccessTokenStorePg() #pylint: disable=invalid-name

token_generator = Uuid4() #pylint: disable=invalid-name
token_generator.expires_in[oauth2.grant.ClientCredentialsGrant.grant_type] = 3600

auth_controller = oauth2.Provider( #pylint: disable=invalid-name
    access_token_store=token_store,
    auth_code_store=token_store,
    client_store=client_store,
    token_generator=token_generator
)

site_adapter = OAuthSiteAdapter() #pylint: disable=invalid-name

auth_controller.token_path = '/api/0.7/oauth/token'
auth_controller.add_grant(oauth2.grant.ClientCredentialsGrant())
auth_controller.add_grant(oauth2.grant.RefreshToken(expires_in=3600))
auth_controller.add_grant(oauth2.grant.ResourceOwnerGrant(site_adapter=site_adapter))


import sys
sys.path.insert(1, "../../../")
import h2o, tests
import numpy as np
import random
import math
import scipy.special

def frame_math_ops():

    sin_cos_tan_atan_sinh_cosh_tanh_asinh_data = [[random.uniform(-10,10) for r in range(10)] for c in range(10)]
    asin_acos_atanh_data = [[random.uniform(-1,1) for r in range(10)] for c in range(10)]
    acosh_data = [[random.uniform(1,10) for r in range(10)] for c in range(10)]
    abs_data = [[random.uniform(-100000,0) for r in range(10)] for c in range(10)]
    signif_data = [[0.0000123456, 1], [2, 3]]

    h2o_data1 = h2o.H2OFrame(python_obj=sin_cos_tan_atan_sinh_cosh_tanh_asinh_data)
    h2o_data2 = h2o.H2OFrame(python_obj=asin_acos_atanh_data)
    h2o_data3 = h2o.H2OFrame(python_obj=acosh_data)
    h2o_data4 = h2o.H2OFrame(python_obj=abs_data)
    h2o_data5 = h2o.H2OFrame(python_obj=signif_data)

    np_data1 = np.array(sin_cos_tan_atan_sinh_cosh_tanh_asinh_data)
    np_data2 = np.array(asin_acos_atanh_data)
    np_data3 = np.array(acosh_data)
    np_data4 = np.array(abs_data)

    for d in range(1,6):
        h2o_signif = h2o_data5.signif(digits=d)
        h2o_round = h2o_data5.round(digits=d+4)
        s = h2o_signif[0,0]
        r = h2o_round[0,0]
        assert s == r, "Expected these to be equal, but signif: {0}, round: {1}".format(s, r)
    h2o_transposed = h2o_data1[0:5].transpose()
    r, c = h2o_transposed.dim
    assert r == 5 and c == 10, "Expected 5 rows and 10 columns, but got {0} rows and {1} columns".format(r,c)
    tests.np_comparison_check(h2o_transposed, np.transpose(np_data1[:,0:5]), 10)
    tests.np_comparison_check(h2o_data1.cos(), np.cos(np_data1), 10)
    tests.np_comparison_check(h2o_data1.sin(), np.sin(np_data1), 10)
    tests.np_comparison_check(h2o_data1.tan(), np.tan(np_data1), 10)

if __name__ == "__main__":
    tests.run_test(sys.argv, frame_math_ops)


#!/usr/bin/python
# -*- coding: utf8 -*-

import requests, re
from flexions.flexions import *

class HtmlFrRu:
	def __init__(self, word):
		self.word = word
		r = requests.get(u"http://fr.wiktionary.org/wiki/%s" % word)
		self.html = r.text

	def flexions_args(self):
		flexions = self.flexions()

		acronyms = flexions.format_acronyms()
		for acronym in acronyms:
			yield (acronym, acronyms[acronym])

class HtmlFrRuAdj(HtmlFrRu):
	def nominatif(self):
		s = re.search(u"""]+>Nominatif	\s+([^<]+)	\s+]+>([^<]+)	\s+]+>([^<]+)	\s+]+>([^<]+)	""", self.html, re.U | re.S)
		if s is None:
			return None
		else:
			return (s.group(1), s.group(2), s.group(3), s.group(4))

	def __simple_case(self, name):
		s = re.search(u"""%s	\s+]+>([^<]+)	\s+]+>([^<]+)	\s+]+>([^<]+)	\s+
""" % name, self.html, re.U | re.S)
		if s is not None:
			return (s.group(1), s.group(1), s.group(2), s.group(3))

		s = re.search(u"""%s	\s+]+>([^<]+)	\s+([^<]+)	\s+]+>([^<]+)	\s+
""" % name, self.html, re.U | re.S)
		if s is not None:
			return (s.group(1), s.group(1), s.group(2), s.group(3))

		return None

	def genitif(self):
		return self.__simple_case(u"Génitif")

	def datif(self):
		return self.__simple_case(u"Datif")

	def prepositionnel(self):
		return self.__simple_case(u"Prépositionnel")

	def instrumental(self):
		s = re.search(u"""Instrumental	\s+]+>([^<]+)	\s+]+>([^<]+)\s+/\s+]+>([^<]+)	\s+]+>([^<]+)	\s+
""", self.html, re.U | re.S)
		if s is not None:
			return (s.group(1), s.group(1), s.group(2), s.group(3), s.group(4))

		s = re.search(u"""Instrumental	\s+]+>([^<]+)	\s+([^<]+)\s+/\s+]+>([^<]+)	\s+]+>([^<]+)	\s+
""", self.html, re.U | re.S)
		if s is not None:
			return (s.group(1), s.group(1), s.group(2), s.group(3), s.group(4))

		return None

	def accusatif(self):
		s = re.search(u"""
\s+]+>Accusatif	\s+Animé	\s+]+>([^<]+)	\s+]+>([^<]+)	\s+]+>]+>([^<]+)	\s+]+>([^<]+)	\s+
\s+
\s+Inanimé	\s+([^<]+)	\s+]+>([^<]+)	\s+
""", self.html, re.U | re.S)
		if s is None:
			return None
		else:
			return (s.group(1), s.group(2), s.group(3), s.group(4), s.group(5), s.group(6))

	def flexions(self):
		ret = RuAdjectiveFlexions()

		(m, n, f, p) = self.nominatif()
		ret.add_flexion(m, NominatifCase, SingularNumber, MaleGender, None)
		ret.add_flexion(n, NominatifCase, SingularNumber, NeutralGender, None)
		ret.add_flexion(f, NominatifCase, SingularNumber, FemaleGender, None)
		ret.add_flexion(p, NominatifCase, PluralNumber, None, None)

		(m, n, f, p) = self.genitif()
		ret.add_flexion(m, GenitifCase, SingularNumber, MaleGender, None)
		ret.add_flexion(n, GenitifCase, SingularNumber, NeutralGender, None)
		ret.add_flexion(f, GenitifCase, SingularNumber, FemaleGender, None)
		ret.add_flexion(p, GenitifCase, PluralNumber, None, None)

		(m, n, f, p) = self.datif()
		ret.add_flexion(m, DatifCase, SingularNumber, MaleGender, None)
		ret.add_flexion(n, DatifCase, SingularNumber, NeutralGender, None)
		ret.add_flexion(f, DatifCase, SingularNumber, FemaleGender, None)
		ret.add_flexion(p, DatifCase, PluralNumber, None, None)

		(m, n, f, p) = self.prepositionnel()
		ret.add_flexion(m, PrepositionnelCase, SingularNumber, MaleGender, None)
		ret.add_flexion(n, PrepositionnelCase, SingularNumber, NeutralGender, None)
		ret.add_flexion(f, PrepositionnelCase, SingularNumber, FemaleGender, None)
		ret.add_flexion(p, PrepositionnelCase, PluralNumber, None, None)

		(m, n, f, f2, p) = self.instrumental()
		ret.add_flexion(m, InstrumentalCase, SingularNumber, MaleGender, None)
		ret.add_flexion(n, InstrumentalCase, SingularNumber, NeutralGender, None)
		ret.add_flexion(f, InstrumentalCase, SingularNumber, FemaleGender, None)
		ret.add_flexion(f2, InstrumentalCase, SingularNumber, FemaleGender, None)
		ret.add_flexion(p, InstrumentalCase, PluralNumber, None, None)

		(ma, n, f, pa, m, p) = self.accusatif()
		ret.add_flexion(ma, AccusatifCase, SingularNumber, MaleGender, True)
		ret.add_flexion(m, AccusatifCase, SingularNumber, MaleGender, False)
		ret.add_flexion(n, AccusatifCase, SingularNumber, NeutralGender, None)
		ret.add_flexion(f, AccusatifCase, SingularNumber, FemaleGender, None)
		ret.add_flexion(pa, AccusatifCase, PluralNumber, None, True)
		ret.add_flexion(p, AccusatifCase, PluralNumber, None, False)

		return ret

class HtmlFrRuNoun(HtmlFrRu):
	cases = { \
		 u"Nominatif"       : ("n", NominatifCase),		\
		 u"Génitif"         : ("g", GenitifCase),		\
		 u"Datif"           : ("d", DatifCase),			\
		 u"Accusatif"       : ("a", AccusatifCase),		\
		 u"Instrumental"    : ("i", InstrumentalCase),	\
		 u"Prépositionnel"  : ("l", PrepositionnelCase),\
	}

	def nominatif(self):
		s = re.search(u"
\s*Nominatif	\s*(\w|\u0301|\ucc81)+?	[^<]*?((\w|\u0301|\ucc81)+?)	", self.html, re.U | re.S)
		return s.group(2)

	def case(self, name):
		# Nominatif
		s = re.search(u"
\s*%s	\s*\s*]+>]+?>([^<]+?)	\s*]+>]+?>([^<]+?)" % name, self.html, re.U | re.S)
		if s:
			return ([s.group(1)], [s.group(2)])

		# Cas normal: deux rouges
		s = re.search(u"
\s*%s	\s*\s*]+>]+?>([^<]+?)	\s*]+>]+?>([^<]+?)" % name, self.html, re.U | re.S)
		if s:
			return ([s.group(1)], [s.group(2)])

		#s = re.search(u"
\s*%s	\s*\s*]+>]+?>([^<]+?)
[^<]*?]+?>([^<]+?)	\s*]+?>([^<]+?)" % name, self.html, re.U | re.S)
		#if s:
		#	return ([s.group(1), s.group(2)], [s.group(3)])

		# Instrumental féminin
		s = re.search(u"
\s*%s	\s*\s*]+>]+?>([^<]+?)
[^<]*?]+>]+?>([^<]+?)	\s*]+>]+?>([^<]+?)" % name, self.html, re.U | re.S)
		if s:
			return ([s.group(1), s.group(2)], [s.group(3)])

		#s = re.search(u"
\s*%s	\s*]+>([^<]+?)	[^<]*?]+>]+?>([^<]+?)" % name, self.html, re.U | re.S)
		#if s:
		#	return ([s.group(1)], [s.group(2)])

		# Euh...
		s = re.search(u"
\s*%s	\s*]+>]+?>([^<]+?)	\s*]+>([^<]+?)" % name, self.html, re.U | re.S)
		if s:
			return ([s.group(1)], [s.group(2)])
		return None

	def flexions(self):
		ret = RuNounFlexions()

		for case in self.cases:
			couple = self.case(case)
			if couple is None and case in ("Vocatif", "Partitif"):
				continue

			for flexion in couple[0]:
				ret.add_flexion(flexion, self.cases[case][1], SingularNumber)
			for flexion in couple[1]:
				ret.add_flexion(flexion, self.cases[case][1], PluralNumber)
		return ret

	def has_red_link(self):
		s = re.search(u"""[^<]+?	""", self.html, re.S | re.U | re.M)
		return s is not None


import boto3

# aws configuration
access_id_key = ""
secret_access_key = ""
session_token_key = ""

rdsClient = boto3.client(
    "rds",
    aws_access_key_id=access_id_key,
    aws_secret_access_key=secret_access_key,
    aws_session_token=session_token_key,
    region_name="us-east-1",
)

response = rdsClient.create_db_instance(
    DBName="Tejesh",
    DBInstanceIdentifier="Tejesh",
    AllocatedStorage=5,
    DBInstanceClass="db.t2.micro",
    Engine="MySQL",
    MasterUsername="",
    MasterUserPassword="",
    PubliclyAccessible=True,
)


from typing import Dict

from fastapi import APIRouter, Depends
from pydantic import BaseModel
from starlette import status
from starlette.responses import JSONResponse

from api.api_v1.depends import get_user
from api.api_v1.response import User, MeetingResponse, CommonResponse
from src.db.db_session import AsyncSessionLocal
from src.exceptions.contact_exceptions import ContactDoesNotExistException, \
    ContactNotReadyForBingoException
from src.exceptions.meeting_exceptions import \
    MeetingSlotsAlreadySelectedException, MeetingNotReadyException
from src.repositories.contact_repository import ContactRepository
from src.repositories.meeting_repository import MeetingRepository
from src.usecases.meeting_usecases import (
    GetMeetingSlotsUsecase,
    SetMeetingSlotsUsecase, CallContactUsecase
)

router = APIRouter()

@router.get(
    '/',
    status_code=status.HTTP_200_OK,
    description='Get meeting slots for user',
    response_model=MeetingResponse
)
async def get_user_meeting_slots(
        contact_id: int,
        user: User = Depends(get_user),
):
    async with AsyncSessionLocal() as session:
        usecase = GetMeetingSlotsUsecase(
            contact_repository=ContactRepository(session),
            meeting_repository=MeetingRepository(session)
        )
    try:
        return MeetingResponse(
            success=True,
            message='Success',
            content=await usecase.get_meeting_slots(user.id, contact_id)
        )
    except ContactDoesNotExistException as e:
        return JSONResponse(
            status_code=status.HTTP_400_BAD_REQUEST,
            content={
                'success': False,
                'message': e.message,
                'content': None
            }
        )
    except ContactNotReadyForBingoException as e:
        return JSONResponse(
            status_code=status.HTTP_400_BAD_REQUEST,
            content={
                'success': False,
                'message': e.message,
                'content': None
            }
        )

class SlotsIn(BaseModel):
    contact_id: int
    slots: Dict[str, float]

# TODO timstamp as int
# TODO +37498513580 test in server
@router.post(
    '/',
    status_code=status.HTTP_200_OK,
    description='Set meeting time for user',
    response_model=MeetingResponse
)
async def get_user_contacts(
        slots_in: SlotsIn,
        user: User = Depends(get_user),
):
    async with AsyncSessionLocal() as session:
        usecase = SetMeetingSlotsUsecase(
            contact_repository=ContactRepository(session),
            meeting_repository=MeetingRepository(session)
        )
    try:
        return MeetingResponse(
            success=True,
            message='Success',
            content=await usecase.set_meeting_slots(
                user.id, slots_in.contact_id, slots_in.slots)
        )
    except ContactDoesNotExistException as e:
        return JSONResponse(
            status_code=status.HTTP_400_BAD_REQUEST,
            content={
                'success': False,
                'message': e.message,
                'content': None
            }
        )
    except ContactNotReadyForBingoException as e:
        return JSONResponse(
            status_code=status.HTTP_400_BAD_REQUEST,
            content={
                'success': False,
                'message': e.message,
                'content': None
            }
        )
    except MeetingSlotsAlreadySelectedException as e:
        return JSONResponse(
            status_code=status.HTTP_400_BAD_REQUEST,
            content={
                'success': False,
                'message': e.message,
                'content': None
            }
        )

@router.post(
    '/call',
    status_code=status.HTTP_200_OK,
    description='Call contact',
    response_model=CommonResponse
)
async def call_cantact(
        contact_id: int,
        user: User = Depends(get_user),
):
    async with AsyncSessionLocal() as session:
        usecase = CallContactUsecase(
            contact_repository=ContactRepository(session),
        )
    try:
        await usecase.call_contact(user.id, contact_id)
    except ContactDoesNotExistException as e:
        return JSONResponse(
            status_code=status.HTTP_400_BAD_REQUEST,
            content={
                'success': False,
                'message': e.message,
                'content': None
            }
        )
    except MeetingNotReadyException as e:
        return JSONResponse(
            status_code=status.HTTP_400_BAD_REQUEST,
            content={
                'success': False,
                'message': e.message,
                'content': None
            }
        )
    return CommonResponse(
        success=True,
        message='Success',
        content='Changed status from call to success',
    )


import pickle
import pandas as pd
import numpy as np
from scipy.stats import norm

from csdid.aggte_fnc.utils import get_agg_inf_func, get_se, wif, AGGTEobj
from csdid.utils.mboot import mboot
import warnings

def compute_aggte(MP,
                      typec         = "group",
                      balance_e     = None,
                      min_e         = float('-inf'),
                      max_e         = float('inf'),
                      na_rm         = False,
                      bstrap        = None,
                      biters        = None,
                      cband         = None,
                      alp           = None,
                      clustervars   = None,
                      call          = None):

# =============================================================================
# unpack MP object
# =============================================================================
    group       = np.array( MP['group'] )
    t           = np.array( MP['t']     )
    att         = np.array( MP['att'] )
    dp          = MP['DIDparams']
    tlist       = np.array( dp['tlist'] )
    glist       = np.array( dp['glist'] )
    data        = dp['data']
    inffunc     = MP['inffunc']['inffunc']
    n           = MP['n']
    gname       = dp['gname']
    tname       = dp['tname']
    idname      = dp['idname']
    # typec       = MP['type']
    panel       = dp['panel']

    if clustervars is None:
        clustervars = dp['clustervars']
    if bstrap is None:
        bstrap = dp['bstrap']
    if biters is None:
        biters = dp['biters']
    if alp is None:
        alp = dp['alp']
    if cband is None:
        cband = dp['cband']

    # Overwrite MP objects (to compute bootstrap)
    MP['DIDparams']['clustervars'] = clustervars
    MP['DIDparams']['bstrap'] = bstrap
    MP['DIDparams']['biters'] = biters
    MP['DIDparams']['alp'] = alp
    MP['DIDparams']['cband'] = cband

# =============================================================================
#  Treat data
# =============================================================================

    if typec not in ["simple", "dynamic", "group", "calendar"]:
        raise ValueError("`typec` must be one of ['simple', 'dynamic', 'group', 'calendar']")
    # Removing missing values
    if na_rm:
        notna = ~np.isnan(att)
        group = group[notna]
        t = t[notna]
        att = att[notna]
        inffunc = inffunc[:, notna]
        glist = np.sort(np.unique(group))

        if typec == "group":
            gnotna = []
            for g in glist:
                indices = np.where((group == g) & (g <= t))
                is_not_na = np.any(~np.isnan(att[indices]))
                gnotna.append(is_not_na)

            gnotna = np.array(gnotna)
            glist = glist[gnotna]
            not_all_na = np.isin(group, glist)
            group = group[not_all_na]
            t = t[not_all_na]
            att = att[not_all_na]
            inffunc = inffunc[:, not_all_na]
            glist = np.sort(np.unique(group))

    if (not na_rm) and np.any(np.isnan(att)):
        raise ValueError("Missing values at att_gt found. If you want to remove these, set `na_rm = True`.")

    if panel:
        dta = data[data[tname] == tlist[0]]
    else:
        dta = data.groupby(idname).mean().reset_index()
        dta = dta.iloc[:, 1:]

# =============================================================================
#  Treat data 2
# =============================================================================

    originalt = t
    originalgroup = group
    originalglist = glist
    originaltlist = tlist
    # In case g's are not part of tlist
    originalgtlist = np.sort(np.unique(np.concatenate((originaltlist, originalglist))))
    uniquet = list(range(1, len(originalgtlist) + 1))

    # Function to switch from "new" t values to original t values
    def t2orig(t):
        return originalgtlist[uniquet.index(t) if t in uniquet else -1]

    # Function to switch between "original" t values and new t values
    def orig2t(orig):
        new_t = [uniquet[i] for i in range(len(originalgtlist)) if originalgtlist[i] == orig]
        out = new_t[0] if new_t else None
        return out

    t     = [orig2t(orig) for orig in originalt]
    group = [orig2t(orig) for orig in originalgroup]
    glist = [orig2t(orig) for orig in originalglist]
    tlist = np.asarray(list(set(t)))
    maxT  = max(t)

    # Set the weights
    # return data.columns
    weights_ind = dta['w1'].to_numpy()

    # We can work in overall probabilities because conditioning will cancel out
    # since it shows up in numerator and denominator
    pg = np.array([np.mean(weights_ind * (dta[gname].to_numpy() == g)) for g in originalglist])

    # Length of this is equal to the number of groups
    pgg = pg

    # Same but length is equal to the number of ATT(g,t)
    pg = [pg[glist.index(g)] for g in group]

    # Which group time average treatment effects are post-treatment
    keepers = [i for i in range(len(group)) if group[i] <= t[i] <= (group[i] + max_e)] ### added second condition to allow for limit on longest period included in att

    # n x 1 vector of group variable
    G = [orig2t(g) for g in dta[gname].to_numpy()]

# =============================================================================
#  simple
# =============================================================================

    if typec == "simple":
        # Simple ATT
        # Averages all post-treatment ATT(g,t) with weights given by group size
        pg = np.array(pg)
        simple_att = np.sum(att[keepers] * pg[keepers]) / np.sum(pg[keepers])
        if np.isnan(simple_att):
            simple_att = None

        # Get the part of the influence function coming from estimated weights
        simple_wif = wif(keepers, pg, weights_ind, G, group)

        # Get the overall influence function
        simple_if = get_agg_inf_func(att = att ,
                                     inffunc = inffunc ,
                                     whichones = keepers ,
                                     weights_agg = np.array(pg)[keepers]/np.sum(np.array(pg)[keepers]) ,
                                     wif = simple_wif )[:, None]

        # Get standard errors from the overall influence function
        simple_se = get_se(simple_if, dp)

        if simple_se is not None:
            if simple_se <= np.sqrt(np.finfo(float).eps) * 10:
                simple_se = None

        AGGTEobj_print = AGGTEobj(overall_att=simple_att,
                                  overall_se=simple_se,
                                  typec=typec,
                                  inf_function={'simple_att': simple_if},
                                  call=call, DIDparams=dp)

        return AGGTEobj_print

# =============================================================================
#  GRoup
# =============================================================================

    if typec == "group":
        group = np.array(group)
        t = np.array(t)
        pg = np.array(pg)
        selective_att_g = [np.mean(att[( group== g) & (t >= g) & (t <= (group + max_e))]) for g in glist]
        selective_att_g = np.asarray(selective_att_g)
        selective_att_g[np.isnan(selective_att_g)] = None

        selective_se_inner = [None] * len(glist)
        for i, g in enumerate(glist):
            whichg = np.where(np.logical_and.reduce((group == g, g <= t, t <= (group + max_e))))[0]
            weightsg =  pg[whichg] / np.sum(pg[whichg])
            inf_func_g = get_agg_inf_func(att = att ,
                                            inffunc = inffunc ,
                                            whichones = whichg ,
                                            weights_agg = weightsg ,
                                            wif = None)[:, None]
            se_g = get_se(inf_func_g, dp)
            selective_se_inner[i] = {'inf_func': inf_func_g, 'se': se_g}

        # recover standard errors separately by group
        selective_se_g = np.asarray([item['se'] for item in selective_se_inner]).T

        selective_se_g[selective_se_g <= np.sqrt(np.finfo(float).eps) * 10] = None

        selective_inf_func_g = np.column_stack([elem["inf_func"] for elem in selective_se_inner])

        # use multiplier bootstrap (across groups) to get critical value
        # for constructing uniform confidence bands
        selective_crit_val = norm.ppf(1 - alp/2)

        if dp['cband']:
            if not dp['bstrap']:
                print("Used bootstrap procedure to compute simultaneous confidence band")

            selective_crit_val = mboot(selective_inf_func_g, dp)['crit_val']

            if np.isnan(selective_crit_val) or np.isinf(selective_crit_val):
                print("Simultaneous critical value is NA. This probably happened because we cannot compute t-statistic (std errors are NA). We then report pointwise conf. intervals.")
                selective_crit_val = norm.ppf(1 - alp/2)
                dp['cband'] = False

            if selective_crit_val < norm.ppf(1 - alp/2):
                print("Simultaneous conf. band is somehow smaller than pointwise one using normal approximation. Since this is unusual, we are reporting pointwise confidence intervals")
                selective_crit_val = norm.ppf(1 - alp/2)
                dp['cband'] = False

            if selective_crit_val >= 7:
                print("Simultaneous critical value is arguably 'too large' to be reliable. This usually happens when the number of observations per group is small and/or there is not much variation in outcomes.")

        # get overall att under selective treatment timing
        # (here use pgg instead of pg because we can just look at each group)
        selective_att = np.sum(selective_att_g * pgg) / np.sum(pgg)

        # account for having to estimate pgg in the influence function
        selective_wif = wif(keepers = np.arange(1, len(glist)+1)-1,
                            pg  = pgg,
                            weights_ind = weights_ind,
                            G = G,
                            group = group)

        # get overall influence function
        selective_inf_func = get_agg_inf_func(att = selective_att_g,
                                              inffunc = selective_inf_func_g,
                                              whichones = np.arange(1, len(glist)+1)-1,
                                              weights_agg = pgg/np.sum(pgg),
                                              wif = selective_wif)[:, None]

        # get overall standard error
        selective_se = get_se(selective_inf_func, dp)
        if not np.isnan(selective_se):
            if selective_se <= np.sqrt(np.finfo(float).eps) * 10:
                selective_se = None

        AGGTEobj_print = AGGTEobj(overall_att = selective_att,
                            overall_se = selective_se,
                            typec = typec,
                            egt = originalglist,
                            att_egt = selective_att_g,
                            se_egt = selective_se_g,
                            crit_val_egt = selective_crit_val,
                            inf_function = {'selective_inf_func_g': selective_inf_func_g,
                                            'selective_inf_func': selective_inf_func},
                            call = call,
                            DIDparams = dp)

        return AGGTEobj_print

# =============================================================================
#  Dynamic
# =============================================================================

    if typec == "dynamic":
        # event times
        # this looks at all available event times
        # note: event times can be negative here.
        # note: event time = 0 corresponds to "on impact"
        eseq = np.unique(np.array(originalt) - np.array(originalgroup) ) # Subtract corresponding elements and convert to NumPy array
        eseq = np.sort(eseq)  # Sort the unique values in ascending order

        # if the user specifies balance_e, then we are going to
        # drop some event times and some groups; if not, we just
        # keep everything (that is what this variable is for)
        originalt = np.array(originalt)
        originalgroup = np.array(originalgroup)
        pg = np.array(pg)
        include_balanced_gt = np.repeat(True, len(originalgroup))

        if balance_e is not None:
            include_balanced_gt = (t2orig(maxT) - originalgroup >= balance_e)
            eseq = np.unique(originalt[include_balanced_gt] - originalgroup[include_balanced_gt])
            eseq = np.sort(eseq)
            eseq = eseq[(eseq <= balance_e) & (eseq >= balance_e - t2orig(maxT) + t2orig(1))]
        eseq = eseq[(eseq >= min_e) & (eseq <= max_e)]

        dynamic_att_e = []
        for e in eseq:
            whiche = np.where((originalt - originalgroup == e) & include_balanced_gt)
            atte = att[whiche]
            pge = pg[whiche] / np.sum(pg[whiche])
            dynamic_att_e.append(np.sum(atte * pge))

        dynamic_se_inner = []
        for e in eseq:
            whiche = np.where((originalt - originalgroup == e) & (include_balanced_gt) )[0]
            pge = pg[whiche] / sum(pg[whiche])
            wif_e = wif(whiche,
                        pg,
                        weights_ind,
                        G,
                        group)
            inf_func_e = get_agg_inf_func(att         = att,
                                          inffunc     = inffunc,
                                          whichones   = whiche,
                                          weights_agg = pge,
                                          wif         = wif_e)[:, None]
            se_e = get_se(inf_func_e, dp)
            dynamic_se_inner.append({'inf_func': inf_func_e, 'se': se_e})

        dynamic_se_e = np.array([item['se'] for item in dynamic_se_inner]).T

        dynamic_se_e[dynamic_se_e <= np.sqrt(np.finfo(float).eps) * 10] = np.nan

        dynamic_inf_func_e = np.column_stack([item['inf_func'] for item in dynamic_se_inner])

        dynamic_crit_val = norm.ppf(1 - alp/2)
        if dp['cband']:
            if not dp['bstrap']:
                print('Used bootstrap procedure to compute simultaneous confidence band')
            dynamic_crit_val = mboot(dynamic_inf_func_e, dp)['crit_val']

            if np.isnan(dynamic_crit_val) or np.isinf(dynamic_crit_val):
                print('Simultaneous critical value is NA. This probably happened because we cannot compute t-statistic (std errors are NA). We then report pointwise conf. intervals.')
                dynamic_crit_val = norm.ppf(1 - alp/2)
                dp['cband'] = False

            if dynamic_crit_val < norm.ppf(1 - alp/2):
                print('Simultaneous conf. band is somehow smaller than pointwise one using normal approximation. Since this is unusual, we are reporting pointwise confidence intervals')
                dynamic_crit_val = norm.ppf(1 - alp/2)
                dp['cband'] = False

            if dynamic_crit_val >= 7:
                print("Simultaneous critical value is arguably 'too large' to be reliable. This usually happens when the number of observations per group is small and/or there is not much variation in outcomes.")

            epos = eseq >= 0
            dynamic_att = np.mean(np.array(dynamic_att_e)[epos])
            dynamic_inf_func = get_agg_inf_func(att         = np.array(dynamic_att_e)[epos],
                                                inffunc     = np.array(dynamic_inf_func_e[:, epos]),
                                                whichones   = np.arange(1, np.sum(epos)+1)-1,
                                                weights_agg = np.repeat(1 / np.sum(epos), np.sum(epos)),
                                                wif=None)[:, None]

            dynamic_se = get_se(dynamic_inf_func, dp)
            if not np.isnan(dynamic_se):
                if dynamic_se <= np.sqrt(np.finfo(float).eps) * 10:
                    dynamic_se = np.nan

        AGGTEobj_print = AGGTEobj(overall_att=dynamic_att,
                                overall_se=dynamic_se,
                                typec=typec,
                                egt=eseq,
                                att_egt=dynamic_att_e,
                                se_egt=dynamic_se_e,
                                crit_val_egt=dynamic_crit_val,
                                inf_function={'dynamic_inf_func_e': dynamic_inf_func_e,
                                              'dynamic_inf_func': dynamic_inf_func},
                                call=call,
                                min_e=min_e,
                                max_e=max_e,
                                balance_e=balance_e,
                                DIDparams=dp)

        return AGGTEobj_print

# =============================================================================
#  Calendar
# =============================================================================

 # np.array(group)
    if typec == "calendar":
        minG = min(group)
        calendar_tlist = tlist[tlist >= minG]
        pg = np.array(pg)
        calendar_att_t = []
        group = np.array(group)
        t = np.array(t)
        for t1 in calendar_tlist:
            whicht = np.where((t == t1) & (group <= t))[0]
            attt = att[whicht]
            pgt = pg[whicht] / np.sum(pg[whicht])
            calendar_att_t.append(np.sum(pgt * attt))

        # get standard errors and influence functions
        # for each time specific att
        calendar_se_inner = []
        for t1 in calendar_tlist:
            which_t = np.where((t == t1) & (group <= t))[0]
            pgt = pg[which_t] / np.sum(pg[which_t])
            wif_t = wif(keepers=which_t,
                        pg=pg,
                        weights_ind=weights_ind,
                        G=G,
                        group=group)
            inf_func_t = get_agg_inf_func(att=att,
                                            inffunc=inffunc,
                                            whichones=which_t,
                                            weights_agg=pgt,
                                            wif=wif_t)[:, None]
            se_t = get_se(inf_func_t, dp)
            calendar_se_inner.append({"inf_func": inf_func_t, "se": se_t})

        # recover standard errors separately by time
        calendar_se_t = np.array([se["se"] for se in calendar_se_inner]).T
        calendar_se_t[calendar_se_t <= np.sqrt(np.finfo(float).eps) * 10] = np.nan

        # recover influence function separately by time
        calendar_inf_func_t = np.column_stack([se["inf_func"] for se in calendar_se_inner])

        # use multiplier boostrap (across groups) to get critical value
        # for constructing uniform confidence bands
        calendar_crit_val = norm.ppf(1 - alp/2)

        if dp['cband']:
            if not dp['bstrap']:
                warnings.warn('Used bootstrap procedure to compute simultaneous confidence band')

            # mboot function is not provided, please define it separately
            calendar_crit_val = mboot(calendar_inf_func_t, dp)['crit_val']

            if np.isnan(calendar_crit_val) or np.isinf(calendar_crit_val):
                warnings.warn('Simultaneous critical value is NA. This probably happened because we cannot compute t-statistic (std errors are NA). We then report pointwise conf. intervals.')
                calendar_crit_val = norm.ppf(1 - alp/2)
                dp['cband'] = False

            if calendar_crit_val < norm.ppf(1 - alp/2):
                warnings.warn('Simultaneous conf. band is somehow smaller than pointwise one using normal approximation. Since this is unusual, we are reporting pointwise confidence intervals.')
                calendar_crit_val = norm.ppf(1 - alp/2)
                dp['cband'] = False

            if calendar_crit_val >= 7:
                warnings.warn("Simultaneous critical value is arguably 'too large' to be reliable. This usually happens when the number of observations per group is small and/or there is not much variation in outcomes.")

        # get overall att under calendar time effects
        # this is just average over all time periods
        calendar_att = np.mean(calendar_att_t)

        # get overall influence function
        calendar_inf_func = get_agg_inf_func(att=calendar_att_t,
                                             inffunc=calendar_inf_func_t,
                                             whichones=range(len(calendar_tlist)),
                                             weights_agg=np.repeat(1/len(calendar_tlist), len(calendar_tlist)),
                                             wif=None)[:, None]
        calendar_inf_func = np.array(calendar_inf_func)

        # get overall standard error
        calendar_se = get_se(calendar_inf_func, dp)
        if not np.isnan(calendar_se):
            if calendar_se <= np.sqrt(np.finfo(float).eps) * 10:
                calendar_se = np.nan

        AGGTEobj_print = AGGTEobj(overall_att=calendar_att,
                                overall_se=calendar_se,
                                typec=typec,
                                egt=list(map(t2orig, calendar_tlist)),
                                att_egt=calendar_att_t,
                                se_egt=calendar_se_t,
                                crit_val_egt=calendar_crit_val,
                                inf_function={"calendar_inf_func_t": calendar_inf_func_t,
                                              "calendar_inf_func": calendar_inf_func},
                                call=call,
                                DIDparams=dp)

        return AGGTEobj_print


from django.conf.urls import url

from . import views

# 去除硬编码（名称任意，但是需要与命名空间的名称一致，一般取名为项目名称）
app_name = "booklist"
# 去除硬编码 在应用的urlconf中添加url别名(name=)
urlpatterns = [
    # url('index/', views.index),
    # url('index/$', views.index),
    url(r'^$', views.index, name='index'),
    url(r'^booklist/$', views.list, name='list'),
    # url(r'$', views.index),
    url(r'^bookdetail/(\d+)/$', views.detail, name='detail'),
    url(r'^bookinfoss/(\d+)/$', views.infoss, name='infoss'),
    url(r'^add/$', views.add, name='add'),
    url(r'^delete/(\d+)/$', views.delete, name='delete'),
]