using BioMedQuery
using Pkg
using CSV
using DataFrames
using LinearAlgebra
using MySQL
using Sockets
using Random
using Dates
import StatsBase: sample
# @time
include("crossval_and_stats.jl")
include("classifiers.jl")
include("feature_selection.jl")
include("hyperparameter_optimzation.jl")
include("feature_normalization.jl")

function main()
  #feature generation occurs here
  #create a dataframe with all features from all pmids for machine learning
  #Feature options include :MeSH, :Gene_grams, :Gene_subj_pred, :Gene_significant, :Gene_count, :Semtype_check ,:Species_check, :BOW
  feature_set = [:Semtype_check]
  analysis = "ptb"

  print("Enter password (used for MySQL): ")
  password = readline()

  #connect to MySQL database and get full list of pmids
  con = MySQL.connect("127.0.0.1", "root", "Ukog_09291", db = "$(analysis)_pubmed")
  response = convert(Array{Int,2},BioMedQuery.DBUtils.db_query(con, "SELECT * FROM paper_class")) # Must change this to change the training set
  pmids = response[:,1]
  class = response[:,2]
  response_pass=response[response[:,2] .== 1, :] # The set of considered pmids with the class of the paper
  response_fail=response[response[:,2] .== 0, :] # The set of not considered pmids with the class of the paper
  pmids_pass=response_pass[:,1] # The set of considered pmids
  pmids_fail=response_fail[:,1] # The set of not considered pmids

  #select training set and test set pmids - 80% training set size

  Random.seed!(137)
  n_totalp = length(pmids_pass)
  n_totalf = length(pmids_fail)
  ntrainp = round(Int, 0.8 * n_totalp) # round(Int, 0.8 * n_totalf)
  ntrainf = round(Int, 0.8 * n_totalf)

  train_pass = sample(1:n_totalp, ntrainp, replace = false)
  train_fail = sample(1:n_totalf, ntrainf, replace = false)

  test_pass = setdiff(1:n_totalp, train_pass)
  test_fail = setdiff(1:n_totalf, train_fail)
  train_pmids = vcat(pmids_pass[train_pass], pmids_fail[train_fail])
  test_pmids = vcat(pmids_pass[test_pass], pmids_fail[test_fail])


  df = create_complete_df(con, feature_set)
  MySQL.disconnect(con)


  df_train= df[in.(df[:pmid], [train_pmids]),:]
  df_test= df[in.(df[:pmid], [test_pmids]),:]

  df = feature_normalization(df_train, df_test)


  #perform analysis using train and test pmids and write prediction statistics to output csv file

  ##### logistic regression (skl)

  #parameters - the parameters not defined here will be set to the defaults spectified in classifiers.jl
  opt_param_LR= hyperparameter_optimization_LR(df, train_pmids)
  println("Optimized Hyperparameters: ")
  println(opt_param_LR)

  lr_dict = Dict(:class_weight=>"balanced", :proba=> true, :C => get(opt_param_LR, :C, 1.0) ,
  :solver => get(opt_param_LR, :solver, "liblinear"))

  #testing functions - use training set and testing set
  #test_analysis_skl_train_size(df, train_pmids, test_pmids, :LogisticRegression, lr_dict, 2, 2)
  test_analysis_skl(df, train_pmids, test_pmids, :LogisticRegression, lr_dict)

  #training functions - use training set only
  #nfold_analysis_skl(df, train_pmids, 5, :LogisticRegression, lr_dict)
  #train_size_analysis_skl(df, train_pmids, 10, :LogisticRegression, lr_dict)


  ##### random forests (skl)

  #parameters - the parameters not defined here will be set to the defaults spectified in classifiers.jl

  opt_param_RF = hyperparameter_optimization_RF(df, train_pmids)
  println("Optimized Hyperparameters: ")
  println(opt_param_RF)
  rf_dict = Dict(:n_estimators=>500, :max_features=>0.2, :class_weight=>"balanced", :proba=>true, :criterion=> get(opt_param_RF, :criterion, "gini"),
  :max_features=> get(opt_param_RF, :max_features, "auto"), :min_impurity_decrease=> get(opt_param_RF, :min_impurity_decrease, 0.00000001),
  :min_samples_split => get(opt_param_RF, :min_samples_split, 6), :min_samples_leaf=> get(opt_param_RF, :min_samples_leaf, 1))

  #testing functions - use training set and testing set
  #test_analysis_skl_train_size(df, train_pmids, test_pmids, :RandomForest, rf_dict, 2, 2)
  test_analysis_skl(df, train_pmids, test_pmids, :RandomForest, rf_dict)

  #training functions - use training set only
  #nfold_analysis_skl(df, train_pmids, 5, :RandomForest, rf_dict)
  #train_size_analysis_skl(df, train_pmids, 10, :RandomForest, rf_dict)


  ##### linear svm (skl)

  #parameters - the parameters not defined here will be set to the defaults spectified in classifiers.jl
  l_svm_dict = Dict(:class_weight=>"balanced")

  #testing functions - use training set and testing set
  #test_analysis_skl_train_size(df, train_pmids, test_pmids, :LinearSVC, l_svm_dict, 2, 2)
  #test_analysis_skl(df, train_pmids, test_pmids, :LinearSVC, l_svm_dict)

  #training functions - use training set only
  #nfold_analysis_skl(df, train_pmids, 5, :LinearSVC, l_svm_dict)
  #train_size_analysis_skl(df, train_pmids, 10, :LinearSVC, l_svm_dict)


  ##### svm (skl)

  #parameters - the parameters not defined here will be set to the defaults spectified in classifiers.jl
  svm_dict = Dict(:class_weight=>"balanced", :proba=>true, :proba_thresh=>0.01)

  #testing functions - use training set and testing set
  #test_analysis_skl_train_size(df, train_pmids, test_pmids, :SVC, svm_dict, 2, 2)
  #test_analysis_skl(df, train_pmids, test_pmids, :SVC, svm_dict)

  #training functions - use training set only
  #nfold_analysis_skl(df, train_pmids, 5, :SVC, svm_dict)
  #train_size_analysis_skl(df, train_pmids, 10, :SVC, svm_dict)



  ##### nu svm (skl) - results are notiably different from svm

  #parameters - the parameters not defined here will be set to the defaults spectified in classifiers.jl
  nu_svm_dict = Dict(:class_weight=>"balanced")

  #testing functions - use training set and testing set
  #test_analysis_skl_train_size(df, train_pmids, test_pmids, :NuSVC, nu_svm_dict, 2, 2)
  #test_analysis_skl(df, train_pmids, test_pmids, :NuSVC, nu_svm_dict)

  #training functions - use training set only
  #nfold_analysis_skl(df, train_pmids, 5, :NuSVC, nu_svm_dict)
  #train_size_analysis_skl(df, train_pmids, 10, :NuSVC, nu_svm_dict)



  ##### mlp (skl)

  #parameters - the parameters not defined here will be set to the defaults spectified in classifiers.jl
  opt_param_MLP= hyperparameter_optimization_MLP(df, train_pmids)
  println("Optimized Hyperparameters: ")
  println(opt_param_MLP)

  mlp_dict = Dict(:proba=>true, :max_iter=> get(opt_param_MLP, :max_iter, 200),
  :hidden_layer_sizes=>get(opt_param_MLP, :hidden_layer_sizes, (100,)), :solver => get(opt_param_MLP, :solver, "adam"),
  :learning_rate=> get(opt_param_MLP, :learning_rate, "constant") ,:learning_rate_init => get(opt_param_MLP, :learning_rate_init, 0.001), :activation => get(opt_param_MLP, :activation, "relu") ,:alpha => get(opt_param_MLP, :alpha, 0.0001))
  #testing functions - use training set and testing set
  #test_analysis_skl_train_size(df, train_pmids, test_pmids, :MLP, mlp_dict, 2, 2)
  test_analysis_skl(df, train_pmids, test_pmids, :MLP, mlp_dict)

  #training functions - use training set only
  #nfold_analysis_skl(df, train_pmids, 5, :MLP, mlp_dict)
  #train_size_analysis_skl(df, train_pmids, 10, :MLP, mlp_dict)




  ##### mlp (mocha)

  #parameters - the parameters not defined here will be set to the defaults spectified in classifiers.jl
  mlp_mocha_dict = Dict(:proba=>true, :verbose=>false, :fc1_size=>500, :weights=>"balanced")

  #testing functions - use training set and testing set
  #test_analysis_train_size(df, train_pmids, test_pmids, MLPmocha, mlp_mocha_dict, 2, 2)
  #test_analysis(df, train_pmids, test_pmids, MLPmocha, mlp_mocha_dict)

  #training functions - use training set only
  #nfold_analysis(df, train_pmids, 5, MLPmocha, mlp_mocha_dict)
  #train_size_analysis(df, train_pmids, 10, MLPmocha, mlp_mocha_dict)

  println("Machine Learning Complete\n")
end

main()
