// ========================================================================
//
// Copyright (C) 2016-2017 MARC CHEVALDONNE
//                         marc.chevaldonne.free.fr
//
// Module        : Program.cs
// Author        : Marc Chevaldonné
// Creation date : 2016-10-04
//
// ========================================================================
using System.IO;
using System.Linq;
using System.Text;
using static System.Console;

namespace ex_037_002_FileStream
{
    class Program
    {
        /// <summary>
        /// L'architecture des streams (flux) en .NET est basée sur trois concepts :
        /// - les backing stores, i.e. un fichier, une connection réseau, une chambre d'isolation... quelque chose qui donne du sens au flux, en entrée ou en sortie du traitement (une source ou une destination),
        /// - des décorateurs,
        /// - des adaptateurs.
        /// Tout ceci est représenté par la classe abstraite Stream, possédant un jeu de méthodes pour l'écriture, la lecture et le positionnement du curseur dans le flux.
        /// Un Stream ne fonctionne pas comme un tableau ou une collection, ses données ne sont que partiellement accessible en fonction de l'avancement de la lecture ou de l'écriture.
        /// En conséquence, le coût mémoire est plus faible, mais la navigation dans les données plus contrainte.
        /// Reprenons les trois catégories :
        /// - les backing stores streams peuvent être des sous-classes de Stream, comme : FileStream, NetworkStream, IsolatedStorageStream, MemoryStream. Cet exemple présentera FileStream.
        /// - les décorateurs s'appuient sur les précédents et modifient leurs données, comme : DeflateStream, GZipStream, CryptoStream, BufferedStream. Les décorateurs ont la même interface Stream, 
        /// en conséquence, ils ne changent en rien l'utilisation du backing stores choisi. Ils le décorent et peuvent se chaîner ou se brancher à la volée.
        /// Note : les backing stores et les décorateurs ne fonctionnent qu'avec des bytes. Pour les rendre plus "agréables" à manipuler, il faut utiliser des adaptateurs.
        /// - les adaptateurs enveloppent les flux pour permettre de travailler avec du texte ou de l'XML et être ainsi plus proche de l'application.
        /// Contrairement aux décorateurs, ils ne sont pas eux-mêmes des flux, et ne peuvent pas être chaînés. On compte parmi eux : StreamReader et StreamWriter pour le texte, 
        /// BinaryReader et BinaryWriter pour des données (int, float, string...), XmlReader et XmlWriter pour des données XML.
        /// 
        /// La classe de base Stream fournit des méthodes pour lire (CanRead, Read, ReadByte), écrire (CanWrite, Write, WriteByte), chercher (CanSeek, Position, SetLength, Length, Seek),
        /// fermer ou vider (Close, Dispose, Flush), configurer des timeouts (CanTimeout, ReadTimeout, WriteTimeout)...
        /// Depuis la version 4.5 du .NET Framework, elle possède également des méthodes asynchrones de ces méthodes (ReadAsync, WriteAsync...).
        /// 
        /// Dans la suite de cet exemple, nous utiliserons un FileStream. Puis dans les trois exemples suivants, nous utiliserons les adaptateurs StreamReader, StreamWriter,
        /// BinaryReader, BinaryWriter, XmlReader, XmlWriter.
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            OutputEncoding = Encoding.UTF8;

            Directory.SetCurrentDirectory(Path.Combine(Directory.GetCurrentDirectory(), "..//..//..//..//ex_037_002_FileStream"));
            //Construction d'un FileStream : le plus simple est d'utiliser l'un des trois méthodes suivantes
            FileStream fileStream1 = File.OpenRead("fichier.txt"); // en lecture seule
            fileStream1.Dispose();
            FileStream fileStream2 = File.OpenWrite("fichier2.txt"); // en écriture seule
            fileStream2.Dispose();
            FileStream fileStream3 = File.Create("fichier3.txt"); // écriture et lecture
            fileStream3.Dispose();

            //on peut également instancier les FileStream de la manière suivante :
            fileStream1 = new FileStream("fichier.txt", FileMode.Open);
            fileStream1.Dispose();

            //FileModes :
            // - lecture seule : FileMode.Open
            // - écriture/lecture : 
            //      - si le fichier existe déjà :
            //              - FileMode.Truncate (tronque le fichier)
            //              - FileMode.Open : non tronqué
            //      - si le fichier n'existe pas : FileMode.CreateNew
            //      - si on ne sait pas...
            //              - FileMode.Create : tronque le fichier éventuellement existant
            //              - FileMode.OpenOrCreate : le laisse exister s'il existe déjà, sinon, le crée
            //              - FileMode.Append : pour ajouter à la fin du fichier


            //on utilise en général un bloc using car un flux est une ressource non managée.
            //le bloc using prend entre parenthèses la création du flux. A la fin du bloc, la ressource est automatiquement nettoyée (comme si on avait appelé Dispose).
            //L'utilisation du bloc using part du principe qu'on se souvient toujours qu'il faudra nettoyer la mémoire non managée au moment où on la crée, mais que parfois, on oublie avant
            // de le faire :)
            // En conséquence, en même temps qu'on crée le flux, on dit qu'on le nettoye.
            using (var fileStream4 = File.Create("fichier.txt"))
            {
                fileStream4.WriteByte(63); // écrit 63
                fileStream4.WriteByte(21); // écrit 21
                byte[] tab = { 1, 2, 3, 4, 5 };
                fileStream4.Write(tab, 0, tab.Length); // écrit un block de 5 bytes

                WriteLine($"taille actuelle du fileStream : {fileStream4.Length}"); // affiche la taille du fileStream
                WriteLine($"position actuelle du fileStream : {fileStream4.Position}"); // affiche la position du fileStream

                fileStream4.Position = 0; // repositionne la position au début du fichier

                WriteLine(fileStream4.ReadByte()); // lit un byte (63)
                WriteLine(fileStream4.ReadByte()); // lit un byte (21)

                byte[] tab2 = new byte[5];
                WriteLine(fileStream4.Read(tab2, 0, tab2.Length)); // lit un bloc de 5 bytes et les places dans tab
                WriteLine(fileStream4.Read(tab2, 0, tab2.Length)); // comme nous sommes arrivés à la fin du fichier, la moindre lecture rend 0
            }

            //à peu près la même chose en asynchrone :
            FileStreamExampleAsync();
            ReadLine();

            //méthodes raccourcis qui font tout d'un seul coup
            WriteLine(File.ReadAllText("exemple_filestream.txt"));
            WriteLine();
            foreach (string line in File.ReadLines("exemple_filestream.txt"))
            {
                WriteLine($"-- {line}");
            }

            File.WriteAllText("exemple_filestream2.txt", File.ReadAllText("exemple_filestream.txt"));
            File.WriteAllLines("exemple_filestream3.txt", File.ReadLines("exemple_filestream.txt").Select(s => $"-- {s}"));
        }

        async static void FileStreamExampleAsync()
        {
            using (var fileStream5 = File.Create("fichier2.txt"))
            {
                byte[] tab = { 1, 2, 3, 4, 5 };
                await fileStream5.WriteAsync(tab, 0, tab.Length); // écrit un block de 5 bytes

                fileStream5.Position = 0; // repositionne la position au début du fichier

                byte[] tab2 = new byte[5];
                WriteLine(await fileStream5.ReadAsync(tab2, 0, tab2.Length)); // lit un bloc de 5 bytes et les places dans tab
            }
        }
    }
}
