#region Disclaimer / License
// Copyright (C) 2010, Jackie Ng
// http://trac.osgeo.org/mapguide/wiki/maestro, jumpinjackie@gmail.com
// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library 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
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
// 
#endregion
using System;
using System.Collections.Generic;
using System.Text;
using OSGeo.MapGuide.MaestroAPI.Resource;
using OSGeo.MapGuide.ObjectModels.Common;
using OSGeo.MapGuide.MaestroAPI;
using System.ComponentModel;
using System.IO;
using OSGeo.MapGuide.MaestroAPI.Schema;
using System.Collections.Specialized;

namespace OSGeo.MapGuide.ObjectModels.FeatureSource
{
    /// <summary>
    /// Represents an FDO feature source
    /// </summary>
    public interface IFeatureSource : IResource
    {
        /// <summary>
        /// Removes all specified connection properties
        /// </summary>
        void ClearConnectionProperties();

        /// <summary>
        /// Gets an array of names of the currently specified connection properties
        /// </summary>
        string[] ConnectionPropertyNames { get; }

        /// <summary>
        /// Gets or sets the FDO provider.
        /// </summary>
        /// <value>The FDO provider.</value>
        string Provider { get; set; }

        /// <summary>
        /// Gets the connection property.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <returns></returns>
        string GetConnectionProperty(string name);

        /// <summary>
        /// Sets the connection property.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        void SetConnectionProperty(string name, string value);

        /// <summary>
        /// Gets the connection string.
        /// </summary>
        /// <value>The connection string.</value>
        string ConnectionString { get; }

        /// <summary>
        /// Gets the name of the embedded data resource. Can only be called if <see cref="UsesEmbeddedDataFiles"/> returns true.
        /// </summary>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException">If <see cref="UsesEmbeddedDataFiles"/> is false</exception>
        string GetEmbeddedDataName();

        /// <summary>
        /// Gets the name of the alias. Can only be called if <see cref="UsesAliasedDataFiles"/> returns true
        /// </summary>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException">If <see cref="UsesAliasedDataFiles"/> is false </exception>
        string GetAliasName();

        /// <summary>
        /// Gets the name of the aliased file. Can only be called if <see cref="UsesAliasedDataFiles"/> returns true. An
        /// empty string is returned if it is a directory (ie. no file name was found)
        /// </summary>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException">If <see cref="UsesAliasedDataFiles"/> is false</exception>
        string GetAliasedFileName();

        /// <summary>
        /// Gets a value indicating whether [uses embedded data files].
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [uses embedded data files]; otherwise, <c>false</c>.
        /// </value>
        bool UsesEmbeddedDataFiles{ get; }

        /// <summary>
        /// Gets a value indicating whether [uses aliased data files].
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [uses aliased data files]; otherwise, <c>false</c>.
        /// </value>
        bool UsesAliasedDataFiles { get; }

        /// <summary>
        /// Gets the supplemental spatial context info (coordinate system overrides).
        /// </summary>
        /// <value>The supplemental spatial context info.</value>
        IEnumerable<ISpatialContextInfo> SupplementalSpatialContextInfo { get; }

        /// <summary>
        /// Adds the spatial context override.
        /// </summary>
        /// <param name="sc">The sc.</param>
        void AddSpatialContextOverride(ISpatialContextInfo sc);

        /// <summary>
        /// Removes the spatial context override.
        /// </summary>
        /// <param name="sc">The sc.</param>
        void RemoveSpatialContextOverride(ISpatialContextInfo sc);

        /// <summary>
        /// Gets the extensions for this feature source.
        /// </summary>
        /// <value>The extensions.</value>
        IEnumerable<IFeatureSourceExtension> Extension { get; }

        /// <summary>
        /// Adds the extension.
        /// </summary>
        /// <param name="ext">The ext.</param>
        void AddExtension(IFeatureSourceExtension ext);

        /// <summary>
        /// Removes the extension.
        /// </summary>
        /// <param name="ext">The ext.</param>
        void RemoveExtension(IFeatureSourceExtension ext);

        /// <summary>
        /// Gets or sets the name of the configuration document.
        /// </summary>
        /// <value>The name of the configuration document.</value>
        string ConfigurationDocument { get; set; }
    }

    /// <summary>
    /// Represents a spatial context override
    /// </summary>
    public interface ISpatialContextInfo
    {
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>The name.</value>
        string Name { get; set; }

        /// <summary>
        /// Gets or sets the coordinate system.
        /// </summary>
        /// <value>The coordinate system.</value>
        string CoordinateSystem { get; set; }
    }

    /// <summary>
    /// Represents an extended feature class
    /// </summary>
    public interface IFeatureSourceExtension : INotifyPropertyChanged
    {
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>The name.</value>
        string Name { get; set; }

        /// <summary>
        /// Gets or sets the feature class to extend
        /// </summary>
        /// <value>The feature class.</value>
        string FeatureClass { get; set; }

        /// <summary>
        /// Gets the calculated properties.
        /// </summary>
        /// <value>The calculated properties.</value>
        IEnumerable<ICalculatedProperty> CalculatedProperty { get; }

        /// <summary>
        /// Adds the calculated property.
        /// </summary>
        /// <param name="prop">The prop.</param>
        void AddCalculatedProperty(ICalculatedProperty prop);

        /// <summary>
        /// Removes the calculated property.
        /// </summary>
        /// <param name="prop">The prop.</param>
        void RemoveCalculatedProperty(ICalculatedProperty prop);

        /// <summary>
        /// Gets the attribute joins
        /// </summary>
        /// <value>The attribute joins.</value>
        IEnumerable<IAttributeRelation> AttributeRelate { get; }

        /// <summary>
        /// Adds the relation.
        /// </summary>
        /// <param name="relate">The relate.</param>
        void AddRelation(IAttributeRelation relate);

        /// <summary>
        /// Removes the relation.
        /// </summary>
        /// <param name="relate">The relate.</param>
        void RemoveRelation(IAttributeRelation relate);
    }

    /// <summary>
    /// Represents a FDO calculated property
    /// </summary>
    public interface ICalculatedProperty : INotifyPropertyChanged
    {
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>The name.</value>
        string Name { get; set; }

        /// <summary>
        /// Gets or sets the FDO expression.
        /// </summary>
        /// <value>The FDO expression.</value>
        string Expression { get; set; }
    }

    /// <summary>
    /// Defines the type of joins
    /// </summary>
    [System.SerializableAttribute()]
    public enum RelateTypeEnum
    {

        /// <remarks/>
        LeftOuter,

        /// <remarks/>
        RightOuter,

        /// <remarks/>
        Inner,

        /// <remarks/>
        Association,
    }

    /// <summary>
    /// Represents an attribute join
    /// </summary>
    public interface IAttributeRelation : INotifyPropertyChanged
    {
        /// <summary>
        /// Gets or sets whether to force 1:1 cardinality
        /// </summary>
        bool ForceOneToOne { get; set; }

        /// <summary>
        /// Gets the type of join
        /// </summary>
        RelateTypeEnum RelateType { get; set; }

        /// <summary>
        /// Gets or sets the feature source id containing the feature class to extend
        /// </summary>
        string ResourceId { get; set; }

        /// <summary>
        /// Gets or sets the name of the feature class to extend
        /// </summary>
        string AttributeClass { get; set; }

        /// <summary>
        /// Gets or sets the name of the join
        /// </summary>
        string Name { get; set; }

        /// <summary>
        /// Gets or sets the prefix that prevents a naming collision on both sides of the join
        /// </summary>
        string AttributeNameDelimiter { get; set; }

        /// <summary>
        /// Gets the property pairs involved in this join
        /// </summary>
        /// <value>The property pairs.</value>
        IEnumerable<IRelateProperty> RelateProperty { get; }

        /// <summary>
        /// Creates the property join.
        /// </summary>
        /// <param name="primaryProperty">The primary property.</param>
        /// <param name="secondaryProperty">The secondary property.</param>
        /// <returns></returns>
        IRelateProperty CreatePropertyJoin(string primaryProperty, string secondaryProperty);

        /// <summary>
        /// Adds the relate property.
        /// </summary>
        /// <param name="prop">The prop.</param>
        void AddRelateProperty(IRelateProperty prop);

        /// <summary>
        /// Removes the relate property.
        /// </summary>
        /// <param name="prop">The prop.</param>
        void RemoveRelateProperty(IRelateProperty prop);

        /// <summary>
        /// Removes all relate properties.
        /// </summary>
        void RemoveAllRelateProperties();
    }

    /// <summary>
    /// Represents a property pair in an attribute join
    /// </summary>
    public interface IRelateProperty
    {
        /// <summary>
        /// Gets or sets the feature class property.
        /// </summary>
        /// <value>The feature class property.</value>
        string FeatureClassProperty { get; set; }

        /// <summary>
        /// Gets or sets the attribute class property.
        /// </summary>
        /// <value>The attribute class property.</value>
        string AttributeClassProperty { get; set; }
    }

    /// <summary>
    /// Extension method class
    /// </summary>
    public static class FeatureSourceExtensions
    {
        /// <summary>
        /// Gets a collection of connection properties
        /// </summary>
        /// <param name="fs"></param>
        /// <returns></returns>
        public static NameValueCollection GetConnectionProperties(this IFeatureSource fs)
        {
            Check.NotNull(fs, "fs");
            var values = new NameValueCollection();
            foreach (string name in fs.ConnectionPropertyNames)
            {
                values[name] = fs.GetConnectionProperty(name);
            }
            return values;
        }

        /// <summary>
        /// Gets the names of all the schemas in this feature source
        /// </summary>
        /// <param name="fs"></param>
        /// <returns></returns>
        public static string[] GetSchemaNames(this IFeatureSource fs)
        {
            Check.NotNull(fs, "fs");
            return fs.CurrentConnection.FeatureService.GetSchemas(fs.ResourceID);
        }

        /// <summary>
        /// Gets the names of all the feature classes in the specified schema of this feature source
        /// </summary>
        /// <param name="fs"></param>
        /// <param name="schemaName"></param>
        /// <returns></returns>
        public static string[] GetClassNames(this IFeatureSource fs, string schemaName)
        {
            Check.NotNull(fs, "fs");
            Check.NotEmpty(schemaName, "schemaName");

            return fs.CurrentConnection.FeatureService.GetClassNames(fs.ResourceID, schemaName);
        }

        /// <summary>
        /// Sets the connection properties of the feature source
        /// </summary>
        /// <param name="fs"></param>
        /// <param name="values"></param>
        public static void ApplyConnectionProperties(this IFeatureSource fs, NameValueCollection values)
        {
            Check.NotNull(fs, "fs");
            Check.NotNull(values, "values");

            fs.ClearConnectionProperties();

            foreach (string name in values.Keys)
            {
                string value = values[name];

                fs.SetConnectionProperty(name, value);
            }
        }

        /// <summary>
        /// Gets the configuration document content
        /// </summary>
        /// <param name="fs"></param>
        /// <returns></returns>
        public static string GetConfigurationContent(this IFeatureSource fs)
        {
            Check.NotNull(fs, "fs");
            if (string.IsNullOrEmpty(fs.ConfigurationDocument))
                return string.Empty;

            var content = fs.GetResourceData(fs.ConfigurationDocument);
            if (content != null)
            {
                using (var sr = new StreamReader(content))
                {
                    return sr.ReadToEnd();
                }
            }
            return string.Empty;
        }

        /// <summary>
        /// Sets the configuration document content
        /// </summary>
        /// <param name="fs"></param>
        /// <param name="xmlContent"></param>
        public static void SetConfigurationContent(this IFeatureSource fs, string xmlContent)
        {
            Check.NotNull(fs, "fs");
            if (string.IsNullOrEmpty(fs.ConfigurationDocument))
                fs.ConfigurationDocument = "config.xml";

            if (string.IsNullOrEmpty(xmlContent))
            {
                bool hasResourceData = false;
                var resDataList = fs.EnumerateResourceData();
                foreach (var resData in resDataList)
                {
                    if (resData.Name == fs.ConfigurationDocument)
                    {
                        hasResourceData = true;
                        break;
                    }
                }

                if (hasResourceData)
                    fs.DeleteResourceData(fs.ConfigurationDocument);
            }
            else
            {
                using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(xmlContent)))
                {
                    fs.SetResourceData(fs.ConfigurationDocument, ResourceDataType.Stream, ms);
                }
            }
        }

        /// <summary>
        /// Convenience method to return the description of this feature source
        /// </summary>
        /// <returns></returns>
        public static FeatureSourceDescription Describe(this IFeatureSource fs)
        {
            Check.NotNull(fs, "fs");
            return fs.CurrentConnection.FeatureService.DescribeFeatureSource(fs.ResourceID);
        }

        /// <summary>
        /// Convenience method to retrieve the spatial context information of this feature source
        /// </summary>
        /// <returns></returns>
        public static OSGeo.MapGuide.ObjectModels.Common.FdoSpatialContextList GetSpatialInfo(this IFeatureSource fs, bool activeOnly)
        {
            Check.NotNull(fs, "fs");
            return fs.CurrentConnection.FeatureService.GetSpatialContextInfo(fs.ResourceID, activeOnly);
        }

        /// <summary>
        /// Convenience methods to get the identity properties of a given feature class (name)
        /// </summary>
        /// <param name="fs">The fs.</param>
        /// <param name="className">Name of the class.</param>
        /// <returns></returns>
        public static string[] GetIdentityProperties(this IFeatureSource fs, string className)
        {
            Check.NotNull(fs, "fs");
            try
            {
                return fs.CurrentConnection.FeatureService.GetIdentityProperties(fs.ResourceID, className);
            }
            catch (Exception ex)
            {
                //MgClassNotFoundException is thrown for classes w/ no identity properties
                //when the correct server response should be an empty array
                if (ex.Message.IndexOf("MgClassNotFoundException") >= 0)
                {
                    return new string[0];
                }
                else
                {
                    throw;
                }
            }
        }

        /// <summary>
        /// Convenience method to get the spatial extents of a given feature class
        /// </summary>
        /// <param name="fs">The fs.</param>
        /// <param name="className">Name of the class.</param>
        /// <param name="geomProperty">The geom property.</param>
        /// <returns></returns>
        public static IEnvelope GetSpatialExtent(this IFeatureSource fs, string className, string geomProperty)
        {
            Check.NotNull(fs, "fs");
            return fs.CurrentConnection.FeatureService.GetSpatialExtent(fs.ResourceID, className, geomProperty);
        }

        /// <summary>
        /// Convenience method to get the feature class definition
        /// </summary>
        /// <param name="fs">The fs.</param>
        /// <param name="qualifiedName">Name of the qualified.</param>
        /// <returns></returns>
        public static ClassDefinition GetClass(this IFeatureSource fs, string qualifiedName)
        {
            Check.NotNull(fs, "fs");
            return fs.CurrentConnection.FeatureService.GetClassDefinition(fs.ResourceID, qualifiedName);
        }

        /// <summary>
        /// Adds a spatial context override
        /// </summary>
        /// <param name="fs"></param>
        /// <param name="name"></param>
        /// <param name="coordSys"></param>
        public static void AddSpatialContextOverride(this IFeatureSource fs, string name, string coordSys)
        {
            Check.NotNull(fs, "fs");
            fs.AddSpatialContextOverride(new OSGeo.MapGuide.ObjectModels.FeatureSource_1_0_0.SpatialContextType() { Name = name, CoordinateSystem = coordSys });
        }


        /// <summary>
        /// Tests the connection settings in this feature source
        /// </summary>
        /// <param name="fs"></param>
        /// <returns></returns>
        public static string TestConnection(this IFeatureSource fs)
        {
            Check.NotNull(fs, "fs");
            return fs.CurrentConnection.FeatureService.TestConnection(fs.ResourceID);
        }

        /// <summary>
        /// Adds the specified property pair to this join
        /// </summary>
        /// <param name="rel"></param>
        /// <param name="primary"></param>
        /// <param name="secondary"></param>
        public static void AddRelateProperty(this IAttributeRelation rel, string primary, string secondary)
        {
            Check.NotNull(rel, "rel");
            rel.AddRelateProperty(new OSGeo.MapGuide.ObjectModels.FeatureSource_1_0_0.RelatePropertyType() { FeatureClassProperty = primary, AttributeClassProperty = secondary });
        }
    }
}