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

using static System.Console;
using System.Collections.Generic;
using ex_023_017_Deep_Read_only_collections_dll;
using System.Text;

namespace ex_023_017_Deep_Read_only_collections_exe
{
    class Program
    {
        /// <summary>
        /// L'exemple suivant montre comment encapsuler une collection d'un type dans le cas suivant :
        /// => le type est mutable, c'est-à-dire qu'on peut modifier son état interne en appelant ses méthodes ou propriétés
        ///     => MonLit possède une collection privée mNounours de Nounours où Nounours est mutable
        /// Comment encapsuler correctement une collection d'un tel type ?
        /// Dans l'exemple suivant, nous voulons protéger la collection contre :
        /// - les modifications de la collection (ajout ou suppression d'éléments),
        /// - la modification de la collection elle-même (on ne doit pas pouvoir faire de new)
        /// - la modification de chaque élément de la collection : on ne doit pas pouvoir faire de new, ni appeler des méthodes ou propriétés mutables.
        /// 
        /// En résumé, nous cherchons à rendre publique en lecture seule, la collection privée de nounours du lit, ainsi que chaque élément.
        /// 
        /// L'exemple 023_015 montrait comment encapsuler une collection d'un type immuable ou d'un type volontairement laissé mutable.
        /// Tous les mauvais cas présentés dans l'exemple 023_015 sont toujours mauvais ici, et ne sont donc pas représentés.
        /// Le solution qui avait été retenu dans l'exemple 023_015 consistait à utiliser un wrapper ReadOnlyCollection.
        /// Cette solution n'est pas envisageable si le type est mutable et que nous cherchons à le protéger.
        /// 
        /// La bonne solution consiste à :
        /// - rendre le type interne (Nounours)
        ///     c'est la raison pour laquelle cet exemple est sur deux assemblages :
        ///         . une bibliothèque de classes contenant le type interne et une classe contenant une collection privée de ce type ;
        ///         . un exécutable consommant la classe contenant la collection à encapsuler.
        /// - faire une façade immuable publique de ce type (INounours)
        ///     cette façade immuable ne contient que des propriétés et méthodes en lecture seule
        /// - profiter de la covariance des interfaces (IEnumerable, IReadOnlyCollection et IReadOnlyList)
        ///   et encapsuler la collection privée du type mutable interne avec une collection en lecture seule de la façade immuable
        /// => en effet : 
        ///     + les types IEnumerable, IReadOnlyCollection et IReadOnlyList sont des collections en lecture seule, elles ne sont donc pas modifiables
        ///     + elles ne peuvent pas être castées en une collection modifiable du type mutable à l'extérieur de la bibliothèque de classes car celui-ci est interne à la bibliothèque
        ///     + elles ne peuvent pas être castées en une collection modificable de la façade immuable car les classes (comme List, Array, Queue...) ne sont pas covariantes.
        /// => C'EST GAGNE !!!
        /// </summary>
        static void Main(string[] args)
        {
            OutputEncoding = Encoding.Unicode;

            MonLit monlit = new MonLit();

            //ce qu'on voulait empêcher est empêché : on ne peut pas appeler les setters publiques car on utilise des éléments du type de la façade immuable INounours
            //il ne sont donc accessibles qu'en lecture seule
            //            monlit.MesNounoursIEnumerable.ElementAt(0).NbPoils -= 100;


            //Essayons maintenant de tout casser :

            //Tentative 1 : caster MesNounours en List<Nounours>
            //impossible car le type Nounours est interne à la bibliothèque et n'est donc pas accessible dans un autre assemblage
            //            List<Nounours> test = monlit.MesNounoursIEnumerable as List<Nounours>; 
            //            List<Nounours> test2 = monlit.MesNounoursIReadOnlyCollection as List<Nounours>; 
            //            List<Nounours> test3 = monlit.MesNounoursIReadOnlyList as List<Nounours>; 

            //Tentative 2 : caster MesNounours en List<INounours> pour pouvoir modifier la collection
            //impossible car les classes, comme List, ne sont pas covariantes, alors que les interfaces le sont
            // on ne peut donc pas caster List<Nounours> en List<INounours> avec Nounours implémentant INounours
            // une exception est lancée
            List<INounours> test4 = monlit.MesNounoursIEnumerable as List<INounours>;
            string result4 = test4 == null ? "échec du cast de IEnumerable<INounours> vers List<Nounours>" : "cast de IEnumerable<INounours> vers List<Nounours> réussi";
            WriteLine(result4);

            List<INounours> test5 = monlit.MesNounoursIReadOnlyCollection as List<INounours>;
            string result5 = test4 == null ? "échec du cast de IReadOnlyCollection<INounours> vers List<Nounours>" : "cast de IReadOnlyCollection<INounours> vers List<Nounours> réussi";
            WriteLine(result5);

            List<INounours> test6 = monlit.MesNounoursIReadOnlyList as List<INounours>;
            string result6 = test4 == null ? "échec du cast de IReadOnlyList<INounours> vers List<Nounours>" : "cast de IReadOnlyList<INounours> vers List<Nounours> réussi";
            WriteLine(result6);

            //Tentative 3 : caster un élément de la collection en Nounours pour pouvoir le modifier
            //impossible puisque Nounours est interne et n'est donc pas accessible dans un autre assemblage que celui dans lequel il a été défini
            //(monlit.MesNounoursIEnumerable.ElementAt(0) as Nounours).NbPoils += 100;
            //(monlit.MesNounoursIReadOnlyCollection.ElementAt(0) as Nounours).NbPoils += 100;
            //(monlit.MesNounoursIReadOnlyList.ElementAt(0) as Nounours).NbPoils += 100;

            WriteLine();

            //ET VOILA CE QU'ON PEUT FAIRE :
            WriteLine("Avec IEnumerable<INounours> :");
            foreach (var n in monlit.MesNounoursIEnumerable)
            {
                WriteLine($"\t{n}");
            }

            WriteLine();
            WriteLine("Avec IReadOnlyCollection<INounours> :");
            int nbNounours = monlit.MesNounoursIReadOnlyCollection.Count;
            foreach (var n in monlit.MesNounoursIReadOnlyCollection)
            {
                WriteLine($"\t{n}");
            }

            WriteLine();
            WriteLine("Avec IReadOnlyList<INounours> :");
            nbNounours = monlit.MesNounoursIReadOnlyList.Count;
            for (int i = 0; i < nbNounours; i++)
            {
                WriteLine($"\t{i+1}/{nbNounours} : {monlit.MesNounoursIReadOnlyList[i]}");
            }

        }
    }
}
