using Coscine.Database.DataModel;
using Coscine.Database.Util;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using LinqKit;

namespace Coscine.Database.Models
{
    public abstract class DatabaseModel<T> where T : class
    {
        public abstract Expression<Func<T, Guid>> GetIdFromObject();

        public abstract void SetObjectId(T databaseObject, Guid id);

        public abstract Microsoft.EntityFrameworkCore.DbSet<T> GetITableFromDatabase(CoscineDB db);


        // GetById utilizes the Expression functionality since Linq2Sql does not support method calls
        // This is therefore a workaround for getting the Id parameter
        public virtual T GetById(Guid id)
        {
            Expression<Func<T, Guid>> expression = GetIdFromObject();
            return DatabaseConnection.ConnectToDatabase((db) =>
            {
                return
                    (from tableEntry in GetITableFromDatabase(db).AsExpandable()
                     where expression.Invoke(tableEntry) == id
                     select tableEntry).FirstOrDefault();
            });
        }

        public virtual T GetWhere(Expression<Func<T, bool>> whereClause)
        {
            return DatabaseConnection.ConnectToDatabase((db) =>
            {
                return
                    (from tableEntry in GetITableFromDatabase(db).AsExpandable()
                     where whereClause.Invoke(tableEntry)
                     select tableEntry).FirstOrDefault();
            });
        }

        public virtual IEnumerable<T> GetAll()
        {
            return DatabaseConnection.ConnectToDatabase((db) =>
            {
                return
                    (from tableEntry in GetITableFromDatabase(db)
                     select tableEntry).ToList();
            });
        }

        public virtual IEnumerable<T> GetAllWhere(Expression<Func<T, bool>> whereClause)
        {
            return DatabaseConnection.ConnectToDatabase((db) =>
            {
                return
                    (from tableEntry in GetITableFromDatabase(db).AsExpandable()
                     where whereClause.Invoke(tableEntry)
                     select tableEntry).ToList();
            });
        }

        public virtual int Update(T databaseObject)
        {
            return DatabaseConnection.ConnectToDatabase((db) =>
            {
                return (int) db.Update(databaseObject).State;
            });
        }

        public virtual int Insert(T databaseObject)
        {
            if (GetIdFromObject().Compile()(databaseObject) == new Guid("00000000-0000-0000-0000-000000000000"))
            {
                SetObjectId(databaseObject, Guid.NewGuid());
            }

            return DatabaseConnection.ConnectToDatabase((db) =>
            {
                return db.Insert(databaseObject);
            });
        }

        public virtual int Delete(T databaseObject)
        {
            return DatabaseConnection.ConnectToDatabase((db) =>
            {
                return db.Delete(databaseObject);
            });
        }
    }
}