//---------------------------------------------------------------------
// <copyright file="FunctionDetailsReader.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;
using System.Data.Common;
using System.Data.EntityClient;
using System.Diagnostics;
using System.Data.Metadata.Edm;

namespace System.Data.Entity.Design.SsdlGenerator
{
    /// <summary>
    /// The purpose of this class is to give us strongly typed access to the results of the reader.
    /// NOTE: this class will dispose of the command when the reader is disposed.
    /// </summary>
    internal abstract class FunctionDetailsReader : IDisposable
    {
        private DbDataReader _reader;
        private EntityCommand _command;
        private EntityConnection _connection;
        private object[] _currentRow;

        public static FunctionDetailsReader Create(EntityConnection connection, IEnumerable<EntityStoreSchemaFilterEntry> filters, Version storeSchemaModelVersion)
        {
            Debug.Assert(connection != null, "the parameter connection is null");
            Debug.Assert(connection.State == ConnectionState.Open, "the connection is not Open");

            if (storeSchemaModelVersion >= EntityFrameworkVersions.Version3)
            {
                return new FunctionDetailsReaderV3(connection, filters);
            }
            else
            {
                return new FunctionDetailsReaderV1(connection, filters);
            }
        }

        protected void InitializeReader(EntityConnection connection, IEnumerable<EntityStoreSchemaFilterEntry> filters)
        {
            _connection = connection;

            _command = EntityStoreSchemaGeneratorDatabaseSchemaLoader.CreateFilteredCommand(
                        _connection,
                        FunctionDetailSql,
                        FunctionOrderByClause,
                        EntityStoreSchemaFilterObjectTypes.Function,
                        new List<EntityStoreSchemaFilterEntry>(filters),
                        new string[] { FunctionDetailAlias });
            _reader = _command.ExecuteReader(CommandBehavior.SequentialAccess);
        }

        internal bool Read()
        {
            Debug.Assert(_reader != null, "don't Read() when it is created from a memento");
            bool haveRow = _reader.Read();
            if (haveRow)
            {
                if (_currentRow == null)
                {
                    _currentRow = new object[ColumnCount];
                }
                _reader.GetValues(_currentRow);
            }
            else
            {
                _currentRow = null;
            }
            return haveRow;
        }

        public void Dispose()
        {
            // Technically, calling GC.SuppressFinalize is not required because the class does not
            // have a finalizer, but it does no harm, protects against the case where a finalizer is added
            // in the future, and prevents an FxCop warning.
            GC.SuppressFinalize(this);
            Debug.Assert(_reader != null, "don't Dispose() when it is created from a memento");
            _reader.Dispose();
            _command.Dispose();
        }

        internal abstract int ColumnCount { get; }

        public abstract string Catalog { get; }

        public abstract string Schema { get; }

        public abstract string ProcedureName { get; }

        public abstract string ReturnType { get; }

        public abstract bool IsIsAggregate { get; }

        public abstract bool IsBuiltIn { get; }

        public abstract bool IsComposable { get; }

        public abstract bool IsNiladic { get; }

        public abstract bool IsTvf { get; }

        public abstract string ParameterName { get; }

        public abstract bool IsParameterNameNull { get; }

        public abstract string ParameterType { get; }

        public abstract bool IsParameterTypeNull { get; }

        public abstract string ProcParameterMode { get; }

        public abstract bool IsParameterModeNull { get; }

        public bool TryGetParameterMode(out ParameterMode mode)
        {
            if (IsParameterModeNull)
            {
                mode = (ParameterMode)(-1);
                return false;
            }

            switch (ProcParameterMode)
            {
                case "IN":
                    mode = ParameterMode.In;
                    return true;
                case "OUT":
                    mode = ParameterMode.Out;
                    return true;
                case "INOUT":
                    mode = ParameterMode.InOut;
                    return true;
                default:
                    mode = (ParameterMode)(-1);
                    return false;
            }
        }

        internal EntityStoreSchemaGenerator.DbObjectKey CreateDbObjectKey()
        {
            Debug.Assert(_currentRow != null, "don't call this method when you not reading");
            return new EntityStoreSchemaGenerator.DbObjectKey(this.Catalog, this.Schema, this.ProcedureName, EntityStoreSchemaGenerator.DbObjectType.Function);
        }

        protected static T ConvertDBNull<T>(object value)
        {
            return Convert.IsDBNull(value) ? default(T) : (T)value;
        }

        public abstract void Attach(Memento memento);

        public abstract Memento CreateMemento();

        private static readonly string FunctionDetailAlias = "sp";
        protected abstract string FunctionDetailSql { get; }
        private static readonly string FunctionOrderByClause = @" 
            ORDER BY
                sp.SchemaName
            ,   sp.Name
            ,   sp.Ordinal
            ";

        internal sealed class FunctionDetailsReaderV1 : FunctionDetailsReader
        {
            internal FunctionDetailsReaderV1(MementoV1 memento)
            {
                _currentRow = memento.Values;
            }

            public FunctionDetailsReaderV1(EntityConnection connection, IEnumerable<EntityStoreSchemaFilterEntry> filters)
            {
                InitializeReader(connection, filters);
            }

            public override void Attach(Memento memento)
            {
                Debug.Assert(memento != null, "the parameter memento is null");
                Debug.Assert(memento.Values != null, "the values in the memento are null");
                Debug.Assert(memento is MementoV1, "the memento is for a different version");
                Debug.Assert(_reader == null, "don't attach to a real reader");
                _currentRow = memento.Values;
            }

            public override Memento CreateMemento()
            {
                Debug.Assert(_currentRow != null, "don't call this method when you not reading");
                return new MementoV1((object[])_currentRow.Clone());
            }

            const int PROC_SCHEMA_INDEX = 0;
            const int PROC_NAME_INDEX = 1;
            const int PROC_RET_TYPE_INDEX = 2;
            const int PROC_ISAGGREGATE_INDEX = 3;
            const int PROC_ISCOMPOSABLE_INDEX = 4;
            const int PROC_ISBUILTIN_INDEX = 5;
            const int PROC_ISNILADIC_INDEX = 6;
            const int PARAM_NAME_INDEX = 7;
            const int PARAM_TYPE_INDEX = 8;
            const int PARAM_DIRECTION_INDEX = 9;

            internal override int ColumnCount { get { return 10; } }

            public override string Catalog { get { return null; } }

            public override string Schema
            {
                get { return ConvertDBNull<string>(_currentRow[PROC_SCHEMA_INDEX]); }
            }

            public override string ProcedureName
            {
                get { return ConvertDBNull<string>(_currentRow[PROC_NAME_INDEX]); }
            }

            public override string ReturnType
            {
                get { return ConvertDBNull<string>(_currentRow[PROC_RET_TYPE_INDEX]); }
            }

            public override bool IsIsAggregate
            {
                get { return ConvertDBNull<bool>(_currentRow[PROC_ISAGGREGATE_INDEX]); }
            }

            public override bool IsBuiltIn
            {
                get { return ConvertDBNull<bool>(_currentRow[PROC_ISBUILTIN_INDEX]); }
            }

            public override bool IsComposable
            {
                get { return ConvertDBNull<bool>(_currentRow[PROC_ISCOMPOSABLE_INDEX]); }
            }

            public override bool IsNiladic
            {
                get { return ConvertDBNull<bool>(_currentRow[PROC_ISNILADIC_INDEX]); }
            }

            public override bool IsTvf { get { return false; } }

            public override string ParameterName
            {
                get { return (string)_currentRow[PARAM_NAME_INDEX]; }
            }

            public override bool IsParameterNameNull
            {
                get { return Convert.IsDBNull(_currentRow[PARAM_NAME_INDEX]); }
            }

            public override string ParameterType
            {
                get { return (string)_currentRow[PARAM_TYPE_INDEX]; }
            }

            public override bool IsParameterTypeNull
            {
                get { return Convert.IsDBNull(_currentRow[PARAM_TYPE_INDEX]); }
            }

            public override string ProcParameterMode
            {
                get { return (string)_currentRow[PARAM_DIRECTION_INDEX]; }
            }

            public override bool IsParameterModeNull
            {
                get { return Convert.IsDBNull(_currentRow[PARAM_DIRECTION_INDEX]); }
            }

            protected override string FunctionDetailSql
            {
                get
                {
                    return @"
            SELECT
                  sp.SchemaName
                , sp.Name 
                , sp.ReturnTypeName
                , sp.IsAggregate
                , sp.IsComposable
                , sp.IsBuiltIn
                , sp.IsNiladic
                , sp.ParameterName
                , sp.ParameterType
                , sp.Mode
            FROM (  
            (SELECT
                  r.CatalogName as CatalogName
              ,   r.SchemaName as SchemaName
              ,   r.Name as Name
              ,   r.ReturnType.TypeName as ReturnTypeName
              ,   r.IsAggregate as IsAggregate
              ,   true as IsComposable
              ,   r.IsBuiltIn as IsBuiltIn
              ,   r.IsNiladic as IsNiladic
              ,   p.Name as ParameterName
              ,   p.ParameterType.TypeName as ParameterType
              ,   p.Mode as Mode
              ,   p.Ordinal as Ordinal
            FROM
                OfType(SchemaInformation.Functions, Store.ScalarFunction) as r 
                 OUTER APPLY
                r.Parameters as p)
            UNION ALL
            (SELECT
                  r.CatalogName as CatalogName
              ,   r.SchemaName as SchemaName
              ,   r.Name as Name
              ,   CAST(NULL as string) as ReturnTypeName
              ,   false as IsAggregate
              ,   false as IsComposable
              ,   false as IsBuiltIn
              ,   false as IsNiladic
              ,   p.Name as ParameterName
              ,   p.ParameterType.TypeName as ParameterType
              ,   p.Mode as Mode
              ,   p.Ordinal as Ordinal
            FROM
                SchemaInformation.Procedures as r 
                 OUTER APPLY
                r.Parameters as p)) as sp
            ";
                }
            }
        }

        internal sealed class FunctionDetailsReaderV3 : FunctionDetailsReader
        {
            internal FunctionDetailsReaderV3(MementoV3 memento)
            {
                _currentRow = memento.Values;
            }

            public FunctionDetailsReaderV3(EntityConnection connection, IEnumerable<EntityStoreSchemaFilterEntry> filters)
            {
                InitializeReader(connection, filters);
            }

            public override void Attach(Memento memento)
            {
                Debug.Assert(memento != null, "the parameter memento is null");
                Debug.Assert(memento.Values != null, "the values in the memento are null");
                Debug.Assert(memento is MementoV3, "the memento is for a different version");
                Debug.Assert(_reader == null, "don't attach to a real reader");
                _currentRow = memento.Values;
            }

            public override Memento CreateMemento()
            {
                Debug.Assert(_currentRow != null, "don't call this method when you not reading");
                return new MementoV3((object[])_currentRow.Clone());
            }

            const int PROC_CATALOG_INDEX = 0;
            const int PROC_SCHEMA_INDEX = 1;
            const int PROC_NAME_INDEX = 2;
            const int PROC_RET_TYPE_INDEX = 3;
            const int PROC_ISAGGREGATE_INDEX = 4;
            const int PROC_ISCOMPOSABLE_INDEX = 5;
            const int PROC_ISBUILTIN_INDEX = 6;
            const int PROC_ISNILADIC_INDEX = 7;
            const int PROC_ISTVF_INDEX = 8;
            const int PARAM_NAME_INDEX = 9;
            const int PARAM_TYPE_INDEX = 10;
            const int PARAM_DIRECTION_INDEX = 11;

            internal override int ColumnCount { get { return 12; } }

            public override string Catalog
            {
                get { return ConvertDBNull<string>(_currentRow[PROC_CATALOG_INDEX]); }
            }

            public override string Schema
            {
                get { return ConvertDBNull<string>(_currentRow[PROC_SCHEMA_INDEX]); }
            }

            public override string ProcedureName
            {
                get { return ConvertDBNull<string>(_currentRow[PROC_NAME_INDEX]); }
            }

            public override string ReturnType
            {
                get { return ConvertDBNull<string>(_currentRow[PROC_RET_TYPE_INDEX]); }
            }

            public override bool IsIsAggregate
            {
                get { return ConvertDBNull<bool>(_currentRow[PROC_ISAGGREGATE_INDEX]); }
            }

            public override bool IsBuiltIn
            {
                get { return ConvertDBNull<bool>(_currentRow[PROC_ISBUILTIN_INDEX]); }
            }

            public override bool IsComposable
            {
                get { return ConvertDBNull<bool>(_currentRow[PROC_ISCOMPOSABLE_INDEX]); }
            }

            public override bool IsNiladic
            {
                get { return ConvertDBNull<bool>(_currentRow[PROC_ISNILADIC_INDEX]); }
            }

            public override bool IsTvf
            {
                get { return ConvertDBNull<bool>(_currentRow[PROC_ISTVF_INDEX]); }
            }

            public override string ParameterName
            {
                get { return (string)_currentRow[PARAM_NAME_INDEX]; }
            }

            public override bool IsParameterNameNull
            {
                get { return Convert.IsDBNull(_currentRow[PARAM_NAME_INDEX]); }
            }

            public override string ParameterType
            {
                get { return (string)_currentRow[PARAM_TYPE_INDEX]; }
            }

            public override bool IsParameterTypeNull
            {
                get { return Convert.IsDBNull(_currentRow[PARAM_TYPE_INDEX]); }
            }

            public override string ProcParameterMode
            {
                get { return (string)_currentRow[PARAM_DIRECTION_INDEX]; }
            }

            public override bool IsParameterModeNull
            {
                get { return Convert.IsDBNull(_currentRow[PARAM_DIRECTION_INDEX]); }
            }

            protected override string FunctionDetailSql
            {
                get
                {
                    return @"
            Function IsTvf(f Store.Function) as (f is of (Store.TableValuedFunction))
            SELECT
                  sp.CatalogName
                , sp.SchemaName
                , sp.Name 
                , sp.ReturnTypeName
                , sp.IsAggregate
                , sp.IsComposable
                , sp.IsBuiltIn
                , sp.IsNiladic
                , sp.IsTvf
                , sp.ParameterName
                , sp.ParameterType
                , sp.Mode
            FROM (  
            (SELECT
                  r.CatalogName as CatalogName
              ,   r.SchemaName as SchemaName
              ,   r.Name as Name
              ,   TREAT(r as Store.ScalarFunction).ReturnType.TypeName as ReturnTypeName
              ,   TREAT(r as Store.ScalarFunction).IsAggregate as IsAggregate
              ,   true as IsComposable
              ,   r.IsBuiltIn as IsBuiltIn
              ,   r.IsNiladic as IsNiladic
              ,   IsTvf(r) as IsTvf
              ,   p.Name as ParameterName
              ,   p.ParameterType.TypeName as ParameterType
              ,   p.Mode as Mode
              ,   p.Ordinal as Ordinal
            FROM
                SchemaInformation.Functions as r 
                 OUTER APPLY
                r.Parameters as p)
            UNION ALL
            (SELECT
                  r.CatalogName as CatalogName
              ,   r.SchemaName as SchemaName
              ,   r.Name as Name
              ,   CAST(NULL as string) as ReturnTypeName
              ,   false as IsAggregate
              ,   false as IsComposable
              ,   false as IsBuiltIn
              ,   false as IsNiladic
              ,   false as IsTvf
              ,   p.Name as ParameterName
              ,   p.ParameterType.TypeName as ParameterType
              ,   p.Mode as Mode
              ,   p.Ordinal as Ordinal
            FROM
                SchemaInformation.Procedures as r 
                 OUTER APPLY
                r.Parameters as p)) as sp
            ";
                }
            }
        }

        internal abstract class Memento
        {
            protected object[] _values;

            internal object[] Values
            {
                get { return _values; }
            }

            public abstract FunctionDetailsReader CreateReader();
        }

        internal sealed class MementoV1 : Memento
        {
            internal MementoV1(object[] values)
            {
                _values = values;
            }

            public override FunctionDetailsReader CreateReader()
            {
                return new FunctionDetailsReaderV1(this);
            }
        }

        internal sealed class MementoV3 : Memento
        {
            internal MementoV3(object[] values)
            {
                _values = values;
            }

            public override FunctionDetailsReader CreateReader()
            {
                return new FunctionDetailsReaderV3(this);
            }
        }
    }
}
