using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;

using OSGeo.FDO;
using OSGeo.FDO.Schema;
using OSGeo.FDO.Expression;
using OSGeo.FDO.Commands;
using OSGeo.FDO.Commands.Feature;
using OSGeo.FDO.Commands.DataStore;
using OSGeo.FDO.Connections;
using OSGeo.FDO.Xml;
using OSGeo.FDO.Commands.Schema;
using OSGeo.FDO.Commands.SpatialContext;

using unit_test.Framework;

namespace unit_test.ProviderTests
{
    class SchemaTests : BaseTestWithConnection
    {
        public SchemaTests(TestProvider connectProvider)
            : base(connectProvider)
        {
            Console.WriteLine("start SchemaTests");
        }

        public override void SetUp()
        {
            base.SetUp();
        }

        public override void TearDown()
        {
            base.TearDown();
        }

        public void Test_Constraints()
        {
            IConnection mConnection = base.ConnectionInitialized;

            string fullpath = @"..\..\..\..\TestData\PARCEL_NEW.SDF";

            File.Delete(fullpath);

            ICreateDataStore CreateCmd = (ICreateDataStore)mConnection.CreateCommand(CommandType.CommandType_CreateDataStore);

            IDataStorePropertyDictionary dictionary = CreateCmd.DataStoreProperties;

            string[] names = dictionary.PropertyNames;

            dictionary.SetProperty(names[0], fullpath);

            CreateCmd.Execute();

            mConnection.ConnectionString = @"File=..\..\..\..\TestData\PARCEL_NEW.SDF";
            Debug.Assert(mConnection.ConnectionState == ConnectionState.ConnectionState_Closed, "connection state not closed");
            ConnectionState state = mConnection.Open();
            Debug.Assert(state == ConnectionState.ConnectionState_Open, "connection state not open");
            Debug.Assert(mConnection.ConnectionState == ConnectionState.ConnectionState_Open, "connection state not open");

            ICreateSpatialContext CreateCreateSpatialContext = (ICreateSpatialContext)mConnection.CreateCommand(CommandType.CommandType_CreateSpatialContext);
            CreateCreateSpatialContext.CoordinateSystemWkt = "[LL84]";
            CreateCreateSpatialContext.Description = "World Coordinate System, Degrees";
            CreateCreateSpatialContext.Name = "World Geodetic Coordinate System, 1984";
            CreateCreateSpatialContext.XYTolerance = 17.0;
            CreateCreateSpatialContext.ZTolerance = 3.14159;
            CreateCreateSpatialContext.Execute();

            IApplySchema applyschema = (IApplySchema)mConnection.CreateCommand(CommandType.CommandType_ApplySchema);

            FeatureSchema schema = new FeatureSchema("MySchema", "");
            FeatureClass clas = new FeatureClass("MyClass", "");
            ClassCollection classes = schema.Classes;
            classes.Add(clas);
            PropertyDefinitionCollection properties = clas.Properties;

            GeometricPropertyDefinition geometry = new GeometricPropertyDefinition("Geom", "");
            geometry.GeometryTypes = 7; // Point, Line, Polygon
            properties.Add(geometry);
            clas.GeometryProperty = geometry;

            DataPropertyDefinition id = new DataPropertyDefinition("FeatId", "");
            id.DataType = DataType.DataType_Int32;
            id.Nullable = false;
            id.ReadOnly = true;
            id.IsAutoGenerated = true;
            properties.Add(id);
            DataPropertyDefinitionCollection idProps = clas.IdentityProperties;
            idProps.Add(id); 

            DataPropertyDefinition dpd = new DataPropertyDefinition("NotNullString", "");
            dpd.Nullable = false;
            dpd.DataType = DataType.DataType_String;
            properties.Add(dpd);

            dpd = new DataPropertyDefinition("StringRange", "");
            PropertyValueConstraintRange rangeConstraint = new PropertyValueConstraintRange();
            rangeConstraint.MaxInclusive = false;
            rangeConstraint.MaxValue = new StringValue("Rich");
            rangeConstraint.MinInclusive = true;
            rangeConstraint.MinValue = new StringValue("Poor");
            dpd.ValueConstraint = rangeConstraint;
            dpd.Nullable = true;
            dpd.DataType = DataType.DataType_String;
            properties.Add(dpd);

            dpd = new DataPropertyDefinition("Int32Range", "The key of the object");
            dpd.DataType = DataType.DataType_Int32;
            rangeConstraint = new PropertyValueConstraintRange();
            rangeConstraint.MaxInclusive = true;
            rangeConstraint.MaxValue = new Int32Value(Int32.MaxValue);
            rangeConstraint.MinInclusive = true;
            rangeConstraint.MinValue = new Int32Value(0);
            dpd.Nullable = true;
            dpd.ValueConstraint = rangeConstraint;
            properties.Add(dpd);

            dpd = new DataPropertyDefinition("StringEnum", "");
            PropertyValueConstraintList listConstraint = new PropertyValueConstraintList();
            DataValueCollection listValues = listConstraint.ConstraintList;
            listValues.Add(new StringValue("Up"));
            listValues.Add(new StringValue("Down"));
            listValues.Add(new StringValue("Left"));
            listValues.Add(new StringValue("Right"));
            dpd.Nullable = true;
            dpd.ValueConstraint = listConstraint;
            dpd.DataType = DataType.DataType_String;
            properties.Add(dpd);

            dpd = new DataPropertyDefinition("Int32Enum", "");
            listConstraint = new PropertyValueConstraintList();
            listValues = listConstraint.ConstraintList;
            listValues.Add(new Int32Value(1));
            listValues.Add(new Int32Value(2));
            listValues.Add(new Int32Value(3));
            listValues.Add(new Int32Value(5));
            listValues.Add(new Int32Value(7));
            listValues.Add(new Int32Value(9));
            dpd.Nullable = true;
            dpd.ValueConstraint = listConstraint;
            dpd.DataType = DataType.DataType_Int32;
            properties.Add(dpd);

            applyschema.FeatureSchema = schema;
            applyschema.Execute();

            IDescribeSchema descSchema = (IDescribeSchema)mConnection.CreateCommand(CommandType.CommandType_DescribeSchema);
            FeatureSchemaCollection schemas = descSchema.Execute();
            Debug.Assert(schemas.Count == 1, "Expecting 1 schema");
            schema = schemas[0];
            Debug.Assert(schema.Name == "MySchema", "Expecting 'MySchema' schema");
            classes = schema.Classes;
            Debug.Assert(classes.Count == 1, "Expecting 1 class");
            ClassDefinition classDef = classes[0];
            Debug.Assert(classDef.Name == "MyClass", "Expecting 'MyClass' class");

            dpd = (DataPropertyDefinition)classDef.Properties["Int32Range"];
            rangeConstraint = (PropertyValueConstraintRange)dpd.ValueConstraint;
            Int32Value minVal = (Int32Value)rangeConstraint.MinValue;
            Int32Value maxVal = (Int32Value)rangeConstraint.MaxValue;

            Debug.Assert(minVal.Int32 == 0);
            Debug.Assert(maxVal.Int32 == Int32.MaxValue);

            mConnection.Close();
            Debug.Assert(mConnection.ConnectionState == ConnectionState.ConnectionState_Closed, "connection state not closed");

            Console.WriteLine("Test_Constraints runs successfully !");
        }

        public void Test_LargeDataVolumeInsert()
        {
            IConnection mConnection = base.ConnectionInitialized;

            mConnection.ConnectionString = @"File=..\..\..\..\TestData\PARCEL_NEW.SDF";
            Debug.Assert(mConnection.ConnectionState == ConnectionState.ConnectionState_Closed, "connection state not closed");
            ConnectionState state = mConnection.Open();
            Debug.Assert(state == ConnectionState.ConnectionState_Open, "connection state not open");
            Debug.Assert(mConnection.ConnectionState == ConnectionState.ConnectionState_Open, "connection state not open");

            IInsert insertCmd = (IInsert)mConnection.CreateCommand(CommandType.CommandType_Insert);

            insertCmd.SetFeatureClassName("MyClass");
            PropertyValueCollection propVals = insertCmd.PropertyValues;

            PropertyValue propVal1 = new PropertyValue();
            propVal1.Name = new Identifier("NotNullString");
            PropertyValue propVal2 = new PropertyValue();
            propVal2.Name = new Identifier("StringRange");
            PropertyValue propVal3 = new PropertyValue();
            propVal3.Name = new Identifier("Int32Range");
            PropertyValue propVal4 = new PropertyValue();
            propVal4.Name = new Identifier("StringEnum");
            PropertyValue propVal5 = new PropertyValue();
            propVal5.Name = new Identifier("Geom");

            Expression expr1 = Expression.Parse("'AB'");
            Expression expr2 = Expression.Parse("'Poor'");
            Expression expr4 = Expression.Parse("'Down'");
            Expression expr5 = Expression.Parse("GEOMFROMTEXT('LINESTRING XY (100000.0 100000.0, 200000.0 200000.0, 100000.0 300000.0)')");
            Int32Value intVal = new Int32Value(0);

            propVals.Clear();
            propVals.Add(propVal1);
            propVals.Add(propVal2);
            propVals.Add(propVal3);
            propVals.Add(propVal4);
            propVals.Add(propVal5);

            propVal1.Value = (ValueExpression)expr1;
            propVal2.Value = (ValueExpression)expr2;
            propVal3.Value = (ValueExpression)intVal;
            propVal4.Value = (ValueExpression)expr4;
            propVal5.Value = (ValueExpression)expr5;

            IFeatureReader reader;

            for (Int32 counter = 0; counter < 200000; counter++)
            {
                intVal.Int32 = counter;

                reader = insertCmd.Execute();

                reader.Close();
                reader.Dispose();
            }

            Console.WriteLine("Test_LargeDataVolumeInsert runs successfully !");
        }

        private static Expression ParseByDataType(string data, DataType dataType)
        {
            Expression expr = null;
            bool bIsNull = false;

            // NOTE: blob parsing doesn't work yet (ever?) in FDO:
            if (dataType != DataType.DataType_BLOB)
            {
                expr = Expression.Parse(data);

                if (expr is BooleanValue)
                {
                    bIsNull = true;
                }
                else
                {
                    bIsNull = false;
                }
            }

            switch (dataType)
            {
                case DataType.DataType_Boolean:
                    {
                        if (bIsNull)
                        {
                            BooleanValue val = new BooleanValue();
                            val.SetNull();
                            expr = val;
                        }
                        else
                        {
                            BooleanValue value = (BooleanValue)(expr);
                            if (value == null)
                            {
                                Debug.Fail("Wrong data type!");
                            }
                        }
                    }
                    break;

                case DataType.DataType_Byte:
                    {
                        if (bIsNull)
                        {
                            ByteValue val = new ByteValue();
                            val.SetNull();
                            expr = val;
                        }
                        else
                        {
                            Int32Value value = (Int32Value)(expr);
                            if (value == null)
                            {
                                Debug.Fail("Wrong data type!");
                            }
                            expr = new ByteValue((byte)value.Int32);
                        }
                    }
                    break;

                case DataType.DataType_Int16:
                    {
                        if (bIsNull)
                        {
                            Int16Value val = new Int16Value();
                            val.SetNull();
                            expr = val;
                        }
                        else
                        {
                            Int32Value value = (Int32Value)(expr);
                            if (value == null)
                            {
                                Debug.Fail("Wrong data type!");
                            }
                            expr = new Int16Value((Int16)value.Int32);
                        }
                    }
                    break;

                case DataType.DataType_Int32:
                    {
                        if (bIsNull)
                        {
                            Int32Value val = new Int32Value();
                            val.SetNull();
                            expr = val;
                        }
                        else
                        {
                            Int32Value value = (Int32Value)(expr);
                            if (value == null)
                            {
                                Debug.Fail("Wrong data type!");
                            }
                        }
                    }
                    break;

                case DataType.DataType_Int64:
                    {
                        if (bIsNull)
                        {
                            Int64Value val = new Int64Value();
                            val.SetNull();
                            expr = val;
                        }
                        else
                        {
                            Int64Value value = (Int64Value)(expr);
                            if (value == null)
                            {
                                Debug.Fail("Wrong data type!");
                            }
                            expr = new Int64Value((Int64)value.Int64);
                        }
                    }
                    break;

                case DataType.DataType_Single:
                    {
                        if (bIsNull)
                        {
                            SingleValue val = new SingleValue();
                            val.SetNull();
                            expr = val;
                        }
                        else
                        {
                            DoubleValue value = (DoubleValue)(expr);
                            if (value == null)
                            {
                                Debug.Fail("Wrong data type!");
                            }
                            expr = new SingleValue((float)value.Double);
                        }
                    }
                    break;

                case DataType.DataType_Double:
                    {
                        if (bIsNull)
                        {
                            DoubleValue val = new DoubleValue();
                            val.SetNull();
                            expr = val;
                        }
                        else
                        {
                            DoubleValue value = (DoubleValue)(expr);
                            if (value == null)
                            {
                                Debug.Fail("Wrong data type!");
                            }
                        }
                    }
                    break;

                case DataType.DataType_DateTime:
                    {
                        if (bIsNull)
                        {
                            DateTimeValue val = new DateTimeValue();
                            val.SetNull();
                            expr = val;
                        }
                        else
                        {
                            DateTimeValue value = (DateTimeValue)expr;
                            if (value == null)
                            {
                                Debug.Fail("Wrong data type!");
                            }
                        }
                    }
                    break;

                case DataType.DataType_Decimal:
                    {
                        if (bIsNull)
                        {
                            DecimalValue val = new DecimalValue();
                            val.SetNull();
                            expr = val;
                        }
                        else
                        {
                            DoubleValue valueDouble = (DoubleValue)expr;
                            if (valueDouble != null)
                                expr = new DecimalValue((double)valueDouble.Double);
                            else
                            {
                                Int32Value valueInt32 = (Int32Value)expr;
                                if (valueInt32 != null)
                                    expr = new DecimalValue((double)valueInt32.Int32);
                                else
                                    Debug.Fail("Wrong data type!");
                            }
                        }
                    }
                    break;

                case DataType.DataType_String:
                    {
                        if (bIsNull)
                        {
                            StringValue val = new StringValue();
                            val.SetNull();
                            expr = val;
                        }
                        else
                        {
                            StringValue value = (StringValue)expr;
                            if (value == null)
                            {
                                Debug.Fail("Wrong data type!");
                            }
                        }
                    }
                    break;
                default:
                    Debug.Fail("Unhandled data type!");
                    break;
            }

            return expr;
        }
    }
}
