// ========================================================================
//
// Copyright (C) 2017-2018 MARC CHEVALDONNE
//                         marc.chevaldonne.free.fr
//
// Module        : Program.cs
// Author        : Marc Chevaldonné
// Creation date : 2017-09-19
//
// ========================================================================

using System;
using static System.Console;

namespace ex_008_005_pattern_matching
{
    class Program
    {
        static void Main(string[] args)
        {
            //C# 7.0 introduit le pattern matching, dont l'objectif est de permettre de "tester" si un objet respecte certaines conditions de "forme".
            //En C# 7.0, on trouve pour le moment trois types de patterns :
            //  - constant patterns : qui permettent de tester si un objet est égal à une constante
            //  - type patterns : qui permettent de tester si un objet est de type T, et si c'est le cas, de le tester dans une nouvelle variable
            //  - var patterns : qui permettent de pacer un objet dans une nouvelle variable du même type
            //Les patterns matching devraient certainement être beaucoup plus utilisés dans les versions futures de C#.
            //Pour le moment, on en trouve deux cas d'utilisation majeurs : les expressions "is" et les clauses "case" dans les switch.

            //PATTERN MATCHING AND IS-EXPRESSIONS
            void DisplayPrice(object price)
            {
                //constant pattern dans une is-expression avec comparaison à null
                if (price is null)
                {
                    WriteLine("Vous avez rentré une valeur nulle");
                    return;
                }

                //var pattern dans une is-expression
                if (price is var price2)
                {
                    WriteLine($"price2 est de type : {price2.GetType().Name} ; price2 a pour valeur : {price2}");
                }

                //type pattern dans une is-expression avec comparaison à un type numérique
                if (!(price is float p))
                {
                    WriteLine($"price n'est pas du bon type. Type attendu : Single, Type utilisé : {price.GetType().Name}");
                    return;
                }
                WriteLine($"Vous avez rentré : {p:0.00} euros");
            }

            DisplayPrice(null);
            DisplayPrice(32.0f);
            DisplayPrice("coucou");
            DisplayPrice(3);

            //var-pattern : on a une copie des variables, donc attention aux types références !
            void TestVarPattern(Nounours original)
            {
                WriteLine("Avant var pattern:");
                WriteLine($"original : {original}");
                if (original is var copie)
                {
                    copie.Nom = "Chucky";
                    copie.NbPoils = 4;
                }

                WriteLine("Après var pattern:");
                WriteLine($"original : {original}");
                WriteLine($"copie : {copie}");
            }

            TestVarPattern(new Nounours { Nom = "Gizmo", NbPoils = 123456789, Naissance = new DateTime(1984, 6, 8) });



            //PATTERN MATCHING DANS UN SWITCH AVEC DES CASES ET DES CONDITIONS SUPPLEMENTAIRES

            Nounours[] nounours =
            {
                new Nounours { Nom = "Gizmo", NbPoils = 123456789, Naissance = new DateTime(1984, 6, 8) },
                new NounoursProf { Nom = "Yoda", NbPoils = 4, Naissance = new DateTime(1980, 5, 21), Matière="How to become a Jedi?" },
                new NounoursElève { Nom = "Ewok", NbPoils = 98989898, Naissance = new DateTime(1983, 5, 25), FormationSuivie="How to destroy an AT-ST?" },
                new NounoursElève { Nom = "Chucky", NbPoils = 2, Naissance = new DateTime(1988, 11, 9), FormationSuivie="How to become naughty?" },
                null
            };

            void TestPatternMatchingInASwitch(Nounours monNounours)
            {
                //Il est possible de faire un switch sur n'importe quel type désormais
                //La nouvelle gestion du switch fait que maintenant, l'ordre des clauses "case" a de l'importance (dans l'exemple ci-dessous, si on mettait la 2ème avant la 1ère,
                //la 1ère ne serait jamais exécutée). Heureusement, le compilateur vous aidera a trouvé les cas qui ne seront jamais exécutés !
                switch (monNounours)
                {
                    //type pattern avec une condition (when)
                    case NounoursElève élève when (élève.Naissance < new DateTime(1985, 1, 1)):
                        WriteLine($"{élève.Nom} est un vieil élève apprenant : {élève.FormationSuivie}");
                        break;
                    //type pattern sans condition
                    case NounoursElève élève:
                        WriteLine($"{élève.Nom} est un élève apprenant : {élève.FormationSuivie}");
                        break;
                    //type pattern
                    case NounoursProf prof:
                        WriteLine($"{prof.Nom} est un prof qui enseigne : {prof.Matière}");
                        break;
                    //même s'il y a un autre case plus bas (comme dans cet exemple), le default est toujours évalué en dernier, pour des raisons de compatibilité.
                    //Pour une meilleure lisibilité, ne faites donc pas comme dans cet exemple : laissez le default à la fin
                    default:
                        WriteLine($"{monNounours.Nom} est un Nounours comme un autre, sans histoire particulière...");
                        break;
                    //constant pattern
                    case null:
                        WriteLine($"Attention, ce Nounours est null ! (nom du paramètre : {nameof(monNounours)})");
                        break;
                }
            }

            foreach (var n in nounours)
            {
                TestPatternMatchingInASwitch(n);
            }
        }
    }

    class Nounours
    {
        public string Nom { get; set; }
        public int NbPoils { get; set; }
        public DateTime Naissance { get; set; }

        public override string ToString()
        {
            return $"{Nom} a {NbPoils} poils et est né en {Naissance.Year}";
        }
    }

    class NounoursProf : Nounours
    {
        public string Matière { get; set; }
    }

    class NounoursElève : Nounours
    {
        public string FormationSuivie { get; set; }
    }
}
