import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import warnings

warnings.filterwarnings("ignore")
sns.set(style="white")
data = pd.read_csv("telco_customer_churn.csv")
data.head()

data.columns.values

data.dtypes

# Impute missing values with 0
data["TotalCharges"] = data["TotalCharges"].replace(" ", 0).astype("float32")

sns.pairplot(data)

# People having lower tenure and higher monthly charges are tend to churn more. Also as you can see below, having month-to-month contract and fiber optic internet have a really huge effect on churn probability.

# Categorical plotting
sns.catplot(y="Churn", kind="count", data=data, height=3.0, aspect=2.5, orient="h")

# Categorical bar plot
sns.catplot(x="Churn", kind="count", palette="ch:.25", data=data)

# Count plot
f, ax = plt.subplots(figsize=(9, 4))
sns.countplot(y="Churn", data=data, color="c")

'''
Categorical scatterplots:
- stripplot(): with kind="strip" the default
- swarmplot(): with kind="swarm"
Categorical distribution plots
- boxplot(): with kind="box"
- violinplot(): with kind="violin"
- boxenplot(): with kind="boxen"
Categorical estimate plots
- pointplot(): with kind="point"
- barplot(): with kind="bar"
- countplot(): with kind="count"
'''

# Numerical features (tenure, monthly charges, total charges)

def kdeplot(feature):
    # to estimate probability density distribution
    plt.figure(figsize=(9, 4))
    plt.title("KDE for {}".format(feature))
    sns.kdeplot(data[data["Churn"] == "No"][feature].dropna(), color="blue", label="Not churn")
    sns.kdeplot(data[data["Churn"] == "Yes"][feature].dropna(), color="orange", label="Churn")

kdeplot("tenure")
kdeplot("MonthlyCharges")
kdeplot("TotalCharges")

'''
From the plots above we can conclude that:
- Recent clients are more likely to churn
- Clients with higher MonthlyCharges are also more likely to churn
- Tenure and MonthlyCharges are probably important features
'''

# Boundaries with scatter plots
sns.PairGrid(data, y_vars=["tenure"], x_vars=["MonthlyCharges", "TotalCharges"], height=4.5, hue="Churn", aspect=1.5).map(plt.scatter, alpha=0.6)

g = sns.PairGrid(data, y_vars=["tenure"], x_vars=["MonthlyCharges", "TotalCharges"], height=4.5, aspect=1.5, hue="Churn")
g.map(sns.regplot, color=".3")
g.set(ylim=(-1, 11), yticks=[0, 5, 10])

# Calculating features
data["total_charges_to_tenure_ratio"] = data["TotalCharges"] / data["tenure"]
data["monthly_charges_diff"] = data["MonthlyCharges"] - data["total_charges_to_tenure_ratio"]
kdeplot("monthly_charges_diff")

'''
There are 16 categorical features:
- 6 binary features (Yes/No)
- 9 features with three unique values
- 1 feature with four unique values
'''

# SeniorCitizen (binary feature)
def plot_percentages(plot_type, feature, orient="v", axis_name="Percentage of customers"):
    ratios = pd.DataFrame()
    g = data.groupby(feature)["Churn"].value_counts().to_frame()
    g = g.rename({"Churn": axis_name}, axis=1).reset_index()
    g[axis_name] = g[axis_name] / len(data)
    if orient == "v":
        ax = sns.barplot(x=feature, y=axis_name, hue="Churn", data=g, orient=orient)
        ax.set_yticklabels(["{:,.0%}".format(y) for y in ax.get_yticks()])
    else:
        if plot_type == "barplot":
            ax = sns.barplot(x=axis_name, y=feature, hue="Churn", data=g, orient=orient)
        elif plot_type == "countplot":
            ax = sns.catplot(y=feature, kind="count", hue="Churn", data=g, orient=orient)
plot_percentages("barplot", "SeniorCitizen")

sns.catplot(y="SeniorCitizen", hue="Churn", kind="count", palette="pastel", edgecolor=".6", data=data)

# Gender distribution
df = data["gender"].value_counts() * 100.0 / len(data)
ax = df.plot(kind="bar", stacked=True, rot=0)
ax.set_ylabel("% Customers")
ax.set_xlabel("Gender")
ax.set_title("Gender Distribution")

# Churn rates to gender
data["churn_rate"] = data["Churn"].replace("No", 0).replace("Yes", 1)
g = sns.FacetGrid(data, col="SeniorCitizen", height=4, aspect=.9)
ax = g.map(sns.barplot, "gender", "churn_rate", palette = "Blues_d", order=["Female", "Male"])

# Senior citizens
df = data["SeniorCitizen"].value_counts() * 100.0 / len(data)
ax = df.plot.pie(autopct="%.2f", labels=["No", "Yes"], figsize=(6,6), fontsize=13)
ax.set_ylabel("Senior Citizens", fontsize=13)
ax.set_title("Senior Citizens Distribution", fontsize=13)

# There are only 16% of the customers who are senior citizens. Thus most of our customers in the data are younger people

# Phone services
plt.figure(figsize=(9, 5))
plot_percentages("barplot", "MultipleLines", orient="h")

# Effect of multiple lines to MonthlyCharges
sns.catplot(x="MultipleLines", y="MonthlyCharges", hue="Churn", kind="violin",
            split=True, palette="pastel", data=data, height=4.5, aspect=1.6)

sns.catplot(x="MultipleLines", y="MonthlyCharges", hue="Churn", kind="bar",
            palette="pastel", edgecolor=".6", data=data)

# Internet services
plt.figure(figsize=(9, 5))
plot_percentages("barplot", "InternetService", orient="h")

'''
- Clients without internet have a very low churn rate
- Customers with fiber are more probable to churn than those with DSL connection
'''

sns.catplot(x="InternetService", y="MonthlyCharges", hue="Churn", kind="violin",
            split=True, palette="pastel", data=data, height=4.5, aspect=1.6);

sns.catplot(x="InternetService", y="MonthlyCharges", hue="Churn", kind="bar",
            palette="pastel", edgecolor=".6", data=data)

# Additional services (OnlineSecurity, OnlineBackup, DeviceProtection, TechSupport, StreamingTV, StreamingMovies)
cols = ["OnlineSecurity", "OnlineBackup", "DeviceProtection", "TechSupport", "StreamingTV", "StreamingMovies"]
df = pd.melt(data[data["InternetService"] != "No"][cols]).rename({'value': 'Has service'}, axis=1)
plt.figure(figsize=(10, 5))
sns.countplot(data=df, x="variable", hue="Has service").set(xlabel="Additional service", ylabel="Number of customers")
plt.show()

plt.figure(figsize=(10, 5))
df = data[(data.InternetService != "No") & (data.Churn == "Yes")]
df = pd.melt(df[cols]).rename({"value": "Has service"}, axis=1)
sns.countplot(data=df, x="variable", hue="Has service", hue_order=["No", "Yes"]).set(xlabel="Additional service", ylabel="Number of churns")
plt.show()

sns.catplot(y="TechSupport", kind="count", palette="pastel", edgecolor=".6", data=data)

'''
- Customers with the first 4 additionals (security to tech support) are more unlikely to churn
- Streaming service is not predictive for churn
'''

# Contract and payment
g = sns.FacetGrid(data, col="PaperlessBilling", height=5, aspect=1.0)
ax = g.map(sns.barplot, "Contract", "churn_rate", palette = "Blues_d", order=["Month-to-month", "One year", "Two year"])

plt.figure(figsize=(10, 5))
plot_percentages("barplot", "PaymentMethod", orient="h")

'''
- Customers with paperless billing are more probable to churn
- The preferred payment method is Electronic check with around 35% of customers. This method also has a very high churn rate
- Short term contracts have higher churn rates
'''

sns.catplot(x="Contract", y="MonthlyCharges", hue="Churn", kind="box", data=data, height=4.5, aspect=1.5)

sns.catplot(y="Churn", x="MonthlyCharges", row="PaymentMethod", kind="box", data=data, height=1.6, aspect=4, orient="h")

'''
- Longer contracts are more affected by higher monthly charges (for churn rate)
- Mailed checks have lower charges
- There is a huge gap in charges between customers that churn and those that don't with respect to Mailed Check
'''

# Correlation between features (Correlation heatmap using Pearson correlation coefficient method)
plt.figure(figsize=(15, 7))
data.drop(["customerID", "churn_rate", "total_charges_to_tenure_ratio", "monthly_charges_diff"], axis=1, inplace=True)
corr = data.apply(lambda x: pd.factorize(x)[0]).corr()
sns.heatmap(corr, xticklabels=corr.columns, yticklabels=corr.columns, linewidths=.2, cmap="RdYlBu")

# Correlation heatmap of numerical and binary columns with plotly
import plotly.offline as py
import plotly.graph_objs as go
import plotly.tools as tls
import plotly.subplots as subplots
import plotly.figure_factory as ff

# Correlation Matrix
data = pd.read_csv("telco_customer_churn.csv")
correlation = data.corr()

# Tick labels
matrix_cols = correlation.columns.tolist()

# Convert to array
corr_array = np.array(correlation)

# Plotting
trace = go.Heatmap(z = corr_array,
                   x = matrix_cols,
                   y = matrix_cols,
                   colorscale = "Viridis",
                   colorbar = dict(title = "Pearson Correlation coefficient",
                                   titleside = "right"))

layout = go.Layout(dict(title = "Correlation Matrix for variables",
                        autosize = False,
                        height = 720,
                        width = 800,
                        margin = dict(r = 0, l = 210, t = 25, b = 210),
                        yaxis = dict(tickfont = dict(size = 9)),
                        xaxis = dict(tickfont = dict(size = 9))))

data = [trace]
fig = go.Figure(data = data, layout = layout)
py.iplot(fig)

# https://towardsdatascience.com/better-heatmaps-and-correlation-matrix-plots-in-python-41445d0f2bec
# Correlation matrix with heatmapz

from heatmap import heatmap, corrplot

data = pd.read_csv("telco_customer_churn.csv")
plt.figure(figsize=(8,8))
corrplot(data.corr(), size_scale=300);

# Contracts information (month-to-month contract, two year contract, one year contract)
ax1 = sns.distplot(data[data["Contract"]=="Month-to-month"]["tenure"],
             hist=True, kde=False, bins=int(180/5), hist_kws={'edgecolor':'black'},
             kde_kws={'linewidth': 4})
ax1.set_ylabel('Number of Customers')
ax1.set_xlabel('Tenure (months)')
ax1.set_title('Month-to-month Contract')

ax2 = sns.distplot(data[data["Contract"]=="One year"]["tenure"],
             hist=True, kde=False, bins=int(180/5), hist_kws={'edgecolor':'black'},
             kde_kws={'linewidth': 4})
ax2.set_xlabel('Tenure (months)',size = 14)
ax2.set_title('One Year Contract',size = 14)

ax3 = sns.distplot(data[data["Contract"]=="Two year"]["tenure"],
                  hist=True, kde=False, bins=int(180/5), hist_kws={'edgecolor':'black'},
                  kde_kws={'linewidth': 4})
ax3.set_xlabel('Tenure (months)')
ax3.set_title('Two Year Contract')

# Additional Documents
# - https://www.kaggle.com/salibbigt/customer-churn-analysis (Explaratory data analysis with Tableau)


import numpy as np
import torch
import matplotlib.pyplot as plt
from torch import nn, optim, autograd
from torch.nn import functional as F

torch.manual_seed(1234)
np.random.seed(1234)

class Unit(nn.Module):

    def __init__(self, in_N, out_N):
        super(Unit, self).__init__()
        self.in_N = in_N
        self.out_N = out_N
        self.L = nn.Linear(in_N, out_N)

    def forward(self, x):
        x1 = self.L(x)
        x2 = torch.tanh(x1)
        return x2

class NN1(nn.Module):

    def __init__(self, in_N, width, depth, out_N):
        super(NN1, self).__init__()
        self.width = width
        self.in_N = in_N
        self.out_N = out_N
        self.stack = nn.ModuleList()

        self.stack.append(Unit(in_N, width))

        for i in range(depth):
            self.stack.append(Unit(width, width))

        self.stack.append(nn.Linear(width, out_N))

    def forward(self, x):
        # first layer
        for i in range(len(self.stack)):
            x = self.stack[i](x)
        return x

class NN2(nn.Module):
    def __init__(self, in_N, width, depth, out_N):
        super(NN2, self).__init__()
        self.in_N = in_N
        self.width = width
        self.depth = depth
        self.out_N = out_N

        self.stack = nn.ModuleList()

        self.stack.append(nn.Linear(in_N, width))

        for i in range(depth):
            self.stack.append(nn.Linear(width, width))

        self.stack.append(nn.Linear(width, out_N))

    def forward(self, x):
        # first layer
        for i in range(len(self.stack)):
            x = self.stack[i](x)
        return x

def weights_init(m):
    if isinstance(m, (nn.Conv2d, nn.Linear)):
        nn.init.xavier_normal_(m.weight)
        nn.init.constant_(m.bias, 0.0)

def main():
    x_hi = np.array([0, 0.4, 0.6, 1]).reshape(-1, 1)
    x_lo = np.linspace(0, 1, 11).reshape(-1, 1)
    y_hi_star = (6 * x_hi - 2) ** 2 * np.sin(12 * x_hi - 4)
    x = np.linspace(0, 1, 101).reshape(-1, 1)
    y_hi = (6 * x - 2) ** 2 * np.sin(12 * x - 4)
    y_lo = 0.5 * (6 * x - 2) ** 2 * np.sin(12 * x - 4) + 10 * (x - 0.5) - 5
    y_lo_star = 0.5 * (6 * x_lo - 2) ** 2 * np.sin(12 * x_lo - 4) + 10 * (x_lo - 0.5) - 5
    y_lo_star_prime = 10 + 6 * (6 * x_lo - 2) * np.sin(12 * x_lo - 4) + \
                      0.5 * (6 * x_lo - 2) ** 2 * np.cos(12 * x_lo - 4) * 12

    fig, ax = plt.subplots()
    ax.plot(x, y_hi, label='$y_H$', color='black')
    ax.plot(x, y_lo, label='$y_L$', color='black', linestyle='dashed')
    ax.scatter(x_hi, y_hi_star, color='red', linewidth='2', marker='x', label='high-fidelity training data')
    ax.scatter(x_lo, y_lo_star, color='', edgecolors='blue', marker='o', label='low-fidelity training data')
    ax.set(xlabel='x', ylabel='y')
    ax.legend()
    plt.show()
    in_N = 1
    width = 10
    depth = 2
    out_N = 1
    model_h = NN1(in_N, width, depth, out_N)
    model_h.apply(weights_init)
    optimizer = optim.Adam(model_h.parameters(), lr=0.001)
    nIter = 2000
    it = 0
    loss_value = 1
    while loss_value > 1e-3:
        pred_h = model_h(torch.from_numpy(x_hi).float())
        loss = torch.mean(torch.square(pred_h - torch.from_numpy(y_hi_star).float()))

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        loss_value = loss.item()
        if it % 50 == 0:
            print('It:', it, 'Loss', loss.item())
        it = it + 1
    nn_pred_h = model_h(torch.from_numpy(x).float())

    fig2, ax2 = plt.subplots()
    line = ax2.plot(x, nn_pred_h.detach().numpy(), label='DNN though HF', color='darkviolet')
    line[0].set_dashes([2, 2, 4, 2])  # 2pt line, 2pt break, 4pt line, 2pt break
    ax2.plot(x, y_hi, label='$Exact$', color='black')
    ax2.scatter(x_hi, y_hi_star, marker='x', color='red', linewidth=2)
    ax2.legend()
    plt.show()
    model_h = NN1(1, 20, 4, 1)
    model_h.apply(weights_init)
    optimizer = optim.Adam(model_h.parameters(), lr=1e-3)
    loss_value = 1
    x_lo_r = torch.from_numpy(x_lo).float()
    x_lo_r.requires_grad_()
    it = 0
    while loss_value > 1e-3:
        pred_h = model_h(x_lo_r)
        grads = autograd.grad(outputs=pred_h, inputs=x_lo_r,
                              grad_outputs=torch.ones_like(pred_h),
                              create_graph=True, retain_graph=True, only_inputs=True)[0]
        loss = torch.mean(torch.square(pred_h - torch.from_numpy(y_lo_star).float())) + \
               torch.mean(torch.sum(torch.square(grads - torch.from_numpy(y_lo_star_prime).float()), 1, keepdim=True))

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        loss_value = loss.item()
        if it % 50 == 0:
            print('It:', it, 'Loss', loss.item())
        it = it + 1
    nn_pred_h = model_h(torch.from_numpy(x).float())
    fig3, ax3 = plt.subplots()
    line = ax3.plot(x, nn_pred_h.detach().numpy(), label='DNN though LF', color='darkviolet')
    line[0].set_dashes([2, 2, 4, 2])  # 2pt line, 2pt break, 4pt line, 2pt break
    ax3.plot(x, y_lo, label='$Exact$', color='black')
    ax3.scatter(x_lo, y_lo_star, marker='x', color='red', linewidth=2)
    ax3.legend()
    plt.show()

    alpha = torch.tensor([0.5])
    model3 = NN1(2, 20, 4, 1)
    model4 = NN2(2, 10, 2, 1)
    model3.apply(weights_init)
    model4.apply(weights_init)
    optimizer2 = optim.Adam([{'params': model3.parameters(), 'weight_decay': 0.01},
                             {'params': model_h.parameters(), 'weight_decay': 0.01},
                             {'params': model4.parameters(), 'weight_decay': 0.01},
                             {'params': alpha}], lr=1e-3)
    nIter2 = 10000
    x_lo_r = torch.from_numpy(x_lo).float()
    x_lo_r.requires_grad_()
    loss2_value = 1
    it = 0
    # x_lo_r.requires_grad_()
    while loss2_value > 1e-3 and it < nIter2:
        pred_h = model_h(x_lo_r)
        grads = autograd.grad(outputs=pred_h, inputs=x_lo_r,
                              grad_outputs=torch.ones_like(pred_h),
                              create_graph=True, retain_graph=True, only_inputs=True)[0]
        loss3 = torch.mean(torch.square(pred_h - torch.from_numpy(y_lo_star).float())) + \
               torch.mean(torch.sum(torch.square(grads - torch.from_numpy(y_lo_star_prime).float()), 1, keepdim=True))

        pred_2h = model_h(torch.from_numpy(x_hi).float())
        pred_2 = alpha * model3(torch.cat((torch.from_numpy(x_hi).float(), pred_2h), 1)) + \
                 (1 - alpha) * model4(torch.cat((torch.from_numpy(x_hi).float(), pred_2h), 1))
        loss2 = torch.mean(torch.square(pred_2 - torch.from_numpy(y_hi_star).float())) + loss3
        loss2_value = loss2.item()
        optimizer2.zero_grad()
        loss2.backward()
        optimizer2.step()
        if it % 100 == 0:
            print('It:', it, 'Loss:', loss2.item())
        it = it + 1
    xx_lo = model_h(torch.from_numpy(x).float())
    xx_high = alpha * model3(torch.cat((torch.from_numpy(x).float(), xx_lo), 1)) +\
              (1 - alpha) * model4(torch.cat((torch.from_numpy(x).float(), xx_lo), 1))
    print(alpha)
    fig4, ax4 = plt.subplots()
    ax4.plot(x, xx_high.detach().numpy(), label='DNN though multi-fidelity model', color='darkviolet')
    ax4.plot(x, y_hi, label='$Exact$', color='black')
    plt.legend()
    plt.show()

if __name__ == '__main__':
    main()


"""
Neural network approximation Q-Learning with prioritized experience replay
experience replay implementation taken from: https://github.com/Damcy/prioritized-experience-replay
"""
import time
import numpy as np
import tensorflow as tf
import numpy
import os
import pickle
from tfboard import TensorboardWriter
from prioritized_memory import Memory
from env_time import Environment
import multiprocessing

class QLearning:

    def __init__(
            self,
            k,
            d,
            n_features,
            n_actions,
            n_hidden,
            save_and_load_path,
            load,
            tensorboard_path,
            learning_rate=0.005,
            reward_decay=0.9,
            e_greedy=0.8,
            replace_target_iter=500,
            save_model_iter=5000,
            memory_capacity=10000,
            batch_size=32,
            e_greedy_increment=0.0001,
            prioritized=True,
            planning=False
    ):
        self.env = Environment(k=k, d=d)
        self.n_features = n_features
        self.n_actions = n_actions
        self.n_hidden = n_hidden
        self.save_and_load_path = save_and_load_path
        self.load = load
        self.tensorboard_path = tensorboard_path

        self.lr = learning_rate
        self.gamma = reward_decay
        self.epsilon_max = e_greedy
        self.replace_target_iter = replace_target_iter
        self.save_model_iter = save_model_iter
        self.memory_capacity = memory_capacity
        self.batch_size = batch_size
        self.epsilon_increment = e_greedy_increment
        self.epsilon = 0 if e_greedy_increment is not None else self.epsilon_max
        self.prioritized = prioritized  # decide to use double q or not
        self.planning = planning  # decide to use planning for additional learning

        self.learn_step_counter = 0

        # create a graph for model variables and session
        self.graph = tf.Graph()
        self.sess = tf.Session(graph=self.graph)
        if not load:
            with self.graph.as_default():
                self._build_net()
                self.sess.run(tf.global_variables_initializer())
            self.memory = Memory(prioritized=self.prioritized, capacity=self.memory_capacity,
                                 n_features=self.n_features, n_actions=self.n_actions,
                                 batch_size=self.batch_size, planning=self.planning,
                                 qsa_feature_extract=self.env.step_state,
                                 qsa_feature_extract_for_all_acts=self.env.all_possible_next_states)
        else:
            self.load_model()

        self.tb_writer = TensorboardWriter(folder_name=self.tensorboard_path, session=self.sess)
        self.memory_lock = multiprocessing.Lock()     # lock for memory modification

    def save_model(self):
        with self.graph.as_default():
            saver = tf.train.Saver()
            path = saver.save(self.sess, self.save_and_load_path)
            self.memory_lock.acquire()
            with open(self.save_and_load_path + '_memory.pickle', 'wb') as f:
                pickle.dump(self.memory, f, protocol=-1)   # -1: highest protocol
            self.memory_lock.release()
            print('save model to', path)

    def load_model(self):
        with self.graph.as_default():
            saver = tf.train.import_meta_graph(self.save_and_load_path + '.meta')
            saver.restore(self.sess, tf.train.latest_checkpoint(os.path.dirname(self.save_and_load_path)))
            # placeholders
            self.s = self.graph.get_tensor_by_name('s:0')  # Q(s,a) feature
            self.s_ = self.graph.get_tensor_by_name('s_:0')  # Q(s',a') feature
            self.rewards = self.graph.get_tensor_by_name('reward:0')   # reward
            self.terminal_weights = self.graph.get_tensor_by_name('terminal:0')  # terminal
            # variables
            self.q_eval = self.graph.get_tensor_by_name('eval_net/q_eval:0')
            self.eval_w1 = self.graph.get_tensor_by_name('eval_net/l1/w1:0')
            self.eval_b1 = self.graph.get_tensor_by_name('eval_net/l1/b1:0')
            self.eval_w2 = self.graph.get_tensor_by_name('eval_net/l2/w2:0')
            self.eval_b2 = self.graph.get_tensor_by_name('eval_net/l2/b2:0')
            self.q_next = self.graph.get_tensor_by_name('eval_net/q_next:0')
            self.q_target = self.graph.get_tensor_by_name("q_target:0")
            self.is_weights = self.graph.get_tensor_by_name("is_weights:0")
            self.loss = self.graph.get_tensor_by_name("loss:0")
            self.abs_errors = self.graph.get_tensor_by_name("abs_errors:0")
            # operations
            self.train_op = self.graph.get_operation_by_name('train_op')

        with open(self.save_and_load_path + '_memory.pickle', 'rb') as f:
            self.memory = pickle.load(f)  # -1: highest protocol

    def _build_net(self):
        self.s = tf.placeholder(tf.float32, [None, self.n_features], name='s')  # Q(s,a) feature
        self.s_ = tf.placeholder(tf.float32, [None, self.n_actions, self.n_features], name='s_')  # Q(s',a') feature
        self.rewards = tf.placeholder(tf.float32, [None], name='reward')  # reward
        self.terminal_weights = tf.placeholder(tf.float32, [None], name='terminal')  # terminal

        w_initializer, b_initializer = \
            tf.random_normal_initializer(0., 0.3), tf.constant_initializer(0.1)  # config of layers

        # ------------------ build evaluate_net ------------------
        with tf.variable_scope('eval_net'):
            # s is Q(s,a) feature, shape: (n_sample, n_features)
            # s_ Q(s',a') for all a' feature, shape: (n_sample, n_actions, n_features)
            with tf.variable_scope('l1'):
                self.eval_w1 = tf.get_variable('w1', [self.n_features, self.n_hidden], initializer=w_initializer)
                self.eval_b1 = tf.get_variable('b1', [self.n_hidden], initializer=b_initializer)
                # l1 shape: (n_sample, n_hidden)
                l1 = tf.nn.relu(tf.matmul(self.s, self.eval_w1) + self.eval_b1)
                # l1 shape: shape: (n_sample, n_actions, n_hidden)
                l1_ = tf.nn.relu(tf.einsum('ijk,kh->ijh', self.s_, self.eval_w1) + self.eval_b1)
            with tf.variable_scope('l2'):
                self.eval_w2 = tf.get_variable('w2', [self.n_hidden, 1], initializer=w_initializer)
                self.eval_b2 = tf.get_variable('b2', [1], initializer=b_initializer)
                # out shape: (n_sample, 1)
                out = tf.matmul(l1, self.eval_w2) + self.eval_b2
                # out_ shape: (n_sample, n_actions, 1), Q(s',a') for all a' feature
                out_ = tf.einsum('ijh,ho->ijo', l1_, self.eval_w2) + self.eval_b2
            self.q_eval = tf.squeeze(out, name='q_eval')
            self.q_next = tf.squeeze(out_, name='q_next')

        # ------------------ loss function ----------------------
        # importance sampling weight
        self.q_target = tf.add(self.rewards,
                               self.terminal_weights * (self.gamma * tf.reduce_max(self.q_next, axis=1)),
                               name='q_target')
        # importance sampling weight
        self.is_weights = tf.placeholder(tf.float32, [None], name='is_weights')
        self.loss = tf.reduce_mean(self.is_weights * tf.squared_difference(self.q_target, self.q_eval), name='loss')
        self.abs_errors = tf.abs(self.q_target - self.q_eval, name='abs_errors')
        self.train_op = tf.train.RMSPropOptimizer(self.lr).minimize(self.loss, name='train_op')

    def store_transition(self, s, a, r, s_, terminal):
        self.memory_lock.acquire()
        # transition is a tuple (current_state, action, reward, next_state, whether_terminal)
        self.memory.store((s, a, r, s_, terminal))
        self.memory_lock.release()

    def update_memory_priority(self, exp_ids, abs_errors):
        """ update memory priority """
        self.memory_lock.acquire()
        self.memory.update_priority(exp_ids, abs_errors)
        self.memory_lock.release()

    def choose_action(self, state, next_possible_states, next_possbile_actions, epsilon_greedy=True):
        pred_q_values = self.sess.run(self.q_eval, feed_dict={self.s: next_possible_states}).flatten()
        if not epsilon_greedy or np.random.uniform() < self.epsilon:
            action_idx = np.argmax(pred_q_values)
        else:
            action_idx = np.random.choice(numpy.arange(len(next_possbile_actions)))
        action = next_possbile_actions[action_idx]
        pred_q_value = pred_q_values[action_idx]
        return action, pred_q_value

    # def _replace_target_params(self):
    #     with self.graph.as_default():
    #         t_params = tf.get_collection('target_net_params')
    #         e_params = tf.get_collection('eval_net_params')
    #         self.sess.run([tf.assign(t, e) for t, e in zip(t_params, e_params)])
    #         print('target_params_replaced')

    def planning_learn(self, qsa_next_features, qsa_features):
        """
        additional learning from planning:
        for a sample s, a, r, s', we know all 's,'a that lead to s'
        :param qsa_next_features: features for Q(s',a') for all a'
        :param qsa_features: features for Q('s,'a) for all 'a which leads to s'
        """
        pass

    def learn(self, MEMORY_CAPACITY_START_LEARNING):
        while True:
            if self.memory_size() < MEMORY_CAPACITY_START_LEARNING:
                print('wait for more samples')
                time.sleep(1)
                continue
            #
            # if self.learn_step_counter % self.replace_target_iter == 0:
            #     self._replace_target_params()

            if self.learn_step_counter % self.save_model_iter == 0:
                self.save_model()

            qsa_feature, qsa_features, qsa_next_features, rewards, terminal_weights, is_weights, exp_ids \
                = self.memory.sample()

            _, loss, abs_errors = self.sess.run([self.train_op, self.loss, self.abs_errors],
                                                feed_dict={self.s: qsa_feature,
                                                           self.s_: qsa_next_features,
                                                           self.rewards: rewards,
                                                           self.terminal_weights: terminal_weights,
                                                           self.is_weights: is_weights})

            if self.prioritized:
                self.update_memory_priority(exp_ids, abs_errors)

            if self.planning:
                self.planning_learn(qsa_feature)

            self.epsilon = self.cur_epsilon()
            self.learn_step_counter += 1
            print('learn at memory virtual size:', self.memory_virtual_size())

    def cur_epsilon(self):
        return self.epsilon + self.epsilon_increment if self.epsilon < self.epsilon_max else self.epsilon_max

    def tb_write(self, tags, values, step):
        """ write to tensorboard """
        self.tb_writer.write(tags, values, step)

    def memory_size(self):
        return self.memory.size

    def memory_virtual_size(self):
        return self.memory.virtual_size

    def collect_samples(self, EPISODE_SIZE, TRIAL_SIZE, MEMORY_CAPACITY_START_LEARNING, TEST_PERIOD, RANDOM_SEED):
        for i_episode in range(EPISODE_SIZE):
            cur_state = self.env.reset()
            for i_epsisode_step in range(TRIAL_SIZE):
                next_possible_states, next_possible_actions = self.env.all_possible_next_state_action(cur_state)
                action, _ = self.choose_action(cur_state, next_possible_states, next_possible_actions,
                                               epsilon_greedy=True)
                cur_state_, reward = self.env.step(action)
                terminal = True if i_epsisode_step == TRIAL_SIZE - 1 else False
                self.store_transition(cur_state, action, reward, cur_state_, terminal)
                cur_state = cur_state_
            print('episode ', i_episode, ' finished with value', self.env.output(cur_state),
                  '\tcur_epsilon', self.cur_epsilon(), '\tmem_size', self.memory_virtual_size())

            if self.memory_virtual_size() >= MEMORY_CAPACITY_START_LEARNING and i_episode % TEST_PERIOD == 0:
                cur_state = self.env.reset()
                for i_episode_test_step in range(TRIAL_SIZE):
                    next_possible_states, next_possible_actions = self.env.all_possible_next_state_action(cur_state)
                    action, q_val = self.choose_action(cur_state, next_possible_states, next_possible_actions,
                                                       epsilon_greedy=False)
                    cur_state, reward = self.env.step(action)
                    test_output = self.env.output(cur_state)
                    print('TEST step {0}, output: {1}, at {2}, qval: {3}, reward {4}'.
                          format(i_episode_test_step, test_output, cur_state, q_val, reward))

                self.tb_write(tags=['Prioritized={0}, gamma={1}, seed={2}/Test Ending Output'.
                                format(self.prioritized, self.gamma, RANDOM_SEED),
                                      'Prioritized={0}, gamma={1}, seed={2}/Test Ending Qvalue'.
                                format(self.prioritized, self.gamma, RANDOM_SEED),
                                      ],
                                values=[test_output,
                                        q_val],
                                step=self.learn_step_counter)


import sys

from PyQt5 import Qt, QtCore, QtGui
from PyQt5.QtWidgets import(
    QApplication,
    QWidget,
    QStyle,
    QPushButton
)

class MainWindow(QWidget):
    """Main Window"""
    def __init__(self):
        super().__init__()
        button = QPushButton("push")
        button.setParent(self)
        style = button.style()
        icon = style.standardIcon(QStyle.SP_MessageBoxCritical)
        button.setIcon(icon)

app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()


#import OpenGL
#OpenGL.FULL_LOGGING = True
import functools
import logging
from PySide.QtOpenGL import QGLFormat

logging.basicConfig()
LOG = logging.getLogger(__name__)
from time import time
from PySide import QtCore, QtGui
from UI import QAppCtx, QGLView, QTimeline, createAction
from UI.QGLViewer import Camera
from UI import GLGrid, GLPoints2D, GLPoints3D, GLSkel, GLCameras
from UI import DRAWOPTS, DRAWOPT_ALL, DRAWOPT_DEFAULT, DRAWOPT_AXES
import IO

class CameraSwitchMenu(QtGui.QMenu):
	''' menu that allows selecting the current camera from a list
	the menu has a pointer to a :class:`UI.QGLViewer.QGLView` and reads it's cameras attribute directly.
	it will then set the current camera in the view by directly setting it's camera attribute
	the camera list is built dynamically when the menu is about to be shown
	'''
	def __init__(self, name, parent=None):
		if not parent:
			# windows pyside 1.1.2 will crash if this is not constructed with a parent
			raise RuntimeError("CameraSwitchMenu must be passed a parent widget")
		super(CameraSwitchMenu, self).__init__(name, parent=parent)

		#: pointer to the :class:`UI.QGLView` that i'll build the camera list from
		self.glview = None

		# add the actions before showing. (this can't be in the showEvent or the menu is not
		# resized properly and may potentially go off the bottom of the screen)
		self.aboutToShow.connect(self._addActions)

	@QtCore.Slot()
	def _addActions(self):
		''' create an action for each camera whenever the menu is about shown '''
		for camera in self.glview.cameras:
			ca = QtGui.QAction(camera.name, self)
			ca.setCheckable(True)
			ca.setChecked(self.glview.camera == camera)
			ca.triggered.connect(functools.partial(self.setCamera, camera))
			self.addAction(ca)

	def hideEvent(self, event):
		''' remove all the cameras on hide (the list is freshly created on show) '''
		super(CameraSwitchMenu, self).hideEvent(event)
		for action in self.actions():
			self.removeAction(action)

	@QtCore.Slot()
	def setCamera(self, camera):
		''' connected to the camera actions to switch the current camera in the view '''
		self.glview.camera = camera
		self.glview.updateGL()

class GPanel(QtGui.QFrame):
	''' this is the base class for all GRIP panels. '''
	def __init__(self, parent=None):
		super(GPanel, self).__init__(parent=parent)
		self.setFrameStyle(QtGui.QFrame.Sunken)
		# 1px frame around the panel that has focus
		self.setStyleSheet("""QFrame:focus {border: 1px solid #FFFFFF;}""")

		# menus
		self.menuBar = QtGui.QMenuBar(self)

		layout = QtGui.QVBoxLayout()
		layout.setContentsMargins(1, 0, 1, 1)
		layout.setSpacing(0)
		layout.addWidget(self.menuBar)
		self.setLayout(layout)

class QGLPanel(GPanel):
	''' contains a QGLView and menus'''
	def __init__(self, grid=True, parent=None):
		super(QGLPanel, self).__init__(parent=parent)
		self.setFrameStyle(QtGui.QFrame.Sunken)

		# mask of drawable stuff (axes/bones/labels etc). don't show axes by default
		self.drawOpts = DRAWOPT_DEFAULT

		#: The QGLView widget, access this directly, there's no .view() getter
		self.view = QGLView(parent=self)

		# add grid
		if grid:
			self.view.primitives.append(GLGrid())
		self.view.drawOpts = self.drawOpts

		cameraMenu = QtGui.QMenu("&Camera")
		self.showMenu = QtGui.QMenu("Show")

		# the camera switch menu needs a pointer to the view so that it can get the list of cameras
		# and switch the current one
		camSwitchMenu = CameraSwitchMenu("Switch", parent=self)
		camSwitchMenu.glview = self.view
		cameraMenu.addMenu(camSwitchMenu)

		#cameraMenu.addAction(createAction('Next Camera', self, [functools.partial(self.view.cycleCamera, 1)]))
		#cameraMenu.addAction(createAction('Previous Camera', self, [functools.partial(self.view.cycleCamera, -1)]))
		cameraMenu.addAction(createAction('Reset', self, [self.resetCamera3D, self.resetCamera2D]))
		cameraMenu.addAction(createAction('Reset 3D', self, [self.resetCamera3D]))
		cameraMenu.addAction(createAction('Reset 2D', self, [self.resetCamera2D]))

		# show menu
		self.showMenu.addAction(createAction('Frame Selected', self, [self.view.frame]))
		self.showMenu.addAction(createAction('Show All', self, [functools.partial(self.setAllDrawOptions, True)]))
		self.showMenu.addAction(createAction('Show None', self, [functools.partial(self.setAllDrawOptions, False)]))
		for opt in sorted(DRAWOPTS):
			a = createAction(opt, self, [functools.partial(self.toggleDrawOption, DRAWOPTS[opt])], checkable=True, checked=bool(DRAWOPTS[opt] & self.drawOpts))
			self.showMenu.addAction(a)

		self.menuBar.addMenu(cameraMenu)
		self.menuBar.addMenu(self.showMenu)

		self.layout().addWidget(self.view)
		self.layout().setStretch(1, 1)

	def frame(self):
		self.view.frame()
		self.view.updateGL()

	def resetCamera3D(self):
		self.view.camera.reset3D()
		self.view.updateGL()

	def resetCamera2D(self):
		self.view.camera.reset2D()
		self.view.updateGL()

	def setAllDrawOptions(self, state):
		'''set all draw options on or off

		:param bool state: True = on'''
		for a in self.showMenu.actions(): a.setChecked(state)
		self.view.drawOpts = [0, DRAWOPT_ALL][state]
		self.view.updateGL()

	def toggleDrawOption(self, opt):
		'''toggle the provided draw option

		:param int opt: one or a combination of :`data:UI.DRAWOPTS` values'''
		self.view.drawOpts = self.view.drawOpts ^ opt
		self.view.updateGL()

class QGViewer(QtGui.QMainWindow):
	def __init__(self, parent=None):
		super(QGViewer, self).__init__(parent=parent)

		self.setWindowTitle('Imaginarium Viewer')
		self.setMinimumWidth(640)
		self.setMinimumHeight(480)

		self.menus = {}
		#: list of all the GL* things to draw. the views all refer to this
		self.primitives = []
		self.primitives2D = []

		self.setTabPosition(QtCore.Qt.TopDockWidgetArea, QtGui.QTabWidget.North)
		self.setTabPosition(QtCore.Qt.RightDockWidgetArea, QtGui.QTabWidget.East)
		self.setTabPosition(QtCore.Qt.LeftDockWidgetArea, QtGui.QTabWidget.West)
		self.setTabPosition(QtCore.Qt.BottomDockWidgetArea, QtGui.QTabWidget.North)

		menuBar = QtGui.QMenuBar(self)
		self.setMenuBar(menuBar)
		self.getOrCreateMenu('&File')

		self.createWidgets()
		self.createMenus()
		self.createLayout()
		self.createConnections()

		self.new()

	def createMenus(self):
		self.getOrCreateMenu('&File').addAction(createAction('&New', self, [self.new], tip='Create a new scene'))
		# hack. maybe make a getOrCreateAction thing like the menus so i can get them by name
		# (this is so i can insert the import action before it, so it's not very robust or good)
		self._exitAction = createAction('Exit', self, [self.close], tip='Quit the application')
		self.getOrCreateMenu('&File').addAction(self._exitAction)
		self.getOrCreateMenu('&Edit')
		self.getOrCreateMenu('&View').addAction(createAction('Show status bar', self, [lambda:self.statusBar().setVisible(not self.statusBar().isVisible())],checkable=True, checked=True,  tip='Toggle the help/status bar on or off'))

	def createWidgets(self):
		# widgets
		self._mainWidget = QtGui.QWidget(self)
		self.setCentralWidget(self._mainWidget)
		self.statusBar().showMessage("Starting up...", 1000) # this shows the status bar too
		self.timeline = QTimeline(self)
		self.addDock('Timeline', self.timeline,
					QtCore.Qt.BottomDockWidgetArea | QtCore.Qt.TopDockWidgetArea,
					QtCore.Qt.BottomDockWidgetArea,
					QtGui.QDockWidget.DockWidgetMovable | QtGui.QDockWidget.DockWidgetVerticalTitleBar,
					useTitle=False)
		self._panel = QGLPanel(grid=False)
		self.view().primitives = self.primitives
		self.view().primitives2D = self.primitives2D

	def createConnections(self):
		# when the frame rate changes, reset the lastTimes cache in the view so it can update
		# rate hud display more accurately sooner
		self.timeline.rateChanged.connect(lambda: self.view().__setattr__('lastTimes', [time()]))

	def createLayout(self):
		# layout
		self.viewLayout = QtGui.QVBoxLayout()
		self.viewLayout.setContentsMargins(0, 0, 0, 0)
		self.viewLayout.setSpacing(0)
		self.viewLayout.addWidget(self._panel)
		self._mainWidget.setLayout(self.viewLayout)

	def getOrCreateMenu(self, menuName, before=None):
		try:
			return self.menus[menuName]
		except KeyError:
			if before:
				m = QtGui.QMenu(menuName)
				self.menuBar().insertMenu(self.menus[before].menuAction(), m)
				self.menus[menuName] = m
			else:
				self.menus[menuName] = self.menuBar().addMenu(menuName)
			return self.menus[menuName]

	def addDock(self, title, obj, allowed, start, features, useTitle=True):
		dock = QtGui.QDockWidget(self)
		if useTitle: dock.setWindowTitle(title)
		dock.setObjectName(title) # required so that save settings doesn't barf
		dock.setWidget(obj)
		dock.setAllowedAreas(allowed)
		dock.setFeatures(features)
		dock.layout().setContentsMargins(0, 0, 0, 0)
		dock.layout().setSpacing(0)
		self.addDockWidget(start, dock)
		self.getOrCreateMenu('&View').addAction(createAction('Show %s' % title, self, [lambda:dock.setVisible(not dock.isVisible())], checkable=True, checked=True, tip='Toggle whether the %s is displayed' % title))

	def view(self):
		return self._panel.view

	def updateGL(self):
		self.view().updateGL()

	def refreshImageData(self):
		self.view().refreshImageData()

	def new(self):
		del self.primitives[:]
		self.view().points = None
		self.primitives.append(GLGrid())
		self.view().camera.reset2D()
		self.view().camera.reset3D()
		self.timeline.setRange(1, 100)
		self.updateGL()

	def addPoints2D(self, points):
		'''
		add 2d points to the viewer.

		:type points: :class:`numpy.array` (Nx2)
		:param points: array of vertices
		'''
		glPoints = GLPoints2D(points)
		self.primitives2D.append(glPoints)
		return self.primitives2D[-1]

	def addPoints3D(self, points):
		'''
		add 3d points to the viewer.

		:type points: :class:`numpy.array` (Nx3)
		:param points: array of vertices
		'''
		glPoints = GLPoints3D(points)
		self.primitives.append(glPoints)
		return self.primitives[-1]

	def addCameras(self, mats, camera_ids, movies=None):
		if movies == None: movies = [None]*len(mats)
		for mat, cid, md in zip(mats, camera_ids, movies):
			camera = Camera(cid)
			camera.setP(mat[2], distortion=mat[3])
			camera.setResetData()
			if md != None: camera.setImageData(md['vbuffer'],md['vheight'],md['vwidth'],3)
			self.view().addCamera(camera)
		cams = GLCameras(camera_ids, mats)
		self.primitives.append(cams)

if __name__ == '__main__':
	with QAppCtx():
		dialog = QGViewer()
		dialog.show()


import requests
import urllib.parse
import json

class TvMaze:
    """
    Gets all important information about a show
    """
    def __init__(self, show):
        self.api_root_url = 'http://api.tvmaze.com'
        self.show = show

    def get_show_info(self):
        proxies = {
            'http': 'one.proxy.att.com:8080'
        }
        q = '/singlesearch/shows?q={}&embed=episodes'.format(urllib.parse.quote_plus(self.show))
        r = requests.get('{}{}'.format(self.api_root_url, q))
        return json.loads(r.text)

if __name__ == '__main__':
    tvmaze = TvMaze('hate thy neighbor')
    info = tvmaze.get_show_info()
    episodes = info['_embedded']['episodes']


import ast
import collections
import json
import re
import time

import colorlabels as cl

from util import (csv_reader, data_source_file, file_read_contents,
                  file_read_lines, file_write_contents, open_html_in_browser,
                  re_sub_literal, report_file, twlda_result_file,
                  twlda_source_file, visual_file)

def parse_user_topic(desc, encoding='utf-8'):
    logfilename = twlda_result_file('%s/TopicsDistributionOnUsers.txt' % desc)
    user_topic = []

    for line in file_read_lines(logfilename, encoding=encoding):
        data_line = line.strip().split('\t')

        if not data_line:
            continue

        data_line[0] = data_line[0][:-4]  # Remove '.txt'
        data_line[1:] = list(map(float, data_line[1:]))
        user_topic.append(data_line)

    return user_topic

def parse_topic_words(desc, encoding='utf-8'):
    topic_words = collections.defaultdict(list)
    logfilename = twlda_result_file('%s/WordsInTopics.txt' % desc)
    content = file_read_contents(logfilename, encoding=encoding)

    for topic in content.split('Topic ')[1:]:
        topic_id, words = topic.split(':')
        topic_id = int(topic_id)
        for word in words.strip().split('\n'):
            w, p = word.strip().split('\t')
            topic_words[topic_id].append((w, float(p)))

    return topic_words

def load_user_info(userinfofile):
    user_info = {}

    for row in csv_reader(userinfofile):
        row['favourites_count'] = int(row['favourites_count'])
        row['followers_count'] = int(row['followers_count'])
        row['friends_count'] = int(row['friends_count'])
        row['listed_count'] = int(row['listed_count'])
        row['statuses_count'] = int(row['statuses_count'])
        row['verified'] = ast.literal_eval(row['verified'])
        user_info[row['screen_name']] = row

    return user_info

def show_topic_words(topic_words, topic_id):
    sum_prob = sum(p for w, p in topic_words[topic_id])
    return [{
        'word': w,
        'prob': p / sum_prob,
        'text': []
    } for w, p in topic_words[topic_id]]

def organize_data(desc, user_topic, topic_words, user_info, topusers):
    num_topics = len(topic_words)

    topics = [{
        'id': i,
        'words': show_topic_words(topic_words, i),
        'users': [],
        'showAllWords': False,
        'showAllUsers': False,
    } for i in range(num_topics)]

    for i, topic in enumerate(topics):
        user_topic = sorted(user_topic, key=lambda x: x[i + 1], reverse=True)

        for item in user_topic[:topusers]:
            sourcefilename = twlda_source_file('%s/%s.txt' % (desc, item[0]))
            text = file_read_contents(sourcefilename)
            topic['users'].append({
                            'username': item[0],
                            'weight': item[i + 1],
                            'info': user_info.get(item[0]) or {},
                            'text': [t for t in text.split('\n') if t]
                        })

            for line in text.split('\n'):
                for word in topic['words']:
                    if word['word'].lower() in line.lower():
                        word['text'].append(line)

        topic['weight'] = sum(item[i + 1] for item in user_topic)

    topics = sorted(topics, key=lambda x: x['weight'], reverse=True)

    for i, topic in enumerate(topics):
        topic['rank'] = i + 1

    return topics

def export_html(keyword, desc, data, portable, open_browser):
    data = json.dumps({
        'keyword': keyword,
        'description': desc,
        'topics': data,
        'currentWord': {'text': []},
        'currentUser': {'info': {}, 'text': []}
    }, separators=(',', ':'))

    if portable:
        template = file_read_contents(visual_file('template.html'))
        html = re.sub(r'<script src="data.js">(.*)', '', template)
        html = re_sub_literal(r'var data =(.*)', 'var data = ' + data, html)

        reportfilename = 'ldavisual-%s-%s.html' \
            % (desc, time.strftime('%Y%m%d%H%M%S'))
        reportfile = report_file(reportfilename)
        file_write_contents(reportfile, html)
        cl.success('Visualization saved as: %s' % reportfilename)

        if open_browser:
            open_html_in_browser(reportfile)

        return reportfilename
    else:
        file_write_contents(visual_file('data.js'), 'var data = ' + data)
        cl.success('Visualization data saved as: data.js')
        return None

def visualization_twlda(keyword, desc, desc_show, userinfofile, topusers=20,
                        encoding='utf-8', portable=True, open_browser=True):
    cl.section('Twitter-LDA Visualization')

    user_topic = parse_user_topic(desc, encoding=encoding)
    topic_words = parse_topic_words(desc, encoding=encoding)
    user_info = load_user_info(data_source_file(userinfofile))
    result = organize_data('test', user_topic, topic_words, user_info,
                           topusers)
    return export_html(keyword, desc_show, result, portable, open_browser)


import json

class App:
    main_names = dict()

    def __init__(self, file):
        self.file = file
        try:
            with open(file, 'r') as f:
                self.main_names = json.load(f)
        except OSError:
            print("No such file, will do a new one")
            f = open(file, 'w+')
            f.close()
        print("Initialized")

    def run(self):
        run = True
        while run:
            print("1. Print all aims")                                   # display_aims()
            print("1. Print Special Aim")                                # display_aims(aim)
            print("1. Print Special SubAim of Aim")                      # display_aims(aim, sub)
            print("1. Print Special Task of SubAim of Aim")              # display_aims(aim, sub, task)
            print("1. Print non completed Tasks")
            print("2. Delete Aim")
            print("2. Delete Aim's SubAim")
            print("2. Delete Aim's SubAim's Task")
            print("3. Add an Aim")                                       # add_aim()
            print("3. Add a  SubAim")                                   # add_sub_aim()
            print("3 Add a task")                                       # add_task()
            print("4. Change an Aim")
            print("4. Change a SubAim of Aim")
            print("4. Change a task of SubAim of Aim")                  # change_task()
            print("4. Change a value of SubAm of Aim")                  # change_task_value()
            print("4. Change step by step")                             # change_step_by_step()
            print("4. Change by concrete path")                         # change_by_concrete_path()
            print("5. exit")
            answer = int(input())
            if answer == 1:
                self.display_print_menu()
            if answer == 2:
                self.display_delete_menu()
            if answer == 3:
                self.display_add_menu()
            if answer == 4:
                self.display_change_menu()
            if answer == 5:
                run = False

    def display_print_menu(self):
        run = True
        while run:
            print("1. Print all aims")  # display_aims()
            print("2. Print Special Aim")  # display_aims(aim)
            print("3. Print Special SubAim of Aim")  # display_aims(aim, sub)
            print("4. Print Special Task of SubAim of Aim")  # display_aims(aim, sub, task)
            print("5. Print non completed Tasks")
            print("6. Go back")
            answer = int(input())
            if answer == 1:
                self.display_aims()
            elif answer == 2:
                self.display_aims(main="get")
            elif answer == 3:
                self.display_aims(main="get", sub_aim="get")
            elif answer == 4:
                self.display_aims(main="get", sub_aim="get", task="get")
            elif answer == 5:
                self.display_list_to_do()
            elif answer == 6:
                run = False
            else:
                print("Enter a digit in range 1-4")

    def display_change_menu(self):
        run = True
        while run:
            print("How do you want to change everything?")
            print("1. Change an Aim")                   # change_aim_name()
            print("2. Change a SubAim of Aim")          # change_sub_aim_name()
            print("3. Change a task of SubAim of Aim")  # change_task()
            print("3. Change a value of SubAm of Aim")  # change_task_value()
            print("3. Change step by step")  # change_step_by_step()
            print("3. Change by concrete path")  # change_by_concrete_path()
            print("4. Go back")
            print("Enter in in format: 'main_aim sub_aim task'")
            print("if you don't know the task or sub_aim, fill it with None")
            answer = int(input("Answer: "))
            if answer == 1:
                self.change_aim_name()
            elif answer == 2:
                self.change_sub_aim_name()
            elif answer == 3:
                print("1. Step by step")
                print("2. by path 'main sub task'")
                print("else go back")
                answer_to_four = int(input())
                if answer_to_four == 1:
                    self.change_step_by_step()
                elif answer_to_four == 2:
                    self.change_by_concrete_path()
            elif answer == 4:
                run = False

    def display_add_menu(self):
        run = True
        while run:
            print("1. Add an Aim")
            print("2. Add a  SubAim")
            print("3. Add a task")
            print("4. Exit")
            answer = int(input())
            if answer == 1:
                self.add_aim()
            elif answer == 2:
                self.add_sub_aim()
            elif answer == 3:
                self.add_task()
            elif answer == 4:
                run = False
            else:
                print("Enter a digit in range 1-4")

    def display_delete_menu(self):
        run = True
        while run:
            print("1. Delete Aim")
            print("2. Delete Aim's SubAim")
            print("3. Delete Aim's SubAim's Task")
            print("4. Exit")
            answer = int(input())
            if answer == 1:
                self.delete_aim()
            elif answer == 2:
                self.delete_sub_aim()
            elif answer == 3:
                self.delete_task()
            elif answer == 4:
                run = False
            else:
                print("Enter a digit in range 1-4")

    def add_aim(self):
        aim_to_add = input("Enter an aim name: ")
        if aim_to_add not in self.main_names:
            self.main_names.update({aim_to_add: {}})
            self.save()
        else:
            print("Main Aim " + aim_to_add + " already exists")

    def add_sub_aim(self, main_aim=None):
        if main_aim is None:
            main_aim = self.get_main_aim()
        sub_aim_to_add = input("Enter a sub_aim name: ")
        if sub_aim_to_add not in self.main_names[main_aim]:
            self.main_names[main_aim].update({sub_aim_to_add: {}})
            self.save()
        else:
            print("Sub Aim " + sub_aim_to_add + " already exists")

    def add_task(self, main_aim=None, sub_aim=None):
        if main_aim == "None" or (main_aim not in self.main_names):
            main_aim = self.get_main_aim()
        if sub_aim  == "None" or (sub_aim not in self.main_names[main_aim]):
            sub_aim = self.get_sub_aim(main_aim)
        task_to_add = input("Enter a task to add")
        if task_to_add not in self.main_names[main_aim][sub_aim]:
            self.main_names[main_aim][sub_aim].update({task_to_add: "New"})
            self.save()
        else:
            print("Task " + task_to_add + " already exists")

    def change_aim_name(self, main_aim=None):
        if main_aim is None or main_aim or main_aim not in self.main_names:
            main_aim = self.get_main_aim()
        new_main_aim = input("Enter new text")
        if new_main_aim not in self.main_names or new_main_aim == main_aim:
            new_args = self.main_names[main_aim]
            self.delete_aim(main_aim)
            self.main_names.update({new_main_aim : new_args})
            self.save()
        else:
            print("Such aim exists")

    def change_sub_aim_name(self, main_aim=None, sub_aim=None):
        if main_aim is None or main_aim or main_aim not in self.main_names:
            main_aim = self.get_main_aim()
        if len(self.main_names[main_aim]) == 0:
            print("Main aim - " + main_aim + " is empty, add new subaims.")
            return
        if sub_aim  is None or (sub_aim not in self.main_names[main_aim]):
            sub_aim  = self.get_sub_aim(main_aim)
        new_sub_aim = input("Enter new text")
        if new_sub_aim not in self.main_names[main_aim] or new_sub_aim == sub_aim:
            new_args = self.main_names[main_aim][sub_aim]
            self.delete_sub_aim(main_aim,sub_aim)
            self.main_names.update({new_sub_aim: new_args})
            self.save()
        else:
            print("Such aim exists")

    def change_step_by_step(self):
        main_aim = self.get_main_aim()
        if not len(self.main_names[main_aim]) == 0:
            sub_aim = self.get_sub_aim(main_aim)
            if not len(self.main_names[main_aim][sub_aim]) == 0:
                task = self.get_task(main_aim, sub_aim)
                self.work_with_task(main_aim, sub_aim, task)
            else:
                print("No tasks here")
        else:
            print("No sub aims here")

    def change_by_concrete_path(self, parameter=None):
        if parameter == None:
            parameter = input("Enter the path")
        main_aim, sub_aim, task = parameter.split(" ")
        if main_aim == "None" or (main_aim not in self.main_names):
            main_aim = self.get_main_aim()
        if not len(self.main_names[main_aim]) == 0:
            if sub_aim  == "None" or (sub_aim not in self.main_names[main_aim]):
                sub_aim  = self.get_sub_aim(main_aim)
            if not len(self.main_names[main_aim][sub_aim]) == 0:
                if task     == "None" or (task    not in self.main_names[main_aim][sub_aim]):
                    task     = self.get_task(main_aim, sub_aim)
                print("Working with MAIN AIM - " + main_aim)
                print("Working with SUB AIM - " + sub_aim)
                print("Working with TASK - " + task + " its value: " + self.main_names[main_aim][sub_aim][task])
                self.work_with_task(main_aim, sub_aim, task)

    def change_task_state(self, main_aim, sub_aim, task):
        print("Change - " + main_aim + " " + sub_aim + " " + task)
        self.main_names[main_aim][sub_aim][task] = input("Enter smth to change the value: ")
        self.save()

    def change_task(self, main_aim, sub_aim, task):
        self.main_names[main_aim][sub_aim].pop(task)
        self.task_names.pop(task)
        task = input("Enter new task: ")
        self.main_names[main_aim][sub_aim].update({task: "Incompleted"})
        self.task_names.update({task: "Incompleted"})
        print("New task " + task + " " + "Incompleted")
        self.save()

    def get_main_aim(self):
        to_return = ""
        run = True
        for item in self.main_names:
            print(item)
        while run:
            to_return = input("Enter a main_aim to edit ")
            if to_return in self.main_names:
                run = False
            else:
                print("Enter a valid main_aim")
        return to_return

    def get_sub_aim(self, main_aim):
        to_return = ""
        run = True
        self.display_sub_aims(main_aim)
        while run:
            to_return = input("Enter a sub_aim to edit ")
            if to_return in self.main_names[main_aim]:
                run = False
            else:
                print("Enter a valid sub_aim")
        return to_return

    def get_task(self, main_aim, sub_aim):
        to_return = ""
        run = True
        while run:
            self.display_aims(main_aim, sub_aim)
            to_return = input("Enter a task to edit")
            if to_return in self.main_names[main_aim][sub_aim]:
                run = False
            else:
                print("Enter a valid task")
        return to_return

    def work_with_task(self, main_aim, sub_aim, task):
        run = True
        while run:
            desion = int(input("\n1. to change a state."
                               "\n2. to change the task name."
                               "\n3.print "
                               "\n4. exit"))
            if desion == 1:
                self.change_task_state(main_aim, sub_aim, task)
            elif desion == 2:
                self.change_task(main_aim, sub_aim, task)
            elif desion == 3:
                print("Task - " + task + " value: " + self.main_names[main_aim][sub_aim][task])
                break
            elif desion == 4:
                print("Exit")
                run = False
            else:
                print("Enter a valid number")

    def delete_task(self, main_aim=None, sub_aim=None, task=None):
        if main_aim is None or (main_aim not in self.main_names):
            main_aim = self.get_main_aim()
        if sub_aim  is None or (sub_aim not in self.main_names[main_aim]):
            sub_aim  = self.get_sub_aim(main_aim)
        if task     is None or (task    not in self.main_names[main_aim][sub_aim]):
            task     = self.get_task(main_aim, sub_aim)
        answer = input("Do you really want to delete task: " + task + "\n If yew - print y else n")
        if answer.lower() == 'y':
            self.main_names[main_aim][sub_aim].pop(task)
            self.save()
            print("Deleted")
        else:
            print("Terminated")

    def delete_sub_aim(self, main_aim=None, sub_aim=None):
        if main_aim is None or (main_aim not in self.main_names):
            main_aim = self.get_main_aim()
        if sub_aim  is None or (sub_aim not in self.main_names[main_aim]):
            sub_aim  = self.get_sub_aim(main_aim)

        answer = input("Do you really want to delete sub_aim: " + sub_aim + "\n If yes - print Y else N")
        if answer.lower() == 'y':
            self.main_names[main_aim].pop(sub_aim)
            self.save()
            print("Deleted")
        else:
            print("Terminated")

    def delete_aim(self, main_aim=None):
        if main_aim is None or main_aim not in self.main_names:
            main_aim = self.get_main_aim()

        answer = input("Do you really want to delete main_aim: " + main_aim + "\n If yes - print Y else N")
        if answer.lower() == 'y':
            self.main_names.pop(main_aim)
            self.save()
            print("Deleted")
        else:
            print("Terminated")

    def display_sub_aims(self, main_aim):
        print(main_aim)
        for sub_aim, task in self.main_names[main_aim].items():
            print("\t" + sub_aim)

    def display_list_to_do(self, main=None, sub_aim=None):
        if main == "get":
            main = self.get_main_aim()
        if sub_aim == "get":
            sub_aim = self.get_sub_aim(main)

        aim_to_print = dict()
        if main is None:
            aim_to_print = dict()
            for main_aim, sub_aim in self.main_names.items():
                sub_to_print = dict()
                for sub_aim, task in self.main_names[main_aim].items():
                    tas_to_print = dict()
                    for task, state in self.main_names[main_aim][sub_aim].items():
                        if state != "Completed":
                            tas_to_print.update({task: state})
                    if len(tas_to_print) != 0:
                        sub_to_print.update({sub_aim: tas_to_print})
                if len(sub_to_print) != 0:
                    aim_to_print.update({main_aim: sub_to_print})
            print("FOR ALL AIMS IN YOUR LIST")
            if len(aim_to_print) != 0:
                for m, s in aim_to_print.items():
                    print(m)
                    if s is not None:
                        for s, t in aim_to_print[m].items():
                            print("\t" + s)
                            if t is not None:
                                for t, d in aim_to_print[m][s].items():
                                    print("\t\tTask: " + t + " state " + d)
        elif main in self.main_names:
            for main_aim, sub_aim in self.main_names.items:
                if main_aim == main:
                    sub_to_print = dict()
                    if sub_aim in self.main_names[main]:
                        tas_to_print = dict()
                        for task, state in self.main_names[main][sub_aim].items():
                            if state != "Completed":
                                tas_to_print.update({task: state})
                        if len(tas_to_print) != 0:
                            sub_to_print.update({sub_aim: tas_to_print})
                    else:
                        for sub_aim, task in self.main_names[main].items():
                            tas_to_print = dict()
                            for task, state in self.main_names[main][sub_aim].items():
                                if state != "completed":
                                    tas_to_print.update({task: state})
                            if len(tas_to_print) != 0:
                                sub_to_print.update({sub_aim: tas_to_print})

                    if len(sub_to_print) != 0:
                        aim_to_print.update({main: sub_to_print})
            print("For all sub aims in - " + main)
            if len(aim_to_print) != 0:
                for m, s in aim_to_print.items():
                    print(m)
                    if s is not None:
                        for s, t in aim_to_print[m].items():
                            print("\t" + s)
                            if t is not None:
                                for t, d in aim_to_print[m][s].items():
                                    print("\t\tTask: " + t + " state " + d)
        else:
            print("No such main aim " + main)

    def display_aims(self, main=None, sub_aim=None, task=None):
        if main == "get":
            if not len(self.main_names) == 0:
                main = self.get_main_aim()
            else:
                main = None
        if sub_aim == "get":
            if main is not None or len(self.main_names[main]) == 0:
                sub_aim = self.get_sub_aim(main)
            else:
                sub_aim = None
        if task == "get":
            if not len(self.main_names[main][sub_aim]) == 0:
                task = self.get_task(main, sub_aim)
            else:
                task = None

        if main is None:
            for main_aim, sub_aim in self.main_names.items():
                print(main_aim)
                for sub_aim, task in self.main_names[main_aim].items():
                    print("\t" + sub_aim)
                    for task, task_description in self.main_names[main_aim][sub_aim].items():
                        print("\t\t" + task + "\t" + task_description)
        elif main in self.main_names:
            if sub_aim in self.main_names[main]:
                if task in self.main_names[main][sub_aim]:
                    print("Task: " + task + " description: " + self.main_names[main][sub_aim][task])
                else:
                    print("SubAim: " + sub_aim)
                    for task, task_description in self.main_names[main][sub_aim].items():
                        print("\t\t" + task + "\t" + task_description)
            else:
                print(main)
                for sub_aim, task in self.main_names[main].items():
                    print("\t" + sub_aim)
                    for task, task_description in self.main_names[main][sub_aim].items():
                        print("\t\t" + task + "\t" + task_description)
        else:
            print("No such main aim: " + main)

    def save(self):
        with open("app.json", 'w') as file:
            json.dump(self.main_names, file)

    def add_aims():
        pass

if __name__ == '__main__':
    Ap = App("appdd.json")
    Ap.run()


"""
    Copyright 2017 Inmanta

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

    Contact: code@inmanta.com
"""
import logging

import pytest

LOGGER = logging.getLogger(__name__)

@pytest.mark.gen_test(timeout=60)
def test_form(client, environment):
    """
        Test creating and updating forms
    """
    form_id = "cwdemo::forms::ClearwaterSize"
    form_data = {
        'attributes': {
            'bono': {'default': 1,
                     'options': {'min': 1, 'max': 100, 'widget': 'slider', 'help': 'help'},
                     'type': 'number'},
            'ralf': {'default': 1,
                     'options': {'min': 1, 'max': 100, 'widget': 'slider', 'help': 'help'},
                     'type': 'number'}
        },
        'options': {'title': 'VNF replication', 'help': 'help', 'record_count': 1},
        'type': 'cwdemo::forms::ClearwaterSize'
    }
    result = yield client.put_form(tid=environment, id=form_id, form=form_data)
    assert(result.code == 200)

    result = yield client.get_form(environment, form_id)
    assert(result.code == 200)

    result = yield client.list_forms(environment)
    assert(result.code == 200)
    assert(len(result.result["forms"]) == 1)
    assert(result.result["forms"][0]["form_type"] == form_id)

@pytest.mark.gen_test(timeout=60)
def test_update_form(client, environment):
    """
        Test creating and updating forms
    """
    form_id = "cwdemo::forms::ClearwaterSize"
    form_data = {
        'attributes': {
            'bono': {'default': 1,
                     'options': {'min': 1, 'max': 100, 'widget': 'slider', 'help': 'help'},
                     'type': 'number'},
            'ralf': {'default': 1,
                     'options': {'min': 1, 'max': 100, 'widget': 'slider', 'help': 'help'},
                     'type': 'number'}
        },
        'options': {'title': 'VNF replication', 'help': 'help', 'record_count': 1},
        'type': 'cwdemo::forms::ClearwaterSize'
    }
    result = yield client.put_form(tid=environment, id=form_id, form=form_data)
    assert(result.code == 200)

    result = yield client.get_form(environment, form_id)
    assert(result.code == 200)
    assert(len(result.result["form"]["field_options"]) == 2)

    form_data["attributes"]["sprout"] = {'default': 1, 'options': {'min': 1, 'max': 100, 'widget': 'slider', 'help': 'help'},
                                         'type': 'number'}
    result = yield client.put_form(tid=environment, id=form_id, form=form_data)
    assert(result.code == 200)

    result = yield client.get_form(environment, form_id)
    assert(result.code == 200)
    assert(len(result.result["form"]["field_options"]) == 3)

@pytest.mark.gen_test(timeout=60)
def test_records(client, environment):
    """
        Test creating and updating forms
    """
    form_id = "FormType"
    result = yield client.put_form(tid=environment, id=form_id,
                                   form={'attributes': {'field1': {'default': 1, 'options': {'min': 1, 'max': 100},
                                                                   'type': 'number'},
                                                        'field2': {'default': "", 'options': {}, 'type': 'string'}},
                                         'options': {},
                                         'type': form_id}
                                   )
    assert(result.code == 200)

    result = yield client.create_record(tid=environment, form_type=form_id, form={"field1": 10, "field2": "value"})
    assert(result.code == 200)

    record_id = result.result["record"]["id"]
    result = yield client.update_record(tid=environment, id=record_id, form={"field1": 20, "field2": "value2"})
    assert(result.code == 200)

    result = yield client.get_record(tid=environment, id=record_id)
    assert(result.code == 200)

    result = yield client.list_records(tid=environment, form_type=form_id)
    assert(result.code == 200)
    assert(len(result.result["records"]) == 1)

    yield client.create_record(tid=environment, form_type=form_id, form={"field1": 10, "field2": "value"})
    result = yield client.list_records(tid=environment, form_type=form_id, include_record=True)
    assert(result.code == 200)
    assert(len(result.result["records"]) == 2)
    assert("field1" in result.result["records"][0]["fields"])

    result = yield client.delete_record(tid=environment, id=record_id)
    assert(result.code == 200)


from collections import defaultdict
from typing import Dict, List

from django.db.models import Count
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response

from apps.social.models import NotificationService
from apps.stream.filters import EventFilter
from apps.stream.models import Rule, Event
from apps.stream.serializers import StreamSerializer, RuleSerializer, EventSerializer, FullEventSerializer, \
    StatsSerializer
from apps.stream import tasks

class StreamModelView(viewsets.ModelViewSet):
    queryset = NotificationService.objects.all()
    serializer_class = StreamSerializer

class RuleModelView(viewsets.ModelViewSet):
    queryset = Rule.objects.all()
    serializer_class = RuleSerializer

    def get_queryset(self):
        qs = super().get_queryset()
        social = self.request.query_params.get("social")

        if social:
            qs = qs.filter(service=social)

        social_user = self.request.social_user

        if not qs:
            return Rule.objects.none()

        qs = qs.filter(user=social_user.user)
        return qs

    def perform_create(self, serializer):
        instance = serializer.save()
        tasks.add_rule(instance.key, instance.value)

    @action(methods=['delete'], detail=False, url_path='remove_rule', url_name='remove_rule')
    def remove_rule(self, request, *args, **kwargs):
        key = request.data.get("key")
        social = request.data.get("social")

        rule = self.get_queryset().filter(key=key, service=social, user=self.request.social_user.user).first()

        if not rule:
            return Response(status=status.HTTP_404_NOT_FOUND)

        rule.delete()

        return Response("ok", status=status.HTTP_200_OK)

class EventViewSet(viewsets.ModelViewSet):
    queryset = Event.objects.all()
    serializer_class = EventSerializer
    filter_class = EventFilter

    def get_serializer_class(self):
        return {
            "retrieve": FullEventSerializer
        }.get(self.action, super().get_serializer_class())

    def get_queryset(self):
        qs = super().get_queryset()
        rules = self.request.social_user.user.rules.get_queryset()

        tags = list(rules.values_list("key", flat=True).all())
        return qs.filter(tags__overlap=tags)

    def _count(self, tags: Dict[str, List[Event]]):
        res = defaultdict(dict)

        for tag, events in tags.items():
            res[tag]["count"] = defaultdict(int)
            res[tag]["total"] = len(events)

            for event in events:
                res[tag]["count"][event.event_type] += 1

        return res

    @action(methods=['post'], detail=False, url_path='stats', url_name='stats')
    def stats(self, request, *args, **kwargs):
        serializer = StatsSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        qs = self.get_queryset()

        if serializer.tags:
            qs = qs.filter(tags__overlap=serializer.tags)

        if serializer.from_date:
            qs = qs.filter(created__gte=serializer.from_date)
        if serializer.to_date:
            qs = qs.filter(created__lte=serializer.to_date)

        tags = defaultdict(list)

        for event in qs:
            for tag in event.tags:
                if not serializer.tags or tag in serializer.tags:
                    tags[tag].append(event)

        result = {
            "total": qs.count(),
            "rules": [
                {
                    "tag": tag,
                    **event,
                }
                for tag, event in self._count(tags).items()
            ]
        }

        return Response(result)