#region Copyright and GPL
/*************************************************************************
 * dataMagus - The open source database learning software
 * Copyright (C) 2007 by all authors
 * Authors: Monika Schwitter (monsy@gmx.ch)
 *          Jürg Zgraggen (j.zgraggen@ejaz.net)
 *          HSLU (Hochschule Luzern - Technik+Architektur)
 * 
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *************************************************************************/
#endregion
#region Using
using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;
using dataMagus.Common.Physical;
using dataMagus.Common.PlugIn;
using dataMagus.Common.Physical.Relational;
#endregion

namespace dataMagus.PlugIns.Generators.PostgreSql
{
	/// <summary>
	/// This class is used to hold generator specific data.
	/// </summary>
	public class GeneratorData : IExtendedData
    {
        #region Members
        /// <summary>
		/// Singelton instance object.
		/// </summary>
        private static GeneratorData _instance = null;
        
        /// <summary>
        /// Lock object for thread safe access.
        /// </summary>
        private static readonly object _padlock = new object();
        
        /// <summary>
        /// Indicator if the primary key constraints will be transformed by column
        /// constraints. If not, the table constraints will be used.
        /// </summary>
        private bool _forceColumnConstraintForPrimaryKey = true;
        
        /// <summary>
        /// Indicator if the foreign key constraints will be written directly into
        /// the CREATE TABLE statement. It not, ALTER TABLE statement will be generated
        /// at the bottom of the script.
        /// </summary>
        private bool _forceForeignKeyWithCreateTable = true;
        
        /// <summary>
        /// Indicator if the foreign key constraints will be transformed by column
        /// constraints. If not, the table constraints will be used.
        /// </summary>
        /// <remarks>This flag will only be regarded when the ForceForeignKeyWithCreateTable is set to true.</remarks>
        private bool _forceColumnConstraintForForeignKey = true;
        
        /// <summary>
        /// Indicator if the unique constraints will be written directly into the
        /// CREATE TABLE statement. It not, CREATE UNIQUE INDEX statement will be generated
        /// </summary>
        private bool _forceUniqueIndexWithCreateTable = true;
        
        /// <summary>
        /// Indicator if the unique constraints will be transformed by column
        /// constraints. If not, the table constraints will be used.
        /// </summary>
        /// <remarks>This flag will only be regarded when the ForceUniqueIndexWithCreateTable is set to true.</remarks>
        private bool _forceColumnConstraintForUnique = true;
        
        /// <summary>
        /// Holds the mapping of the datatypes.
        /// </summary>
        private Dictionary<string, string> _dataTypeMapping = new Dictionary<string, string>();
        
        /// <summary>
        /// Holds the generator specific data for relationships.
        /// </summary>
        private Dictionary<string, RelationshipGeneratorData> _relationshipData = new Dictionary<string, RelationshipGeneratorData>();
        #endregion

        #region Constructors / Destructors
        /// <summary>
        /// Default constructor.
        /// </summary>
        private GeneratorData()
		{
            // define the mapping for the base datatypes
            this._dataTypeMapping.Add(DataType.String.Name, "varchar(100)");
            this._dataTypeMapping.Add(DataType.Number.Name, "integer");
            this._dataTypeMapping.Add(DataType.Float.Name, "decimal");
            this._dataTypeMapping.Add(DataType.DateTime.Name, "timestamp ");
            this._dataTypeMapping.Add(DataType.Boolean.Name, "boolean");
            this._dataTypeMapping.Add(DataType.Binary.Name, "bytea");
            this._dataTypeMapping.Add(DataType.Unknown.Name, "unknown");
        }
        #endregion

        #region Properties
        /// <summary>
		/// Singelton instance object.
		/// </summary>
        public static GeneratorData Instance 
		{
			get
			{
                if (null == GeneratorData._instance)
				{
                    lock (GeneratorData._padlock)
					{
                        if (null == GeneratorData._instance)
						{
                            GeneratorData._instance = new GeneratorData();
						}
					}
				}
                return GeneratorData._instance;
			}
        }

        /// <summary>
        /// Indicator if the primary key constraints will be transformed by column
        /// constraints. If not, the table constraints will be used.
        /// </summary>
        public bool ForceColumnConstraintForPrimaryKey
        {
            get { return this._forceColumnConstraintForPrimaryKey; }
            set { this._forceColumnConstraintForPrimaryKey = value; }
        }

        /// <summary>
        /// Indicator if the foreign key constraints will be written directly into
        /// the CREATE TABLE statement. It not, ALTER TABLE statement will be generated
        /// at the bottom of the script.
        /// </summary>
        public bool ForceForeignKeyWithCreateTable
        {
            get { return this._forceForeignKeyWithCreateTable; }
            set { this._forceForeignKeyWithCreateTable = value; }
        }

        /// <summary>
        /// Indicator if the foreign key constraints will be transformed by column
        /// constraints. If not, the table constraints will be used.
        /// </summary>
        /// <remarks>This flag will only be regarded when the ForceForeignKeyWithCreateTable is set to true.</remarks>
        public bool ForceColumnConstraintForForeignKey
        {
            get { return this._forceColumnConstraintForForeignKey; }
            set { this._forceColumnConstraintForForeignKey = value; }
        }

        /// <summary>
        /// Indicator if the unique constraints will be written directly into the
        /// CREATE TABLE statement. It not, CREATE UNIQUE INDEX statement will be generated
        /// </summary>
        public bool ForceUniqueIndexWithCreateTable
        {
            get { return this._forceUniqueIndexWithCreateTable; }
            set { this._forceUniqueIndexWithCreateTable = value; }
        }

        /// <summary>
        /// Indicator if the unique constraints will be transformed by column
        /// constraints. If not, the table constraints will be used.
        /// </summary>
        /// <remarks>This flag will only be regarded when the ForceUniqueIndexWithCreateTable is set to true.</remarks>
        public bool ForceColumnConstraintForUnique
        {
            get { return this._forceColumnConstraintForUnique; }
            set { this._forceColumnConstraintForUnique = value; }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Clean all extended generator data.
        /// </summary>
        /// <param name="schema">Schema to clean the data for.</param>
        public void CleanExtendedData(PhysicalRelationalSchema schema)
        {
            List<string> relationshipKeysToDelete = new List<string>();
            IEnumerator relDataEnumerator = this.GetRelationshipData();
            while (relDataEnumerator.MoveNext())
            { 
                KeyValuePair<string, RelationshipGeneratorData> relData = (KeyValuePair<string, RelationshipGeneratorData>)relDataEnumerator.Current;
                if (null == schema.FindRelationship(relData.Key))
                {
                    relationshipKeysToDelete.Add(relData.Key);
                }
            }
            foreach (string key in relationshipKeysToDelete)
            {
                this.RemoveRelationshipData(key);
            }
        }

        /// <summary>
        /// Get all stored data for relationships.
        /// </summary>
        /// <returns>Enumerator to parse.</returns>
        public IEnumerator GetRelationshipData()
        {
            return this._relationshipData.GetEnumerator();
        }

        /// <summary>
        /// Get all stored mapping of the datatype.
        /// </summary>
        /// <returns>Enumerator to parse.</returns>
        public IEnumerator GetDataTypeMappings()
        {
            return this._dataTypeMapping.GetEnumerator();
        }

        /// <summary>
        /// Update relationship data to a physical id.
        /// </summary>
        /// <param name="physicalId">Physical id the data belongs to.</param>
        /// <param name="value">Value to update.</param>
        public void UpdateRelationshipData(string physicalId, RelationshipGeneratorData value)
        {
            if (null == value)
            {
                return;
            }
            if (!this._relationshipData.ContainsKey(physicalId))
            {
                this._relationshipData.Add(physicalId, value);
            }
            this._relationshipData[physicalId] = value;
        }

        /// <summary>
        /// Get relationship data by a physical id.
        /// </summary>
        /// <param name="physicalId">Physical id to get the data from.</param>
        /// <returns>Object containing the data or null if nothing has been found.</returns>
        public RelationshipGeneratorData GetRelationshipData(string physicalId)
        {
            if (this._relationshipData.ContainsKey(physicalId))
            {
                return this._relationshipData[physicalId];
            }
            return null;
        }

        /// <summary>
        /// Remove relationship data to a physical id.
        /// </summary>
        /// <param name="physicalId">Physical id the data belongs to.</param>
        public void RemoveRelationshipData(string physicalId)
        {
            if (this._relationshipData.ContainsKey(physicalId))
            {
                this._relationshipData.Remove(physicalId);
            }
        }

        /// <summary>
        /// Update a datatype on the schema.
        /// </summary>
        /// <param name="newDatatype">New datatype.</param>
        /// <param name="oldDatatype">Old datatype.</param>
        /// <param name="action">Action happend.</param>
        public void UpdateDataType(DataType newDatatype, DataType oldDatatype, PhysicalItemContainer.DataTypeAction action)
        {
            if (PhysicalItemContainer.DataTypeAction.Added == action)
            {
                if (null != newDatatype && null != newDatatype.Name)
                {
                    this.UpdateDataTypeMapping(newDatatype.Name, newDatatype.BaseType.ToString());
                }
            }
            else if (PhysicalItemContainer.DataTypeAction.Removed == action)
            {
                if (null != oldDatatype && null != oldDatatype.Name)
                {
                    this.RemoveDataTypeMapping(oldDatatype.Name);
                }
            }
            else if (PhysicalItemContainer.DataTypeAction.Modified == action)
            {
                if (null != newDatatype && null != newDatatype.Name &&
                    null != oldDatatype && null != oldDatatype.Name)
                {
                    string savedDatatype = this.GetDataTypeMapping(oldDatatype.Name);
                    this.RemoveDataTypeMapping(oldDatatype.Name);
                    this.UpdateDataTypeMapping(newDatatype.Name, savedDatatype);
                }
            }
        }

		/// <summary>
		/// Update the internal stored datatype if the an existing one has changed or a new 
		/// one has been added.
		/// </summary>
		/// <param name="dataType">Datatype to update.</param>
		/// <param name="value">Value to update.</param>
        public void UpdateDataTypeMapping(string dataType, string value)
        {
            if (String.IsNullOrEmpty(dataType) || null == value)
            {
                return;
            }
            if (!this._dataTypeMapping.ContainsKey(dataType))
            {
                this._dataTypeMapping.Add(dataType, String.Empty);
            }
            this._dataTypeMapping[dataType] = value;
        }

        /// <summary>
        /// Get the stored mapping of a datatype.
        /// </summary>
        /// <param name="dataType">Datatype to get the mapping for.</param>
        /// <returns>The found mapping or null if nothing has been found.</returns>
        public string GetDataTypeMapping(string dataType)
        {
            if (!this._dataTypeMapping.ContainsKey(dataType))
            {
                return null;
            }
            return this._dataTypeMapping[dataType];
        }

        /// <summary>
        /// Remove the stored mapping of a datatype.
        /// </summary>
        /// <param name="dataType">Datatype to remove the mapping for.</param>
        public void RemoveDataTypeMapping(string dataType)
        {
            if (this._dataTypeMapping.ContainsKey(dataType))
            {
                this._dataTypeMapping.Remove(dataType);
            }
        }
		
        /// <summary>
        /// Gets the xml schema object.
        /// </summary>
        /// <returns>Always null.</returns>
		public XmlSchema GetSchema()
		{
            return null;
		}
		
		/// <summary>
        /// Reads back an instance of this class from a reader.
        /// </summary>
        /// <param name="reader">Reader object to read from.</param>
		public void ReadXml(XmlReader reader)
		{
            if (reader.IsStartElement("GeneratorData"))
            {
                this._forceColumnConstraintForPrimaryKey = Convert.ToBoolean(reader.GetAttribute("forceColumnConstraintForPrimaryKey"));
                this._forceForeignKeyWithCreateTable = Convert.ToBoolean(reader.GetAttribute("forceForeignKeyWithCreateTable"));
                this._forceColumnConstraintForForeignKey = Convert.ToBoolean(reader.GetAttribute("forceColumnConstraintForForeignKey"));
                this._forceUniqueIndexWithCreateTable = Convert.ToBoolean(reader.GetAttribute("forceUniqueIndexWithCreateTable"));
                this._forceColumnConstraintForUnique = Convert.ToBoolean(reader.GetAttribute("forceColumnConstraintForUnique"));

                reader.ReadStartElement();
                if (reader.IsStartElement("DataTypeMappings"))
                {
                    if (!reader.IsEmptyElement)
                    {
                        while (!reader.EOF)
                        {
                            reader.Read();
                            if (reader.NodeType == System.Xml.XmlNodeType.EndElement && reader.Name == "DataTypeMappings")
                            {
                                break;
                            }
                            if (reader.IsStartElement("Mapping"))
                            {
                                this.UpdateDataTypeMapping(reader.GetAttribute("key"), reader.GetAttribute("value"));
                            }
                        }
                    }
                }
                if (!reader.IsEmptyElement)
                {
                    reader.ReadEndElement();
                }
                else
                {
                    reader.ReadStartElement();
                }
                
                if (reader.IsStartElement("Relationships"))
                {
                    if (!reader.IsEmptyElement)
                    {
                        while (!reader.EOF)
                        {
                            reader.Read();
                            if (reader.NodeType == System.Xml.XmlNodeType.EndElement && reader.Name == "Relationships")
                            {
                                break;
                            }
                            if (reader.IsStartElement("Relationship"))
                            {
                                RelationshipGeneratorData relData = new RelationshipGeneratorData();
                                relData.OnUpdate = (RelationshipGeneratorData.ReferentialIntegrityAction)Enum.Parse(typeof(RelationshipGeneratorData.ReferentialIntegrityAction), reader.GetAttribute("onUpdate"));
                                relData.OnDelete = (RelationshipGeneratorData.ReferentialIntegrityAction)Enum.Parse(typeof(RelationshipGeneratorData.ReferentialIntegrityAction), reader.GetAttribute("onDelete")); 
                                string physicalId = reader.GetAttribute("physicalId");

                                this.UpdateRelationshipData(physicalId, relData);
                            }
                        }
                    }
                }
                if (!reader.IsEmptyElement)
                {
                    reader.ReadEndElement();
                }
                else
                {
                    reader.ReadStartElement();
                }
            }
		}
		
		/// <summary>
        /// Writes a the instace of this class to a writer.
        /// </summary>
        /// <param name="writer">Writer object to write into.</param>
		public void WriteXml(XmlWriter writer)
		{
            writer.WriteStartElement("GeneratorData");
            writer.WriteAttributeString("forceColumnConstraintForPrimaryKey", this.ForceColumnConstraintForPrimaryKey.ToString());
            writer.WriteAttributeString("forceForeignKeyWithCreateTable", this.ForceForeignKeyWithCreateTable.ToString());
            writer.WriteAttributeString("forceColumnConstraintForForeignKey", this.ForceColumnConstraintForForeignKey.ToString());
            writer.WriteAttributeString("forceUniqueIndexWithCreateTable", this.ForceUniqueIndexWithCreateTable.ToString());
            writer.WriteAttributeString("forceColumnConstraintForUnique", this.ForceColumnConstraintForUnique.ToString());

            writer.WriteStartElement("DataTypeMappings");
            IEnumerator dataTypeMappingsEnumerator = this._dataTypeMapping.GetEnumerator();
            while (dataTypeMappingsEnumerator.MoveNext())
            { 
                KeyValuePair<string, string> dataTypeMapping = (KeyValuePair<string, string>)dataTypeMappingsEnumerator.Current;
                writer.WriteStartElement("Mapping");
                writer.WriteAttributeString("key", dataTypeMapping.Key);
                writer.WriteAttributeString("value", dataTypeMapping.Value);
                writer.WriteEndElement();
            }
            writer.WriteEndElement();
            
            writer.WriteStartElement("Relationships");
            IEnumerator relationshipDataEnumerator = this._relationshipData.GetEnumerator();
            while (relationshipDataEnumerator.MoveNext())
            {
                KeyValuePair<string, RelationshipGeneratorData> relationshipData = (KeyValuePair<string, RelationshipGeneratorData>)relationshipDataEnumerator.Current;
                writer.WriteStartElement("Relationship");
                writer.WriteAttributeString("physicalId", relationshipData.Key);
                writer.WriteAttributeString("onUpdate", relationshipData.Value.OnUpdate.ToString());
                writer.WriteAttributeString("onDelete", relationshipData.Value.OnDelete.ToString());
                writer.WriteEndElement();
            }
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
        #endregion
    }
}
