import pyspark
from pyspark import SparkContext
import urllib
from pyspark.mllib.regression import LabeledPoint
from numpy import array
from pyspark.mllib.tree import RandomForest, RandomForestModel
from pyspark.sql import SQLContext
from time import time
#  Custom imports
import MySQLConnection


"""
Explanation: Training the IoT Back Brace Machine Learning Model
Creating a Random Forest classification model using Spark and MLLib
End of explanation
"""


sqlContext = SQLContext(sc)
#  Get username and password from file in this format: {"user":"yourusername","password":"yourpassword"}
connectionProperties = MySQLConnection.getDBConnectionProps('/home/erik/mysql_credentials.txt')
# Get training data from the database...biosensor database and SensorTrainingReadings table
data = sqlContext.read.jdbc("jdbc:mysql://localhost/biosensor", "SensorTrainingReadings", properties=connectionProperties).selectExpr("deviceID","metricTypeID","uomID","positionID","actualPitch")
print "Train data size is {}".format(data.count())

"""
Explanation: Getting the training data and creating the RDD
The data is stored in a local MySQL database.  A connection is made and data is read from the "SensorTrainingReadings" table.
End of explanation
"""


# Split data into training and test dataasets
(trainingDataTable, testDataTable) = data.randomSplit([0.9, 0.1])

trainingDataTable.show()
testDataTable.show()


"""
Explanation: Split training data into training set and test set
In order to evaluate the model, we need to hold back some data.  In this case 30% hold-back should be good.
End of explanation
"""


# The model requires labeldPoints which is a row with label and a vector of features.
def featurize(t):
	return LabeledPoint(t.positionID, [t.actualPitch])

trainingData = trainingDataTable.map(featurize)

"""
Explanation: Create an RDD of LabeledPoints
The featurize method returns a LabeledPoint with the label and an vector array of features.  
An example for a reading from the stooped position would be:
*  0, [-40,15]
End of explanation
"""


# Train the classifier/Build the model
startTime = time()

#Random Forest Model
model = RandomForest.trainClassifier(
                                    trainingData, 
                                    numClasses=3, 
                                    categoricalFeaturesInfo={},
                                    numTrees=6, 
                                    featureSubsetStrategy="auto",
                                    impurity='gini', 
                                    maxDepth=4, 
                                    maxBins=32
                                    )

elapsedTime = time() - startTime

print "Classifier trained in {} seconds".format(round(elapsedTime,3))

# Save the madel for use in evaluating readings
model.save(sc,"models/IoTBackBraceRandomForest.model")

"""
Explanation: Training the model
For this example we are choosing a Random Forest model wich is multiple decision trees averaged together.  In this case since we know there will only be 3 distinct values of "labels", numClasses = 3.
End of explanation
"""


# Evaluate model on test instances and compute test error
testData = testDataTable.map(featurize)
predictions = model.predict(testData.map(lambda x: x.features))
labelsAndPredictions = testData.map(lambda lp: lp.label).zip(predictions)
testErr = labelsAndPredictions.filter(lambda (v, p): v != p).count() / float(testData.count())
print('Test Error = ' + str(testErr))

"""
Explanation: Evaluating the accuracy of the model
Since we use 70% of the training data for actually training the model, we have the remaining 30% that we can use as a test dataset.  Since these values are still known, we can see if the model does a good job of classifying.
End of explanation
"""


print('Random Forest Classifcation Model:')
print(model.toDebugString())

"""
Explanation: Another handy feature is that you can view the model logic tree by using the "toDebugString()" method
End of explanation
"""


loadedModel = RandomForestModel.load(sc, "models/IoTBackBraceRandomForest.model")

"""
Explanation: Using the model for analysis of raw data
Once the model is saved, it can be loaded again in any script by referring to the path where it was saved.
End of explanation
"""


for i in range(-50,50):
    prediction = loadedModel.predict([i])
    positions = {
                  0 : "upright",
                  1 : "back bent",
                  2 : "stooped"
                }
    print str(i) + " => " + str(positions[prediction])

"""
Explanation: The example below passes a value to the model from a range of -50 degrees (stooped) to +10 degrees (standing).
End of explanation
"""


