//
// DatabaseMapper.cs
//
// Author:
//       Carsten Sonne Larsen <cs@innolan.dk>
//
// Copyright (c) 2013 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.Generic;
using System.Data;
using System.Linq;
using System.Threading;
using MySql.Data.MySqlClient;
using Ntp.Analyzer.Log;
using Ntp.Analyzer.Objects;
using Ntp.Data;

namespace Ntp.Analyzer.Data.Static
{
    /// <summary>
    /// Base class for ORM mappers. Can be used for mapping object stored in databases.
    /// </summary>
    /// <remarks>Based on MySql</remarks>
    public abstract class DatabaseMapper<T> : DataMapper<T> , ITableInitializer
        where T : PersistentObject
    {
        protected DatabaseMapper(LogBase log)
            : base(log)
        {
        }

        private readonly string connectionString = DataConnection.String;
        private readonly Dictionary<int, T> items = new Dictionary<int, T> ();
        static AutoResetEvent autoEvent = new AutoResetEvent (true);
        private MySqlCommand command;
        private MySqlConnection connection;
        private bool hasContent;

        protected abstract string TableName { get; }

        protected abstract string CreateSql { get; }

        protected abstract bool UseCache { get; }

        protected bool HasContent {
            get { return hasContent; }
        }

        public T this [int id] {
            get {
                if (!UseCache)
                    throw new NotSupportedException ("Mapper does not implement caching.");

                if (!HasContent)
                    ReadContent ();

                return items.ContainsKey (id) ? items [id] : FetchExternal (id);
            }
        }

        protected IEnumerable<T> Content {
            get { return items.Values; }
        }

        protected MySqlCommand Command {
            get { return command; }
        }

        protected MySqlDataReader Reader;
        protected Object Locker = new Object ();

        protected virtual T FetchExternal (int id)
        {
            return null;
        }

        private void ReadContent ()
        {
            // Force fetch from db
            foreach (T item in this)
                ;
        }

        protected void AddItem (T item)
        {
            items.Add (item.Id, item);
            hasContent = true;
        }

        protected void Open ()
        {
            autoEvent.WaitOne ();

            if (Reader != null && !Reader.IsClosed) {
                Reader.Close ();
                Reader = null;
            }

            if (connection == null) {
                connection = new MySqlConnection (connectionString);
            }
            
            if (connection.State == ConnectionState.Closed) {
                connection.Open ();
            }

            command = new MySqlCommand ();
            command.Connection = connection;
        }

        protected void Close ()
        {
            if (connection != null)
                connection.Close ();

            connection = null;

            autoEvent.Set ();
        }

        public void Save (T item)
        {

            if (item.NewObject) {
                Insert (item);

                if (UseCache)
                    AddItem (item);
            } else
                Update (item);

        }

        protected abstract void Insert (T item);

        protected abstract void Update (T item);

        private const string checkSql = 
            "SELECT table_name " +
            "FROM information_schema.tables " +
            "WHERE table_schema = '{0}' " +
            "AND table_name = '{1}';";

        public void CheckTable ()
        {
            string sql = String.Format (checkSql,
                DataConnection.DatabaseName,
                TableName);

            bool exists = false;

            try {
                Open ();
                Command.CommandText = sql;
                Reader = Command.ExecuteReader ();
                exists = Reader.Read();
            } finally {
                Close ();
            }

            if (!exists) {
                Log.WriteLine (
                    "Creating new table '" + TableName + "'.", 
                    Severity.Notice);
                
                try {
                    Open ();
                    Command.CommandText = CreateSql;
                    Command.Prepare ();
                    Command.ExecuteNonQuery ();
                } finally {
                    Close ();
                }
            }
        }
    }
}