using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Runtime.Serialization;

using CategoryTheory;
using System.Data.Common;

namespace DataSetService
{
    /// <summary>
    /// Provider of data set
    /// </summary>
    public interface IDataSetProvider
    {
        /// <summary>
        /// Provided data set
        /// </summary>
        DataSet DataSet
        {
            get;
        }

        /// <summary>
        /// Factory
        /// </summary>
        IDataSetFactory Factory
        {
            get;
            set;
        }
    }

    /// <summary>
    /// Data set consumer
    /// </summary>
    public interface IDataSetConsumer
    {
        /// <summary>
        /// Adds data set
        /// </summary>
        /// <param name="dataSet">Data set to add</param>
        void Add(DataSet dataSet);

        /// <summary>
        /// Removes data set
        /// </summary>
        /// <param name="dataSet">Data set to remove</param>
        void Remove(DataSet dataSet);

        /// <summary>
        /// Factory
        /// </summary>
        IDataSetFactory Factory
        {
            get;
            set;
        }
    }

    /// <summary>
    /// Factory for creating data set metatata from connection
    /// </summary>
    public interface IDataSetFactory
    {
        /// <summary>
        /// Creates connection
        /// </summary>
        DbConnection Connection
        {
            get;
        }

        /// <summary>
        /// Command
        /// </summary>
        DbCommand Command
        {
            get;
        }

        /// <summary>
        /// Gets metadata data set from connection
        /// </summary>
        /// <param name="connection">The connection</param>
        /// <returns>The metadata data set</returns>
        DataSet GetData(DbConnection connection);

        /// <summary>
        /// Gets metadata data set from connection string
        /// </summary>
        /// <param name="connectionString">The connection</param>
        /// <returns>The metadata data set</returns>
        DataSet GetData(string connectionString);

        /// <summary>
        /// Data adapter
        /// </summary>
        IDbDataAdapter Adapter
        {
            get;
        }

        /// <summary>
        /// Creates type from metadata row
        /// </summary>
        /// <param name="row">The metadata row</param>
        /// <returns>The type</returns>
        Type GetTypeFromRow(DataRow row);

        /// <summary>
        /// Table name in metadata
        /// </summary>
        string TableName
        {
            get;
        }

        /// <summary>
        /// Column name in metadata
        /// </summary>
        string ColumnName
        {
            get;
        }

        /// <summary>
        /// Is nullable in metadata
        /// </summary>
        string IsNullable
        {
            get;
        }

        /// <summary>
        /// Gets string representation of type
        /// </summary>
        /// <param name="type">The type</param>
        /// <returns>The string representation</returns>
        string GetStringFromType(Type type);


        /// <summary>
        /// Generates statement from desktop 
        /// </summary>
        /// <param name="desktop">The desktop</param>
        /// <returns>The statement</returns>
        string GenerateStatement(IDataSetDesktop desktop);

        /// <summary>
        /// Gets object type
        /// </summary>
        /// <param name="column">Column</param>
        /// <returns>The type</returns>
        object GetObjectType(DataColumn column);

        /// <summary>
        /// Generates script
        /// </summary>
        /// <param name="column">Column</param>
        /// <returns>Script</returns>
        string GenerateScript(IColumn column);

        /// <summary>
        /// Generates create script
        /// </summary>
        /// <param name="table">Table</param>
        /// <returns>Script lines</returns>
        List<string> GenerateScript(ITable table);

        /// <summary>
        /// Generates creation script from metadata
        /// </summary>
        /// <param name="metaData">Meatadata data set</param>
        /// <returns>Creation script</returns>
        List<string> GenerateScript(DataSet metaData);

    }

    /// <summary>
    /// Chooser of data set factory
    /// </summary>
    public abstract class DataSetFactoryChooser
    {
        /// <summary>
        /// Global chooser
        /// </summary>
        static private DataSetFactoryChooser chooser;

        /// <summary>
        /// Global chooser
        /// </summary>
        static public DataSetFactoryChooser Chooser
        {
            get
            {
                return chooser;
            }
            set
            {
                chooser = value;
            }
        }

        /// <summary>
        /// Gets facroty by name
        /// </summary>
        /// <param name="name">The name</param>
        /// <returns>The factory</returns>
        public abstract IDataSetFactory this[string name]
        {
            get;
        }

        /// <summary>
        /// Names of factories
        /// </summary>
        public abstract string[] Names
        {
            get;
        }

        static public Dictionary<string, Dictionary<string, DataRow> > GetTables(string tableCol, string colCol, DataTable table)
        {
            Dictionary<string, Dictionary<string, DataRow>> d = new Dictionary<string, Dictionary<string, DataRow>>();
            foreach (DataRow row in table.Rows)
            {
                string tn = row[tableCol] + "";
                Dictionary<string, DataRow> c = null;
                if (d.ContainsKey(tn))
                {
                    c = d[tn];
                }
                else
                {
                    c = new Dictionary<string, DataRow>();
                    d[tn] = c;
                }
                string cn = row[colCol] + "";
                c[cn] = row;
            }
            return d;
        }

        static public DataSet GetStandardDataSet(string tableName, string colName, DataTable table, IDataSetFactory factory)
        {
            Dictionary<string, Dictionary<string, DataRow>> dicT = GetTables(tableName, colName, table);
            DataSet ds = new DataSet();
            foreach (string tn in dicT.Keys)
            {
                DataTable dt = new DataTable(tn);
                ds.Tables.Add(dt);
                Dictionary<string, DataRow> dicC = dicT[tn];
                foreach (string cn in dicC.Keys)
                {
                    DataRow row = dicC[cn];
                    Type type = factory.GetTypeFromRow(row);
                    if (type == null)
                    {
                        continue;
                    }
                    DataColumn col = new DataColumn(cn, type);
                    dt.Columns.Add(col);
                }
            }
            return ds;
        }
    }


    /// <summary>
    /// Arrow between data set provider and data set consumer
    /// </summary>
    [SerializableAttribute()]
    public class DataSetArrow : ISerializable, ICategoryArrow, IRemovableObject
    {

        #region Fields


        protected object obj;
        protected IDataSetConsumer source;
        protected IDataSetProvider target;

        #endregion

        #region Constructors

        public DataSetArrow()
        {
        }

        public DataSetArrow(SerializationInfo info, StreamingContext context)
        {
        }
        #endregion

        #region ISerializable Members

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
        }

        #endregion

        #region ICategoryArrow Members

        public ICategoryObject Source
        {
            get
            {
                return source as ICategoryObject;
            }
            set
            {
                if (!(value is IDataSetConsumer))
                {
                    throw new Exception("Illegal type");
                }
                source = value as IDataSetConsumer;
            }
        }

        public ICategoryObject Target
        {
            get
            {
                return target as ICategoryObject;
            }
            set
            {
                if (value == null)
                {
                    return;
                }
                if (!(value is IDataSetProvider))
                {
                    throw new Exception("Illegal Target");
                }
                target = value as IDataSetProvider;
                source.Factory = target.Factory;
                source.Add(target.DataSet);
            }
        }

        public bool IsMonomorphism
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public bool IsEpimorphism
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public bool IsIsomorphism
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public ICategoryArrow Compose(ICategory category, ICategoryArrow next)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        #endregion

        #region IAssociatedObject Members

        public object Object
        {
            get
            {
                return obj;
            }
            set
            {
                obj = value;
            }
        }

        #endregion

        #region IRemovableObject Members

        public void RemoveObject()
        {
            source.Remove(target.DataSet);
        }

        #endregion
    }

    public abstract class AbstractDataProvider : ISerializable, ICategoryObject, IDataSetProvider
    {
        #region Fields

        protected object obj;

        protected string connectionString;

        protected string statement;

        protected IDataSetDesktop desktop;

        protected IDataSetFactory factory;

        #endregion

        #region Constructors

        public AbstractDataProvider()
        {
        }

        public AbstractDataProvider(SerializationInfo info, StreamingContext context)
        {
            connectionString = info.GetValue("ConnectionString", typeof(string)) + "";
            statement = info.GetValue("Statement", typeof(string)) + "";
            try
            {
                desktop = info.GetValue("Desktop", typeof(object)) as IDataSetDesktop;
            }
            catch (Exception)
            {
            }
        }


        #endregion

        #region ISerializable Members

        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("ConnectionString", connectionString);
            info.AddValue("Statement", statement);
            if (desktop != null)
            {
                if (desktop is ISerializable)
                {
                    info.AddValue("Desktop", desktop);
                }
            }
        }

        #endregion

        #region ICategoryObject Members

        public ICategory Category
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public ICategoryArrow Id
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        #endregion

        #region IAssociatedObject Members

        public object Object
        {
            get
            {
                return obj;
            }
            set
            {
                obj = value;
            }
        }

        #endregion

        #region IDataSetProvider Members

        public abstract DataSet DataSet
        {
            get;
        }

        public virtual IDataSetFactory Factory
        {
            get
            {
                return factory;
            }
            set
            {
                factory = value;
            }
        }


        #endregion

        #region Specific Members

 
        public IDataSetDesktop Desktop
        {
            get
            {
                return desktop;
            }
            set
            {
                desktop = value;
            }
        }

        public string ConnecionString
        {
            get
            {
                return connectionString;
            }
            set
            {
                connectionString = value;
            }
        }


        public string Statement
        {
            get
            {
                return statement;
            }
            set
            {
                statement = value;
            }
        }

        #endregion

    }


    [SerializableAttribute()]
    public class StatementWrapper : AbstractDataProvider
    {

        protected string factoryName;

        protected DbCommand command;

        protected DbConnection connection;

        private IDbDataAdapter adapter;

        private DataSet dataSet;

        //private IDataSetFactory factory;

        #region Constructors

        public StatementWrapper()
        {
        }

        public StatementWrapper(SerializationInfo info, StreamingContext context) : base(info, context)
        {
            factoryName = info.GetValue("FactoryName", typeof(string)) + "";
            init();
        }


        #endregion

        #region ISerializable Members

        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
            info.AddValue("FactoryName", factoryName);
        }

        #endregion

        public DataSet Schema
        {
            get
            {
                IDataSetFactory factory = DataSetFactoryChooser.Chooser[factoryName];
                DbConnection connection = factory.Connection;
                connection.ConnectionString = connectionString;
                return factory.GetData(connection);
            }
        }



        public override DataSet DataSet
        {
            get 
            {
                try
                {
                    if (dataSet == null)
                    {
                        init();
                        connection.Open();
                        dataSet = new DataSet();
                        adapter.Fill(dataSet);
                        connection.Close();
                    }
                    return dataSet;
                }
                catch (Exception ex)
                {
                    string sex = ex + "";
                }
                return null;
            }
        }

 

        public void Update()
        {
            dataSet = null;
        }

        public string FactoryName
        {
            get
            {
                return factoryName;
            }
            set
            {
                factoryName = value;
            }
        }

        private void init()
        {
            factory = DataSetFactoryChooser.Chooser[factoryName];
            connection = factory.Connection;
            connection.ConnectionString = connectionString;
            command = factory.Command;
            command.Connection = connection;
            command.CommandType = CommandType.Text;
            command.CommandText = statement;
            adapter = factory.Adapter;
            adapter.SelectCommand = command;
        }
    }
}
