/// <summary>
        ///   Handles the time zone for a date time column
        /// </summary>
        /// <param name="dataObject">The data object.</param>
        /// <param name="columnInfo">The column information.</param>
        /// <param name="reader">The reader.</param>
        /// <returns></returns>
        protected DateTime HandleTimeZone(DateTime dataObject, [NotNull] WriterColumn columnInfo,
                                          [NotNull] IDataRecord reader)
        {
            if (columnInfo is null)
            {
                throw new ArgumentNullException(nameof(columnInfo));
            }
            if (reader is null)
            {
                throw new ArgumentNullException(nameof(reader));
            }
            if (columnInfo.ColumnOrdinalTimeZone > -1)
            {
                var destinationTimeZoneId = reader.GetString(columnInfo.ColumnOrdinalTimeZone);
                if (string.IsNullOrEmpty(destinationTimeZoneId))
                {
                    HandleWarning(columnInfo.Name, "Time zone is empty, value not converted");
                }
                else
                {
                    // ReSharper disable once PossibleInvalidOperationException
                    return(FunctionalDI.AdjustTZExport(dataObject, destinationTimeZoneId, Columns.IndexOf(columnInfo),
                                                       (columnNo, msg) => HandleWarning(Columns[columnNo].Name, msg)).Value);
                }
            }
            else if (!string.IsNullOrEmpty(columnInfo.ConstantTimeZone))
            {
                // ReSharper disable once PossibleInvalidOperationException
                return(FunctionalDI.AdjustTZExport(dataObject, columnInfo.ConstantTimeZone,
                                                   Columns.IndexOf(columnInfo), (columnNo, msg) => HandleWarning(Columns[columnNo].Name, msg)).Value);
            }

            return(dataObject);
        }
        protected string TextEncodeField([NotNull] IFileFormat fileFormat, object dataObject,
                                         [NotNull] WriterColumn columnInfo, bool isHeader,
                                         [CanBeNull] IDataReader reader, [CanBeNull] Func <string, DataType, IFileFormat, string> handleQualify)
        {
            if (columnInfo is null)
            {
                throw new ArgumentNullException(nameof(columnInfo));
            }

            if (fileFormat.IsFixedLength && columnInfo.FieldLength == 0)
            {
                throw new FileWriterException("For fix length output the length of the columns needs to be specified.");
            }

            string displayAs;

            if (isHeader)
            {
                if (dataObject is null)
                {
                    throw new ArgumentNullException(nameof(dataObject));
                }
                displayAs = dataObject.ToString();
            }
            else
            {
                try
                {
                    if (dataObject == null || dataObject is DBNull)
                    {
                        displayAs = columnInfo.ValueFormat.DisplayNullAs;
                    }
                    else
                    {
                        switch (columnInfo.ValueFormat.DataType)
                        {
                        case DataType.Integer:
                            displayAs = Convert.ToInt64(dataObject).ToString(columnInfo.ValueFormat.NumberFormat, CultureInfo.InvariantCulture).Replace(
                                CultureInfo.InvariantCulture.NumberFormat.NumberGroupSeparator, columnInfo.ValueFormat.GroupSeparator);
                            break;

                        case DataType.Boolean:
                            displayAs = (bool)dataObject
                  ? columnInfo.ValueFormat.True
                  : columnInfo.ValueFormat.False;
                            break;

                        case DataType.Double:
                            displayAs = StringConversion.DoubleToString(
                                dataObject is double d ? d : Convert.ToDouble(dataObject.ToString(), CultureInfo.InvariantCulture),
                                columnInfo.ValueFormat);
                            break;

                        case DataType.Numeric:
                            displayAs = StringConversion.DecimalToString(
                                dataObject is decimal @decimal
                    ? @decimal
                    : Convert.ToDecimal(dataObject.ToString(), CultureInfo.InvariantCulture),
                                columnInfo.ValueFormat);
                            break;

                        case DataType.DateTime:
                            displayAs = reader == null
                  ? StringConversion.DateTimeToString((DateTime)dataObject, columnInfo.ValueFormat)
                  : StringConversion.DateTimeToString(HandleTimeZone((DateTime)dataObject, columnInfo, reader),
                                                      columnInfo.ValueFormat);

                            break;

                        case DataType.Guid:
                            // 382c74c3-721d-4f34-80e5-57657b6cbc27
                            displayAs = ((Guid)dataObject).ToString();
                            break;

                        case DataType.String:
                        case DataType.TextToHtml:
                        case DataType.TextToHtmlFull:
                        case DataType.TextPart:
                            displayAs = dataObject.ToString();
                            if (columnInfo.ValueFormat.DataType == DataType.TextToHtml)
                            {
                                displayAs = HTMLStyle.TextToHtmlEncode(displayAs);
                            }

                            // a new line of any kind will be replaced with the placeholder if set
                            if (fileFormat.NewLinePlaceholder.Length > 0)
                            {
                                displayAs = StringUtils.HandleCRLFCombinations(displayAs, fileFormat.NewLinePlaceholder);
                            }

                            if (fileFormat.DelimiterPlaceholder.Length > 0 && fileFormat.FieldDelimiterChar != '\0')
                            {
                                displayAs = displayAs.Replace(fileFormat.FieldDelimiterChar.ToStringHandle0(),
                                                              fileFormat.DelimiterPlaceholder);
                            }

                            if (fileFormat.QuotePlaceholder.Length > 0 && fileFormat.FieldQualifierChar != '\0')
                            {
                                displayAs = displayAs.Replace(fileFormat.FieldQualifierChar.ToStringHandle0(), fileFormat.QuotePlaceholder);
                            }
                            break;

                        default:
                            displayAs = string.Empty;
                            break;
                        }
                    }
                }
                catch (Exception ex)
                {
                    // In case a cast did fail (eg.g trying to format as integer and providing a text, use the
                    // original value
                    displayAs = dataObject?.ToString() ?? string.Empty;
                    if (string.IsNullOrEmpty(displayAs))
                    {
                        HandleError(columnInfo.Name, ex.Message);
                    }
                    else
                    {
                        HandleWarning(columnInfo.Name,
                                      "Value stored as: " + displayAs +
                                      $"\nExpected {columnInfo.ValueFormat.DataType} but was {dataObject?.GetType()}" + ex.Message);
                    }
                }
            }

            // Adjust the output in case its is fixed length
            if (fileFormat.IsFixedLength)
            {
                if (displayAs.Length <= columnInfo.FieldLength || columnInfo.FieldLength <= 0)
                {
                    return(displayAs.PadRight(columnInfo.FieldLength, ' '));
                }
                HandleWarning(columnInfo.Name,
                              $"Text with length of {displayAs.Length} has been cut off after {columnInfo.FieldLength} character");
                return(displayAs.Substring(0, columnInfo.FieldLength));
            }

            // Qualify text if required
            if (fileFormat.FieldQualifierChar != '\0' && handleQualify != null)
            {
                return(handleQualify(displayAs, columnInfo.ValueFormat.DataType, fileFormat));
            }

            return(displayAs);
        }
        /// <summary>
        ///   Gets the column information based on the SQL Source, but overwritten with the definitions
        /// </summary>
        /// <param name="generalFormat">
        ///   general value format for not explicitly specified columns format
        /// </param>
        /// <param name="columnDefinitions"></param>
        /// <param name="sourceSchemaDataReader">The reader for the source.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException">reader</exception>
        public static IEnumerable <IColumn> GetColumnInformation(IValueFormat generalFormat,
                                                                 IReadOnlyCollection <IColumn> columnDefinitions, DataTable schemaTable)
        {
            if (schemaTable == null)
            {
                throw new ArgumentNullException(nameof(schemaTable));
            }
            var result = new List <WriterColumn>();

            var colName = new BiDirectionalDictionary <int, string>();

            // Make names unique and fill the dictionary
            foreach (DataRow schemaRow in schemaTable.Rows)
            {
                var colNo   = (int)schemaRow[SchemaTableColumn.ColumnOrdinal];
                var newName =
                    StringUtils.MakeUniqueInCollection(colName.Values, schemaRow[SchemaTableColumn.ColumnName].ToString());

                colName.Add(colNo, newName);
            }

            foreach (DataRow schemaRow in schemaTable.Rows)
            {
                var colNo  = (int)schemaRow[SchemaTableColumn.ColumnOrdinal];
                var column = columnDefinitions.FirstOrDefault(x => x.Name.Equals(colName[colNo], StringComparison.OrdinalIgnoreCase));

                if (column != null && column.Ignore)
                {
                    continue;
                }

                // Based on the data Type in the reader defined and the general format create the value format
                var valueFormat = column?.ValueFormat ?? new ImmutableValueFormat(
                    ((Type)schemaRow[SchemaTableColumn.DataType]).GetDataType(), generalFormat.DateFormat,
                    generalFormat.DateSeparator,
                    generalFormat.TimeSeparator, generalFormat.NumberFormat, generalFormat.GroupSeparator,
                    generalFormat.DecimalSeparator, generalFormat.True,
                    generalFormat.False, generalFormat.DisplayNullAs);

                var fieldLength = Math.Max((int)schemaRow[SchemaTableColumn.ColumnSize], 0);
                switch (valueFormat.DataType)
                {
                case DataType.Integer:
                    fieldLength = 10;
                    break;

                case DataType.Boolean:
                {
                    var lenTrue  = valueFormat.True.Length;
                    var lenFalse = valueFormat.False.Length;
                    fieldLength = lenTrue > lenFalse ? lenTrue : lenFalse;
                    break;
                }

                case DataType.Double:
                case DataType.Numeric:
                    fieldLength = 28;
                    break;

                case DataType.DateTime:
                    fieldLength = valueFormat.DateFormat.Length;
                    break;

                case DataType.Guid:
                    fieldLength = 36;
                    break;

                case DataType.String:
                case DataType.TextToHtml:
                case DataType.TextToHtmlFull:
                case DataType.TextPart:
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
                }

                var constantTimeZone            = string.Empty;
                var columnOrdinalTimeZoneReader = -1;

                // the timezone information
                if (column != null)
                {
                    var tz = column.TimeZonePart;
                    if (!string.IsNullOrEmpty(tz))
                    {
                        var tzInfo = tz.GetPossiblyConstant();
                        if (tzInfo.Item2)
                        {
                            constantTimeZone = tzInfo.Item1;
                        }
                        else
                        {
                            if (colName.TryGetByValue(tzInfo.Item1, out var ordinal))
                            {
                                columnOrdinalTimeZoneReader = ordinal;
                            }
                        }
                    }
                }
                var ci = new WriterColumn(colName[colNo], colNo, valueFormat, fieldLength, constantTimeZone, columnOrdinalTimeZoneReader);
                result.Add(ci);

                // add an extra column for the time, reading columns they get combined, writing them they
                // get separated again

                if (column == null || string.IsNullOrEmpty(column.TimePart) || colName.ContainsValue(column.TimePart))
                {
                    continue;
                }

                if (ci.ValueFormat.DateFormat.IndexOfAny(new[] { 'h', 'H', 'm', 's' }) != -1)
                {
                    Logger.Warning(
                        $"'{ci.Name}' will create a separate time column '{column.TimePart}' but seems to write time itself '{ci.ValueFormat.DateFormat}'");
                }

                // In case we have a split column, add the second column (unless the column is also present
                result.Add(new WriterColumn(column.TimePart, colNo, new ImmutableValueFormat(DataType.DateTime, column.TimePartFormat, timeSeparator: column.ValueFormat.TimeSeparator), column.TimePartFormat.Length, constantTimeZone, columnOrdinalTimeZoneReader));
            }

            return(result);
        }