using Microsoft.ML;
//using Microsoft.ML.Core.Data;
using Microsoft.ML.Data;
using System;
using System.IO;
using System.Linq;

//opis różnych zadań ML: https://docs.microsoft.com/pl-pl/dotnet/machine-learning/resources/tasks

namespace ML
{
    class Program
    {
        private const string dataPath = "iris-data.txt";
        private const bool trainOrLoad = true;

        #region Multiclass classification
        // STEP 1: Define your data structures
        // IrisData is used to provide training data, and as
        // input for prediction operations
        // - First 4 properties are inputs/features used to predict the label
        // - Label is what you are predicting, and is only set when training
        public class LabeledIrisData
        {
            [LoadColumn(0)]
            public float SepalLength;

            [LoadColumn(1)]
            public float SepalWidth;

            [LoadColumn(2)]
            public float PetalLength;

            [LoadColumn(3)]
            public float PetalWidth;

            [LoadColumn(4)]
            public string Label;
        }

        // IrisPrediction is the result returned from prediction operations
        public class IrisPrediction
        {
            [ColumnName("PredictedLabel")]
            public string PredictedLabels;
        }

        private const string trainedClassificationModelFilePath = "iris-trainedClassificationModel.zip"; //klasyfikator        

        //https://dotnet.microsoft.com/learn/machinelearning-ai/ml-dotnet-get-started-tutorial
        //Multiclass classification
        //supervised training using labeled examples
        //trained model = classifier
        private static void supervisedTraining()
        {
            // STEP 2: Create a ML.NET environment  
            Console.WriteLine("Creating a ML.NET environment...");
            MLContext mlContext = new MLContext();

            DataViewSchema dvs;

            ITransformer model = null;
            if (trainOrLoad)
            {
                // If working in Visual Studio, make sure the 'Copy to Output Directory'
                // property of iris-data.txt is set to 'Copy always'
                Console.WriteLine("Reading data...");
                TextLoader loader = mlContext.Data.CreateTextLoader<LabeledIrisData>(separatorChar: ',', hasHeader: true);
                IDataView trainingDataView = loader.Load(dataPath);
                dvs = trainingDataView.Schema;

                Console.WriteLine("Data containes " + ((trainingDataView.GetRowCount() == null) ? "---" : trainingDataView.GetRowCount().ToString()) + " records in " + trainingDataView.Schema.Count.ToString() + " columns");

                // STEP 3: Transform your data and add a learner
                // Assign numeric values to text in the "Label" column, because only
                // numbers can be processed during model training.
                // Add a learning algorithm to the pipeline. e.g.(What type of iris is this?)
                // Convert the Label back into original text (after converting to number in step 3)
                Console.WriteLine("Transforming data...");
                var pipeline = mlContext.Transforms.Conversion.MapValueToKey("Label")
                    .Append(mlContext.Transforms.Concatenate("Features", "SepalLength", "SepalWidth", "PetalLength", "PetalWidth"))
                    //.Append(mlContext.MulticlassClassification.Trainers.StochasticDualCoordinateAscent(labelColumn: "Label", featureColumn: "Features"))
                    .Append(mlContext.MulticlassClassification.Trainers.SdcaMaximumEntropy(labelColumnName: "Label", featureColumnName: "Features"))
                    .Append(mlContext.Transforms.Conversion.MapKeyToValue("PredictedLabel"));

                // STEP 4: Train your model based on the data set  
                Console.WriteLine("Training model with data...");
                //TransformerChain<KeyToValueMappingTransformer> model;
                model = pipeline.Fit(trainingDataView);

                Console.WriteLine("Saving trained model...");
                using (var stream = File.Create(trainedClassificationModelFilePath))
                {
                    // Saving and loading happens to 'dynamic' models.
                    mlContext.Model.Save(model, loader, stream);
                }
                Console.WriteLine("Trained model saved to " + trainedClassificationModelFilePath);
            }
            else
            {
                Console.WriteLine("Loading trained model from " + trainedClusteringModelFilePath + "...");
                using (var stream = File.OpenRead(trainedClassificationModelFilePath))
                {                    
                    model = mlContext.Model.Load(stream, out dvs);
                }
            }

            // STEP 5: Use your model to make a prediction
            // You can change these numbers to test different predictions
            Console.WriteLine("Testing model...");
            //IrisPrediction prediction = model.CreatePredictionEngine<LabeledIrisData, IrisPrediction>(mlContext).Predict(
            IrisPrediction prediction = mlContext.Model.CreatePredictionEngine<LabeledIrisData, IrisPrediction>(model, dvs).Predict(
                new LabeledIrisData()
                {
                    SepalLength = 3.3f,
                    SepalWidth = 1.6f,
                    PetalLength = 0.2f,
                    PetalWidth = 5.1f,
                });

            Console.WriteLine($"Predicted flower type is: {prediction.PredictedLabels}");
        }
        #endregion

        #region Clustering
        public class UnlabeledIrisData
        {
            [ColumnName("SepalLength")]
            //[LoadColumn(0)]
            public float SepalLength;

            [ColumnName("SepalWidth")]
            //[LoadColumn(1)]
            public float SepalWidth;

            [ColumnName("PetalLength")]
            //[LoadColumn(2)]
            public float PetalLength;

            [ColumnName("PetalWidth")]
            //[LoadColumn(3)]
            public float PetalWidth;
        }

        public class ClusterPrediction
        {
            [ColumnName("PredictedLabel")]
            public uint PredictedClusterId;

            [ColumnName("Score")]
            public float[] Distances;
        }

        static readonly string trainedClusteringModelFilePath = "iris-trainedClusteringModel.zip";

        //https://docs.microsoft.com/pl-pl/dotnet/machine-learning/tutorials/iris-clustering
        //clustering
        //unsupervised machine learning task (ignorujemy etykiety w pliku z danymi)
        private static void clustering()
        {
            MLContext mlContext = new MLContext(seed: 0);

            DataViewSchema dvs;

            ITransformer model = null;
            if (trainOrLoad)
            {
                //czytanie danych                
                TextLoader textLoader = mlContext.Data.CreateTextLoader(new TextLoader.Options()
                {
                    Separators = new char[] { ',' },
                    HasHeader = false,
                    Columns = new TextLoader.Column[]
                    {
                        new TextLoader.Column("SepalLength", DataKind.Single, 0),
                        new TextLoader.Column("SepalWidth", DataKind.Single, 1),
                        new TextLoader.Column("PetalLength", DataKind.Single, 2),
                        new TextLoader.Column("PetalWidth", DataKind.Single, 3)
                        //ignorujemy etykiety
                    }
                });
                IDataView dataView = textLoader.Load(dataPath);
                dvs = dataView.Schema;

                //tworzenie potoku przetwarzania
                string featuresColumnName = "Features";
                var pipeline = mlContext.Transforms
                    .Concatenate(featuresColumnName, "SepalLength", "SepalWidth", "PetalLength", "PetalWidth")
                    .Append(mlContext.Clustering.Trainers.KMeans(featuresColumnName, numberOfClusters: 3));

                //trenowanie modelu
                model = pipeline.Fit(dataView);

                //zapis modelu
                using (var stream = new FileStream(trainedClusteringModelFilePath, FileMode.Create, FileAccess.Write, FileShare.Write))
                {
                    mlContext.Model.Save(model, textLoader, stream);
                }
            }
            else
            {
                Console.WriteLine("Loading trained model from " + trainedClusteringModelFilePath + "...");                
                using (var stream = File.OpenRead(trainedClusteringModelFilePath))
                {                    
                    model = mlContext.Model.Load(stream, out dvs);
                }
            }

            //użycie modelu do utworzenia predyktora
            //PredictionEngine<UnlabeledIrisData, ClusterPrediction> predictor = model.CreatePredictionEngine<UnlabeledIrisData, ClusterPrediction>(mlContext);
            PredictionEngine<UnlabeledIrisData, ClusterPrediction> predictor = mlContext.Model.CreatePredictionEngine<UnlabeledIrisData, ClusterPrediction>(model, dvs);

            //do którego klastra należy ten obiekt
            UnlabeledIrisData Setosa = new UnlabeledIrisData
            {
                SepalLength = 5.1f,
                SepalWidth = 3.5f,
                PetalLength = 1.4f,
                PetalWidth = 0.2f
            };

            var prediction = predictor.Predict(Setosa);
            Console.WriteLine($"Cluster: {prediction.PredictedClusterId}");
            Console.WriteLine($"Distances: {string.Join(" ", prediction.Distances)}");
        }
        #endregion

        static void Main(string[] args)
        {
            Console.WriteLine("Multiclass classification\n-------------------------");
            supervisedTraining();

            Console.WriteLine("\n\nClustering\n----------");
            clustering();
        }
    }
}
