using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using System.Linq;
using System.Diagnostics;

namespace ex_042_012_EF_CF_Dictionary
{
    /// <summary>
    /// La classe qui dérive de DbContext est celle qui permettra de faire les opérations CRUD sur le modèle.
    /// Cette classe contient deux DbSet<T> pour permettre de réaliser des opérations CRUD sur les types T, ici Nounours et CarnetDeSante.
    /// </summary>
    public class NounoursDBEntities : DbContext
    {
        public virtual DbSet<NounoursEx> NounoursSet { get; set; }
        public virtual DbSet<LitEx> LitsSet { get; set; }
        public virtual DbSet<Score> ScoresSet { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=ex_042_012_EF_CF_Dictionary.Nounours.mdf;Trusted_Connection=True;");
        }

        /// <summary>
        /// méthode appelée lors de la création du modèle. 
        /// </summary>
        /// <param name="modelBuilder"></param>
        //protected override void OnModelCreating(ModelBuilder modelBuilder)
        //{
        //    //création de la table TableNounours
        //    modelBuilder.Entity<Nounours>().ToTable("Nounours"); //nom de la table
        //    modelBuilder.Entity<Nounours>().HasKey(n => n.UniqueId); //définition de la clé primaire
        //    modelBuilder.Entity<Nounours>().Property(n => n.UniqueId);
        //    modelBuilder.Entity<Nounours>().Property(n => n.Nom).IsRequired()
        //                                                        .HasMaxLength(256); //définition de la colonne Nom
        //    modelBuilder.Entity<Nounours>().Property(n => n.DateDeNaissance).HasColumnName("Naissance"); //changement du nom de la colonne Naissance
        //    modelBuilder.Entity<Nounours>().Property<Guid>("ForeignKey");
        //    modelBuilder.Entity<Nounours>().Property<Score>("Score");
        //    //note : la colonne NbPoils n'est pas changée : utilisation des conventions EF

        //    //création de la table "Lits"
        //    modelBuilder.Entity<Lit>().ToTable("Lits"); // nom de la table
        //    modelBuilder.Entity<Lit>().HasKey(c => c.UniqueId); //définition de la clé primaire
        //    modelBuilder.Entity<Lit>().Property(c => c.UniqueId)
        //                                        .ValueGeneratedOnAdd();
        //    modelBuilder.Entity<Lit>().Property<IEnumerable<Score>>("ScoresList");
        //    //note : la colonne LastModified n'est pas touchée : utilisation des conventions EF


        //    //on précise qu'il y a une relation entre Score et Nounours
        //    modelBuilder.Entity<Score>() //l'entité Nounours...
        //                .HasOne("Nounours") //a une propriété obligatoire Carnet...
        //                .WithOne("Score") //reliée à la propriété Owner du Carnet...
        //                .HasForeignKey("Nounours", "ForeignKey");//dont la propriété UniqueId est une Foreign Key
        //    //remplace la ForeignKey

        //    modelBuilder.Entity<Lit>()
        //        .HasMany("ScoresList")
        //        .WithOne("Lit")
        //        .HasForeignKey("UniqueId");

        //    base.OnModelCreating(modelBuilder);
        //}

        /// <summary>
        /// permet de récupérer les données rentrées en base en les projetant dans les types utilisés pour le Model.
        /// </summary>
        /// <param name="lits">les lits rentrés en base</param>
        /// <param name="nounours">les nounours rentrés en base</param>
        /// <returns>true si la récupération est réussie, false sinon</returns>
        public bool GetAll(out IEnumerable<Lit> lits, out IEnumerable<Nounours> nounours)
        {
            try
            {
                // récupère la collection de NounoursEx contenus dans la base et les projette en collection de Nounours
                nounours = NounoursSet.ToList().Select(nEx => nEx.Nounours);
                //récupère la collection de LitEx contenus dans la base et les projette en collection de Lit
                lits = LitsSet.ToList().Select(lEx => lEx.Lit);

                //parcourt la collection des scores contenus en base...
                foreach (Score score in ScoresSet)
                {
                    //...pour chaque score ...
                    //... trouve le lit concerné par ce score...
                    Lit leLit = lits.Single(l => l.Propriétaire == score.Lit.Propriétaire);
                    //...trouve le nounours concerné par ce score...
                    Nounours leNounours = nounours.Single(n => n.Equals(score.Key.Nounours));
                    //... met à jour le dictionnaire dans l'instance de Lit
                    leLit[leNounours] = score.Value;
                }
                return true;
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
                lits = null;
                nounours = null;
                return false;
            }
        }

        NounoursEx[] lesNounoursEx;
        LitEx[] lesLitsEx;
        List<Score> lesScores = new List<Score>();

        /// <summary>
        /// ajoute des lits et des nounours à la base
        /// Pour cela, cette méthode projette les Lit et les Nounours en LitEx, NounoursEx et Score
        /// </summary>
        /// <param name="lesLits"></param>
        /// <param name="lesNounours"></param>
        public void AddAll(IEnumerable<Lit> lesLits, IEnumerable<Nounours> lesNounours)
        {
            //projection des données dans les types NounoursEx, LitEx et Scores pour permettre d'utiliser EntityFramework pour l'insertion en base
            lesNounoursEx = lesNounours.Select(n => new NounoursEx { Nounours = n }).ToArray();
            lesLitsEx = lesLits.Select(l => new LitEx { Lit = l }).ToArray();

            //met à jour la collection de scores
            foreach (var litEx in lesLitsEx)
            {
                UpdateScores(litEx);
            }

            //met à jour les tables
            NounoursSet.AddRange(lesNounoursEx);
            LitsSet.AddRange(lesLitsEx);
            ScoresSet.AddRange(lesScores);

            SaveChanges();
        }

        /// <summary>
        /// transforme les dictionnaires en collection de scores
        /// </summary>
        /// <param name="litEx"></param>
        private void UpdateScores(LitEx litEx)
        {
            foreach (var kvp in litEx.Lit.Scores)
            {
                NounoursEx nounoursEx = lesNounoursEx.Single(nEx => nEx.Nounours == kvp.Key);
                Score score = new Score()
                {
                    Key = nounoursEx,
                    Value = kvp.Value,
                    Lit = litEx
                };
                lesScores.Add(score);
                litEx.Scores.Add(score);
                nounoursEx.Score = score;
            }
        }
    }
}
