using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Diagnostics;
using System.Linq;
using DatabaseEditor.DAL.Entities;

namespace DatabaseEditor.DAL
{
    public class LibraryDbInitializer : DropCreateDatabaseIfModelChanges<LibraryDbContext>
    {
        private readonly int _authorsCount;
        private readonly int _booksCount;
        private readonly int _minAuthorsBooks;
        private readonly int _maxAuthorsBooks;
        private const int ChunkSize = 1000;

        public LibraryDbInitializer(int authorsCount, int booksCount, int minAuthorsBooks, int maxAuthorsBooks)
        {
            _authorsCount = authorsCount;
            _booksCount = booksCount;
            _minAuthorsBooks = minAuthorsBooks;
            _maxAuthorsBooks = maxAuthorsBooks;
        }

        protected override void Seed(LibraryDbContext context)
        {
            List<Author> authors = new List<Author>();
            List<Book> books = new List<Book>();
            int objectsSeeded = 0;

            for (int i = 0; i < _booksCount; i++)
            {
                books.Add(Randomizer.RandomBook());
                objectsSeeded++;
            }
            for (int i = 0; i < _authorsCount; i++)
            {
                var author = Randomizer.RandomAuthor();
                var booksCount = Randomizer.RandomInt(_minAuthorsBooks, _maxAuthorsBooks);
                AssignSomeBooks(author, books, booksCount);
                authors.Add(author);
                objectsSeeded += 1 + booksCount;
            }

            context.Configuration.AutoDetectChangesEnabled = false; // improves performance

            Trace.WriteLine($"Started seeding of {objectsSeeded} records");
            Stopwatch stopwatch = Stopwatch.StartNew();
            int numberOfBooksChunks = (int) Math.Ceiling((double) _booksCount / ChunkSize);
            for (int i = 0; i < numberOfBooksChunks; i++)
            {
                var booksRemaining = _booksCount - i * ChunkSize;
                Trace.WriteLine($"Seeding books {i * ChunkSize} to {i * ChunkSize + Math.Min(ChunkSize, booksRemaining)}");
                var rangeOfBooks = books.GetRange(i * ChunkSize, Math.Min(ChunkSize, booksRemaining));
                context.Books.AddRange(rangeOfBooks);
                context.SaveChanges();
            }

            int numberOfAuthorsChunks = (int) Math.Ceiling((double) _authorsCount / ChunkSize);
            for (int i = 0; i < numberOfAuthorsChunks; i++)
            {
                var authorsRemaining = _authorsCount - i * ChunkSize;
                Trace.WriteLine($"Seeding authors {i * ChunkSize} to {i*ChunkSize + Math.Min(ChunkSize, authorsRemaining)}");
                var rangeOfAuthors = authors.GetRange(i * ChunkSize, Math.Min(ChunkSize, authorsRemaining));
                context.Authors.AddRange(rangeOfAuthors);
                context.SaveChanges();
            }

            stopwatch.Stop();
            Trace.WriteLine("Seeding took " + stopwatch.Elapsed);

            context.SaveChanges();
            base.Seed(context);
        }

        /// <summary>
        /// Assigns <paramref name="count"/> books from <paramref name="books"/> to <paramref name="author"/>.
        /// </summary>
        /// <param name="author"></param>
        /// <param name="books">collection of books, from where will be books assigned</param>
        /// <param name="count">number of books to assign</param>
        private void AssignSomeBooks(Author author, ICollection<Book> books, int count)
        {
            for (int i = 0; i < count; i++)
            {
                var randomBook = books.ElementAt(Randomizer.RandomInt(0, books.Count - 1));
                author.Books.Add(randomBook);
            }
        }
    }
}