/// <summary>
 /// Write the SDMX compliant values for dataset attached attributes to the <see cref="DataRetrievalInfoSeries.SeriesWriter"/> if <see cref="MappedValues.StartedDataSet"/> is true
 /// </summary>
 /// <param name="componentValues">
 /// The map between components and their values 
 /// </param>
 /// <param name="info">
 /// The current Data Retrieval state 
 /// </param>
 protected static void TryWriteDataSet(MappedValues componentValues, DataRetrievalInfoSeries info)
 {
     if (!componentValues.StartedDataSet)
     {
         WriteDataSetResults(componentValues, info);
         componentValues.StartedDataSet = true;
     }
 }
        /// <summary>
        /// Writes the attributes.
        /// </summary>
        /// <param name="attributes">The attributes.</param>
        /// <param name="info">The info.</param>
        protected static void WriteAttributes(IEnumerable<ComponentValue> attributes, DataRetrievalInfoSeries info)
        {
            // write  attributes
            foreach (var keyValuePair in attributes)
            {
                var componentEntity = keyValuePair.Key;
                var value = keyValuePair.Value;

                // SODIHD-1272 write optional attribute only if it is not empty.
                if (componentEntity.ComponentType != SdmxComponentType.Attribute || componentEntity.AttStatus != AssignmentStatus.Conditional || !string.IsNullOrEmpty(value))
                {
                    info.SeriesWriter.WriteAttributeValue(componentEntity.Id, value);
                }
            }
        }
        /// <summary>
        /// Retrieve data from a DDB and write it to the specified <paramref name="writer"/> This is the main public method of the DataRetriever class. It is called with a populated QueryBean (containing essentially an SDMX-ML Query) and a database Connection to a "Mapping Store" database. This method is responsible for: 
        /// <list type="bullet">
        /// <item>
        /// Retrieving the <see cref="MappingSetEntity"/> (the class containing the performed mappings), according to the provided Dataflow ID, from the "Mapping Store". Mapping Sets are defined on a Dataflow basis. Thus, this method checks the input QueryBean for the Dataflow that data are requested and fetches the appropriate
        /// <see cref="MappingSetEntity"/>. If no <see cref="MappingSetEntity"/> exists, an exception (<see cref="DataRetrieverException"/>) is thrown.
        /// </item>
        /// <item>
        /// Calling the method generating the appropriate SQL for the dissemination database.
        /// </item>
        /// <item>
        /// Calling the method that executes the generated SQL and uses the
        ///  <paramref name="writer"/>
        ///  to write the output.
        /// </item>
        /// </list>
        /// <note type="note">
        /// The "Data Retriever" expects exactly one Dataflow clause under the DataWhere clause, exactly one
        ///        DataFlowBean within the DataWhereBean (which in turn resides inside the incoming QueryBean).
        /// </note>
        /// </summary>
        /// <exception cref="DataRetrieverException">
        /// See the
        ///   <see cref="ErrorTypes"/>
        ///   for more details
        /// </exception>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="query"/>
        ///   is null
        ///   -or-
        ///   <paramref name="writer"/>
        ///   is null
        /// </exception>
        /// <param name="query">
        /// The query bean for which data will be requested 
        /// </param>
        /// <param name="writer">
        /// The <see cref="ISeriesWriter"/> (e.g. Compact, Generic) writer 
        /// </param>
        /// <example>
        /// An example using this method in C# with <see cref="CompactWriter"/> 
        /// <code source="ReUsingExamples\DataRetriever\ReUsingDataRetriever.cs" lang="cs">
        /// </code>
        /// An example using this method in C# with <see cref="GenericDataWriter"/> 
        /// <code source="ReUsingExamples\DataRetriever\ReUsingDataRetrieverGeneric.cs" lang="cs">
        /// </code>
        /// </example>
        public void GetData(IDataQuery dataQuery, IDsplDataWriterEngine dataWriter)
        {

            if (dataQuery == null)
            {
                throw new ArgumentNullException("query");
            }

            if (dataWriter == null)
            {
                throw new ArgumentNullException("writer");
            }

            try
            {
                Logger.Info(Resources.InfoDataRetrieverBBInvoked);
                Logger.Info(Resources.InfoOutput + dataWriter.GetType().Name);

                // validate input and initialize the mappingset entitiy
                MappingSetEntity mappingSet = this.Initialize(dataQuery);

                var info = new DataRetrievalInfoSeries(mappingSet, dataQuery, this._connectionStringSettings, dataWriter, this._sdmxSchemaVersion)
                {
                    DefaultHeader = this._defaultHeader
                };
                ValidateMappingSet(info);

                //Pietro 27/01
                //this.WriteHeader(dataWriter, info);

                //(SRA-345) 
                //DR the info from I*DataQuery. DimensionAtObservation to IDataWriterEngine.StartDataSet  
                IDatasetStructureReference dsr = new DatasetStructureReferenceCore("", dataQuery.DataStructure.AsReference, null, null, dataQuery.DimensionAtObservation);
                IDatasetHeader header = new DatasetHeaderCore(this._defaultHeader.DatasetId, this._defaultHeader.Action, dsr);

                //info.MappingSet.DataSet.Description.ToString()
                //info.MappingSet.Dataflow.Dsd.Id.ToString()
                dataWriter.SetDsdOrder(dataQuery.DataStructure);
                dataWriter.StartDataset(info.MappingSet.DataSet.Description.ToString(), dataQuery.Dataflow, dataQuery.DataStructure, header, null);

                // Generate sql query
                this.GenerateSql(info, _sqlBuilder);

                this.GenerateSql(info, SeriesDataSetSqlBuilder.Instance);

                this.GenerateSql(info, SeriesGroupSqlBuilder.Instance);                

                // execute sql query
                this.ExecuteSql(info, SeriesQueryEngineManager.Instance.GetQueryEngine(info));

                // close output
                dataWriter.Close();
                Logger.Info(Resources.InfoEndDataRetrieverBBInvoked);
            }
            catch (DataRetrieverException)
            {
                throw;
            }
            catch (SdmxException)
            {
                throw;
            }
            catch (DbException ex)
            {
                Logger.Error(ex.ToString());
                throw new DataRetrieverException(ex,
                    SdmxErrorCode.GetFromEnum(SdmxErrorCodeEnumType.InternalServerError),
                    Resources.DataRetriever_ExecuteSqlQuery_Error_executing_generated_SQL_and_populating_SDMX_model);
            }
            catch (OutOfMemoryException)
            {
                throw;
            }
            catch (Exception ex)
            {
                Logger.Error(ex.ToString());
                throw new DataRetrieverException(ex,
                    SdmxErrorCode.GetFromEnum(SdmxErrorCodeEnumType.InternalServerError),
                    Resources.DataRetriever_ExecuteSqlQuery_Error_during_writing_responce);
            }
        }
        /// <summary>
        /// Appends the cached where to <paramref name="sql"/> from <see cref="DataRetrievalInfoSeries.SqlWhereCache"/> if it is not null or from <see cref="SqlBuilderBase.GenerateWhere"/>
        /// </summary>
        /// <param name="info">
        /// The current DataRetrieval state 
        /// </param>
        /// <param name="sql">
        /// The SQL String buffer to 
        /// </param>
        private static void AppendCachedWhere(DataRetrievalInfoSeries info, SqlQuery sqq)
        {
            if (string.IsNullOrEmpty(info.SqlWhereCache))
            {
                info.SqlWhereCache = GenerateWhere(info);
            }

            sqq.appendSql(info.SqlWhereCache);
        }
        /// <summary>
        /// Write observation element. The time, observation and attribute values are in <paramref name="row"/>
        /// </summary>
        /// <param name="info">
        /// The current Data Retrieval state 
        /// </param>
        /// <param name="row">
        /// The map between components and their values 
        /// </param>
        /// <param name="value">
        /// The observation value 
        /// </param>
        /// <returns>
        /// The number of observations stored. Is is always 1 
        /// </returns>
        protected static int WriteObservation(DataRetrievalInfoSeries info, MappedValues row, string value)
        {
            // write time and obs value
            info.SeriesWriter.WriteObservation(row.DimensionAtObservationValue, value);

            // write observation attributes
            WriteAttributes(row.AttributeObservationValues, info);

            return 1;
        }
        /// <summary>
        /// This method executes an SQL query for a group on the dissemination database and writes it to <see cref="DataRetrievalInfoSeries.SeriesWriter"/> The SQL query is located inside <paramref name="groupInformation"/> at <see cref="GroupInformation.SQL"/> . The group information is located in <paramref name="groupInformation"/>
        /// </summary>
        /// <param name="groupInformation">
        /// The current Time Series Group information 
        /// </param>
        /// <param name="info">
        /// The current DataRetrieval state 
        /// </param>
        /// <param name="connection">
        /// The connection to the dissemination database 
        /// </param>
        private void ExecuteGroupSqlQuery(
            GroupInformation groupInformation, DataRetrievalInfoSeries info, DbConnection connection)
        {
            if (string.IsNullOrEmpty(groupInformation.SQL))
            {
                return;
            }

            using (DbCommand command = connection.CreateCommand())
            {
                command.CommandText = groupInformation.SQL;

                command.CommandTimeout = 0;
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                using (IDataReader reader = command.ExecuteReader())
                {
                    stopwatch.Stop();
                    Logger.Info(
                         string.Format(CultureInfo.InvariantCulture, "Execute Group SQL Reader: {0}", stopwatch.Elapsed));

                    while (reader.Read())
                    {
                        var componentValues = new MappedValues(info, groupInformation.ComponentMappings);
                        if (HandleComponentMapping(reader, componentValues, groupInformation.ComponentMappings, info)
                            && !ProcessedKeySet(groupInformation, info, componentValues))
                        {
                            this.StoreTimeSeriesGroupResults(info, groupInformation, componentValues);
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Check if the specified <paramref name="targetGroup"/> contains the <paramref name="componentValues"/>
        /// </summary>
        /// <param name="targetGroup">
        /// The target group 
        /// </param>
        /// <param name="info">
        /// The current data retrieval state 
        /// </param>
        /// <param name="componentValues">
        /// The component values 
        /// </param>
        /// <returns>
        /// A value indicating whether the key is already processed or not. 
        /// </returns>
        private static bool ProcessedKeySet(
            GroupInformation targetGroup, DataRetrievalInfoSeries info, MappedValues componentValues)
        {
            var current = new ReadOnlyKey(componentValues, info.GroupNameTable);

            if (!targetGroup.KeySet.ContainsKey(current))
            {
                targetGroup.KeySet.Add(current, null);
                return false;
            }

            return true;
        }
 /// <summary>
 /// Write the SDMX compliant values for dataset attached attributes to the <see cref="DataRetrievalInfoSeries.SeriesWriter"/>
 /// </summary>
 /// <param name="componentValues">
 /// The map between components and their values 
 /// </param>
 /// <param name="info">
 /// The current Data Retrieval state 
 /// </param>
 private static void WriteDataSetResults(MappedValues componentValues, DataRetrievalInfoSeries info)
 {
     WriteAttributes(componentValues.AttributeDataSetValues, info);
 }
        /// <summary>
        /// Store the SDMX compliant data for each component entity in the store
        /// </summary>
        /// <param name="info">
        /// The current Data Retrieval state 
        /// </param>
        /// <param name="groupInformation">
        /// The current group related information 
        /// </param>
        /// <param name="row">
        /// The map between components and their values 
        /// </param>
        protected virtual void StoreTimeSeriesGroupResults(
            DataRetrievalInfoSeries info, GroupInformation groupInformation, MappedValues row)
        {
            info.SeriesWriter.StartGroup(groupInformation.ThisGroup.Id);

            // write group dimensions
            foreach (var dimensionValue in row.DimensionValues)
            {
                info.SeriesWriter.WriteGroupKeyValue(dimensionValue.Key.Id, dimensionValue.Value);
            }

            // write group attributes
            WriteAttributes(row.AttributeGroupValues, info);
        }
        /// <summary>
        /// This method executes an SQL query for retrieving the dataset attributes on the dissemination database and writes it to <see cref="DataRetrievalInfoSeries.SeriesWriter"/>
        /// </summary>
        /// <param name="info">
        /// The current DataRetrieval state 
        /// </param>
        /// <param name="connection">
        /// The connection to the dissemination database 
        /// </param>
        private static void ExecuteDataSetAttributeSqlQuery(DataRetrievalInfoSeries info, DbConnection connection)
        {
            if (string.IsNullOrEmpty(info.DataSetSqlString))
            {
                return;
            }

            using (DbCommand command = connection.CreateCommand())
            {
                command.CommandText = info.DataSetSqlString;

                // for pc-axis
                command.CommandTimeout = 0;
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                using (IDataReader reader = command.ExecuteReader())
                {
                    stopwatch.Stop();
                    Logger.Info(
                         string.Format(
                             CultureInfo.InvariantCulture, "Execute DataSet Attribute SQL Reader: {0}", stopwatch.Elapsed));
                    bool found = false;
                    while (!found && reader.Read())
                    {
                        var componentValues = new MappedValues(info, info.DataSetAttributes);
                        if (HandleComponentMapping(reader, componentValues, info.DataSetAttributes, info))
                        {
                            WriteDataSetResults(componentValues, info);
                            found = true;
                        }
                    }

                    // To avoid populating the IDataReader.RecordsAffected on IDataReader close which can take some time on some cases (typec)
                    // this requires an updated MySQL driver, 6.3.7 or later. See http://bugs.mysql.com/bug.php?id=60541
                    command.Cancel();
                }
            }
        }
        /// <summary>
        /// Write a series element.
        /// </summary>
        /// <param name="info">
        /// The current Data Retrieval state 
        /// </param>
        /// <param name="row">
        /// The map between components and their values 
        /// </param>
        protected static void WriteSeries(DataRetrievalInfoSeries info, MappedValues row)
        {
            // start series
            info.SeriesWriter.StartSeries();

            // write dimensions
            foreach (var dimensionValue in row.DimensionValues)
            {
                if (!dimensionValue.Key.Id.Equals(info.DimensionAtObservation))
                {
                    info.SeriesWriter.WriteSeriesKeyValue(dimensionValue.Key.Id, dimensionValue.Value);
                }
            }

            // write time period if it is not the dimension at observation
            if (!info.IsTimePeriodAtObservation && info.TimeTranscoder != null)
            {
                info.SeriesWriter.WriteSeriesKeyValue(info.TimeTranscoder.Component.Id, row.TimeValue);
            }

            // write series attributes
            WriteAttributes(row.AttributeSeriesValues, info);
        }
        /// <summary>
        /// This method generates the SQL SELECT statement for the dissemination database that will return the data for the incoming Query.
        /// </summary>
        /// <param name="groupBean">
        /// The group Bean. 
        /// </param>
        /// <param name="info">
        /// The current data retrieval state 
        /// </param>
        /// <returns>
        /// The string containing the SQL query that needs to be executed on the dissemination database, in order to return the data required by the input query 
        /// </returns>
        private static string GenerateGroupSql(GroupInformation groupBean, DataRetrievalInfoSeries info)
        {
            MappingSetEntity mappingSet = info.MappingSet;
            Logger.Info(Resources.InfoBeginGenerateSql);

            SqlQuery sqlQuery = new SqlQuery();
            string sql = string.Empty;

            try
            {
                // Generate Query subparts
                sql = GenerateSelect(true, ConvertToMapping(groupBean.ComponentMappings));
                sqlQuery.appendSql(sql);

                sqlQuery.appendSql(GenerateFrom(mappingSet));

                if (string.IsNullOrEmpty(info.SqlWhereCache))
                {
                    info.SqlWhereCache = GenerateWhere(info);
                }

                sqlQuery.appendSql(info.SqlWhereCache);

                bool bFlat = false;
                var allDimensions = DimensionAtObservation.GetFromEnum(DimensionAtObservationEnumType.All).Value;
                IBaseDataQuery baseDataQuery = (IBaseDataQuery)info.ComplexQuery ?? info.Query;
                //the Flat option will be read only when we have flat aka AllDimensions
                if (baseDataQuery.DimensionAtObservation.Equals(allDimensions))
                    Boolean.TryParse(ConfigurationManager.AppSettings["QueryFlatFormat"], out bFlat);
                if (!bFlat)
                    sqlQuery.appendSql(GenerateOrderBy(info, groupBean.ThisGroup.Dimensions));
            }
            catch (Exception ex)
            {
                Logger.Error(ex.ToString());
                throw new DataRetrieverException(ex,
                    SdmxErrorCode.GetFromEnum(SdmxErrorCodeEnumType.SemanticError),
                    Resources.ErrorUnableToGenerateSQL);
                //ErrorTypes.QUERY_PARSING_ERROR, Resources.ErrorUnableToGenerateSQL, ex);
            }

            // log for easy debug
            Logger.Info(string.Format(CultureInfo.InvariantCulture, Resources.InfoGeneratedSQLFormat1, sql));
            Logger.Info(Resources.InfoEndGenerateSql);

            return sqlQuery.getSql();
        }
        /// <summary>
        /// Store the SDMX compliant data for each component entity in the store
        /// </summary>
        /// <param name="info">
        /// The current Data Retrieval state 
        /// </param>
        /// <param name="maxMeasures">
        /// The max number of measures to write 
        /// </param>
        /// <param name="row">
        /// The map between components and their values 
        /// </param>
        /// <returns>
        /// The number of observations stored 
        /// </returns>
        private static int WriteXsMeasures(DataRetrievalInfoSeries info, int maxMeasures, MappedValues row)
        {
            int count = 0;

            if (row.IsNewKey())
            {
                TryWriteDataSet(row, info);
                WriteXsMeasureCache(info, row.XSMeasureCaches);

                row.InitXsBuffer();
            }

            for (int i = 0; i < maxMeasures; i++)
            {
                var crossSectionalMeasureMapping = info.CrossSectionalMeasureMappings[i];
                var xsComponent = crossSectionalMeasureMapping.Components[0];
                row.AddToBuffer(xsComponent);
                count++;
            }

            return count;
        }
        /// <summary>
        /// Write the cached <paramref name="xsMeasures"/> .
        /// </summary>
        /// <param name="info">
        /// The current dataretrieval info 
        /// </param>
        /// <param name="xsMeasures">
        /// The xs measures. 
        /// </param>
        private static void WriteXsMeasureCache(
            DataRetrievalInfoSeries info, IEnumerable<KeyValuePair<ComponentEntity, XsMeasureCache>> xsMeasures)
        {
            foreach (var xsMeasure in xsMeasures)
            {
                // start series
                info.SeriesWriter.StartSeries();
                foreach (var dimensionValue in xsMeasure.Value.CachedSeriesKey)
                {
                    info.SeriesWriter.WriteSeriesKeyValue(dimensionValue.Key.Id, dimensionValue.Value);
                }

                info.SeriesWriter.WriteSeriesKeyValue(info.MeasureComponent.Id, xsMeasure.Value.XSMeasureCode);

                WriteAttributes(xsMeasure.Value.CachedSeriesAttributes, info);

                for (int index = 0; index < xsMeasure.Value.XSMeasureCachedObservations.Count; index++)
                {
                    var cachedObservation = xsMeasure.Value.XSMeasureCachedObservations[index];
                    var attributes = xsMeasure.Value.Attributes[index];

                    info.SeriesWriter.WriteObservation(cachedObservation.Key, cachedObservation.Value);
                    WriteAttributes(attributes, info);
                }
            }
        }
        /// <summary>
        /// Store the SDMX compliant data for each component entity in the store
        /// </summary>
        /// <param name="info">
        /// The current Data Retrieval state 
        /// </param>
        /// <param name="groupInformation">
        /// The current group related information 
        /// </param>
        /// <param name="row">
        /// The map between components and their values 
        /// </param>
        protected override void StoreTimeSeriesGroupResults(
            DataRetrievalInfoSeries info, GroupInformation groupInformation, MappedValues row)
        {
            if (groupInformation.MeasureComponent == null)
            {
                base.StoreTimeSeriesGroupResults(info, groupInformation, row);
                return;
            }

            foreach (var measureDimensionQueryValue in info.CrossSectionalMeasureMappings)
            {
                info.SeriesWriter.StartGroup(groupInformation.ThisGroup.Id);

                // write group dimensions
                foreach (var dimensionValue in row.DimensionValues)
                {
                    info.SeriesWriter.WriteGroupKeyValue(dimensionValue.Key.Id, dimensionValue.Value);
                }

                var xsComponent = measureDimensionQueryValue.Components[0];

                info.SeriesWriter.WriteGroupKeyValue(
                    groupInformation.MeasureComponent.Id, xsComponent.CrossSectionalMeasureCode);

                // write group attributes
                WriteAttributes(row.AttributeGroupValues, info);
            }
        }