// 
// Copyright (c) 2013-2016 Carsten Sonne Larsen <cs@innolan.dk>
// 
// 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.Diagnostics.CodeAnalysis;
using System.Linq;
using Ntp.Common.Log;
using Ntp.Data.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);
        }

        [SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities")]
        public void ApplyChanges()
        {
            var controller = new VersionController(factory, log);
            int databaseVersion = controller.FindCurrentVersion();

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

                return;
            }

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

            var changeList = versionChanges.ToList();
            if (changeList.Count == 0)
            {
                log.SchemaUpToDate();
                return;
            }

            log.ApplySchemaChanges();

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

            foreach (var version in changeList)
            {
                var transaction = connection.BeginTransaction();
                try
                {
                    foreach (var change in version)
                    {
                        var command = factory.CreateCommand();
                        command.Connection = connection;
                        command.CommandText = change.Sql;
                        command.Transaction = transaction;
                        command.Prepare();
                        log.SqlExecute(command.CommandText);
                        command.ExecuteNonQuery();
                    }
                    transaction.Commit();
                }
                catch (Exception e)
                {
                    log.SchemaUpdateError(e);
                    transaction.Rollback();
                    return;
                }

                lastVersion = version;
                log.SchemaChangesApplied(lastVersion.VersionText);
            }

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

            controller.SetCurrentVersion(lastVersion);
        }

        public bool CheckLatestVersion()
        {
            var controller = new VersionController(factory, log);
            int databaseVersion = controller.FindCurrentVersion();
            var latest = changes.OrderBy(c => c.VersionNumber).Last();

            if (databaseVersion == -1)
            {
                controller.SetCurrentVersion(latest);
                return true;
            }

            if (databaseVersion == latest.VersionNumber)
                return true;

            log.SchemaVersionError(databaseVersion, latest.VersionNumber);
            return false;
        }
    }
}