//
// DatabaseUpdater.cs
//
// Author:
//       Carsten Sonne Larsen <cs@innolan.dk>
//
// Copyright (c) 2016 Carsten Sonne Larsen
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using Ntp.Log;

namespace Ntp.Data.Schema
{
    public class DatabaseUpdater : IEnumerable<IVersionChanges>
    {
        public DatabaseUpdater(ISqlFactory factory, LogBase log)
        {
            this.factory = factory;
            this.log = log;
            changes = new List<IVersionChanges>();
        }

        private readonly List<IVersionChanges> changes;
        private readonly ISqlFactory factory;
        private readonly LogBase log;

        IEnumerator IEnumerable.GetEnumerator()
        {
            return changes.GetEnumerator();
        }

        public IEnumerator<IVersionChanges> GetEnumerator()
        {
            return changes.GetEnumerator();
        }

        public void Add(IVersionChanges item)
        {
            changes.Add(item);
        }

        public void ApplyChanges()
        {
            var controller = new VersionController(factory, log);
            int databaseVersion = controller.FindCurrentVersion();

            if (databaseVersion <= 0)
            {
                IVersionChanges last = changes.OrderBy(c => c.VersionNumber).LastOrDefault();
                if (last != null)
                    controller.SetCurrentVersion(last);

                return;
            }

            IVersionChanges lastVersion = null;
            IEnumerable<IVersionChanges> versionChanges = changes.
                OrderBy(c => c.VersionNumber).
                Where(c => c.VersionNumber > databaseVersion);

            if (versionChanges.Count() == 0)
            {
                log.WriteLine("Database schema is latest version.", Severity.Info);
                return;
            }

            log.WriteLine("Applying database changes...", Severity.Notice);

            DbConnection connection = factory.CreateConnection();
            connection.Open();

            foreach (IVersionChanges version in versionChanges)
            {
                DbTransaction transaction = connection.BeginTransaction();
                try
                {
                    foreach (SqlChangeSet change in version)
                    {
                        DbCommand command = factory.CreateCommand();
                        command.Connection = connection;
                        command.CommandText = change.Sql;
                        command.Transaction = transaction;
                        command.Prepare();
                        command.ExecuteNonQuery();
                    }
                    transaction.Commit();
                }
                catch (Exception e)
                {
                    log.WriteLine("Failed to apply database change script.", Severity.Error);
                    log.WriteLine(e, Severity.Trace);
                    transaction.Rollback();
                    return;
                }

                lastVersion = version;
                log.WriteLine(String.Format(
                    "Changes for version {0} applied.", lastVersion.VersionText),
                    Severity.Notice
                    );
            }

            if (connection.State == ConnectionState.Open)
                connection.Close();

            controller.SetCurrentVersion(lastVersion);
        }
    }
}