import numpy as np
import matplotlib.pyplot as plt
from pandas.io.parsers import read_csv
import scipy.optimize as opt
from sklearn.preprocessing import PolynomialFeatures

def load_csv(file_name):
    values = read_csv(file_name, header=None).values

    return values.astype(float)

def gradient(thetas, XX, Y, lamb):
    m = np.shape(XX)[0]

    H = h(thetas, XX)
    grad = (1/len(Y)) * np.dot(XX.T, H-Y)
    grad += (lamb/m) * np.c_[thetas]

    return grad

def cost(thetas, X, Y, lamb):
    m = np.shape(X)[0]
    H = h(thetas, X)

    c = (-1/m) * (np.dot(Y.T, np.log(H)) + np.dot((1-Y).T, np.log(1-H)))
    c += (lamb/(2*m)) * (thetas**2).sum()

    return c

def sigmoid(Z):
    return 1/(1 + np.e**(-Z))

def h(thetas, X):
    return np.c_[sigmoid(np.dot(X, thetas))]

def show_decision_boundary(thetas, X, Y, poly):
    plt.figure()

    x1_min, x1_max = X[:, 0].min(), X[:, 0].max()
    x2_min, x2_max = X[:, 1].min(), X[:, 1].max()
    xx1, xx2 = np.meshgrid(np.linspace(x1_min, x1_max), np.linspace(x2_min, x2_max))

    h = sigmoid(poly.fit_transform(np.c_[xx1.ravel(), xx2.ravel()]).dot(thetas))

    h = h.reshape(xx1.shape)

    positives = np.where(Y == 1)
    negatives = np.where(Y == 0)
    plt.scatter(X[positives, 0], X[positives, 1], marker='+', color='blue')
    plt.scatter(X[negatives, 0], X[negatives, 1], color='red')

    plt.contour(xx1, xx2, h, [0.5], linewidths=1, colors='g')
    plt.savefig("images/regresion_logistic_regularized.png")
    plt.show()
    plt.close()

def evaluate(thetas, X, Y, degree):
    poly = PolynomialFeatures(degree)
    X_poly = poly.fit_transform(X)

    result = h(thetas, X_poly)
    passed_missed = np.logical_and((result >= 0.5), (Y == 0)).sum()
    failed_missed = np.logical_and((result < 0.5), (Y == 1)).sum()

    errors = (passed_missed + failed_missed)

    return (result.shape[0] - errors) / (result.shape[0])

def train(X, Y, degree=2, lamb=1, verbose = True):
    poly = PolynomialFeatures(degree)
    X_poly = poly.fit_transform(X)

    m = np.shape(X_poly)[0]
    n = np.shape(X_poly)[1]

    thetas = np.zeros((n, 1), dtype=float)
    result = opt.fmin_tnc(func=cost, x0=thetas, fprime=gradient, args=(X_poly, Y, lamb), disp = 5 if verbose else 0)
    thetas = result[0]

    return thetas

def main():
    # DATA PREPROCESSING
    datos = load_csv("data/ex2data2.csv")
    X = datos[:, :-1]
    Y = datos[:, -1][np.newaxis].T

    thetas = train(X, Y)

    print("Accuracy: ", evaluate(thetas, X, Y, 2)*100, "%")
    show_decision_boundary(thetas, X, Y, poly)


if __name__ == "__main__":
    main()
