import json
import numpy as np
from scipy.optimize import minimize

def sigmoid(z):
    return 1 / (1 + np.exp(-z))

def cost(theta, X, y, lmbda):
    theta = np.matrix(theta)
    X = np.matrix(X)
    y = np.matrix(y)
    first = np.multiply(-y, np.log(sigmoid(X * theta.T)))
    second = np.multiply((1 - y), np.log(1 - sigmoid(X * theta.T)))
    reg = lmbda * np.sum(np.power(theta[:,1:theta.shape[1]], 2))
    return (np.sum(first - second) + reg) / (2 * len(X))

def gradient(theta, X, y, lmbda):
    theta = np.matrix(theta)
    X = np.matrix(X)
    y = np.matrix(y)

    parameters = int(theta.ravel().shape[1])
    error = sigmoid(X * theta.T) - y
    grad = ((X.T * error) / len(X)).T + ((lmbda / len(X)) * theta)

    # intercept gradient is not regularized
    grad[0, 0] = np.sum(np.multiply(error, X[:,0])) / len(X)

    return np.array(grad).ravel()

def one_vs_all(X, y, num_labels, lmbda):
    rows = X.shape[0]
    params = X.shape[1]
    
    all_theta = np.zeros((num_labels, params + 1)) 
    X = np.insert(X, 0, values=np.ones(rows), axis=1)

    for i in range(1, num_labels + 1): 
        theta = np.zeros(params + 1)
        y_i = np.array([1 if label == i else 0 for label in y]) 
        y_i = np.reshape(y_i, (rows, 1)) 
        fmin = minimize(fun=cost, x0=theta, args=(X, y_i, lmbda), method='TNC', jac=gradient)
        all_theta[i-1,:] = fmin.x
    
    return all_theta

def predict_all(X, all_theta):
    rows = X.shape[0]
    params = X.shape[1]
    num_labels = all_theta.shape[0]

    X = np.insert(X, 0, values=np.ones(rows), axis=1)

    X = np.matrix(X)
    all_theta = np.matrix(all_theta)

    h = sigmoid(X * all_theta.T)

    scores = [ {"like": score.item(0,0), "dislike": score.item(0,1)} for score in h]
    return scores

def train_and_predict(trainer, tuning_params):
    num_classifications = 2

    for lmbda in tuning_params:
        print "\ncalculating theta for lambda param: {}".format(lmbda)
        all_theta = one_vs_all(trainer.X_train, trainer.Y, num_classifications, lmbda)
        trainer.timer.interval('Calculated theta')

        print "predicting"
        hypothesis = predict_all(trainer.X_test, all_theta)
        trainer.timer.interval('Made prediction')
        trainer.store_predict(hypothesis, lmbda)
          

