using System;
using System.Text;
using System.Globalization;

using System.Data;
using System.Data.SqlClient;

using CommonHelpers;

namespace SqlToCsvExport {

//**************************************************************************************************
static partial class Program {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

static void PrintSyntax()
{
   HConsole.PrintExpressive(HLocal.TEXT(
      "Syntax: SqlToCsvExport \"<Server>\" \"<Database>\" "+
      "\"<SQL-file (script name/path) or name of stored procedure>\" "+
      "\"<Result file path or string of parameters for SP (if supported)>\"",
      "Синтаксис: SqlToCsvExport \"<Сервер>\" \"<База данных>\" "+
      "\"<SQL-файл (скрипт) либо имя хранимой процедуры>\" "+
      "\"<Путь файла результатов либо строка параметров для Х.П. (если поддерживаются)>\"" ));
   HConsole.PrintIntensive(HLocal.TEXT(
      "Optional connection parameters (environment variables):",
      "Опциональные параметры поключения (переменные среды):" ));
   HConsole.PrintExpressive(HLocal.TEXT(
      "{0} $sql_user {0}{0} name for SQL-login;",
      "{0} $sql_user {0}{0} имя входа в SQL;"
      ), DashChar );
   HConsole.PrintExpressive(HLocal.TEXT(
      "{0} $sql_password_var {0}{0} name of environment variable for SQL-password "+
      "(container).",
      "{0} $sql_password_var {0}{0} наименование переменной среды для пароля SQL "+
      "(контейнер)."
      ), DashChar );
   HConsole.PrintIntensive(HLocal.TEXT(
      "Additional parameters-options (environment variables):",
      "Дополнительные параметры-опции (переменные среды):" ));
   HConsole.PrintExpressive(HLocal.TEXT(
      "{0} SqlToCsvExport.InsertColHeaders {0}{0} insert column headers (1-st line): "+
      "1/0 (default: {1}, {0} {2});",
      "{0} SqlToCsvExport.InsertColHeaders {0}{0} вставлять заголовки колонок (1-я строка): "+
      "1/0 (по умолчанию: {1}, {0} {2});" ), DashChar,
      AdditionalParams.DefaultValues.InsertColHeaders ? "1" : "0",
      AdditionalParams.DefaultValues.InsertColHeaders ? OnValueName : OffValueName );
   HConsole.PrintExpressive(HLocal.TEXT(
      "{0} SqlToCsvExport.InsertRecNoColumn {0}{0} insert record no column (1-st column): "+
      "1/0 (default: {1}, {0} {2});",
      "{0} SqlToCsvExport.InsertRecNoColumn {0}{0} вставлять колонку номера записи (1-я колонка): "+
      "1/0 (по умолчанию: {1}, {0} {2});" ), DashChar,
      AdditionalParams.DefaultValues.InsertRecNoColumn ? "1" : "0",
      AdditionalParams.DefaultValues.InsertRecNoColumn ? OnValueName : OffValueName );
   HConsole.PrintExpressive(HLocal.TEXT(
      "{0} SqlToCsvExport.ColSepChar {0}{0} CSV field separator character (default: \"{1}\");",
      "{0} SqlToCsvExport.ColSepChar {0}{0} литера-разделитель полей CSV (по умолчанию: \"{1}\");"
      ), DashChar, AdditionalParams.DefaultValues.ColSepChar );
   HConsole.PrintExpressive(HLocal.TEXT(
      "{0} SqlToCsvExport.EmbraceAllWithQuotes {0}{0} unconditionally embrace data "+
      "with double quotes: 1/0 (default: {1}, {0} {2});",
      "{0} SqlToCsvExport.EmbraceAllWithQuotes {0}{0} безусловно оборачивать данные "+
      "в двойные кавычки: 1/0 (по умолчанию: {1}, {0} {2});"
      ), DashChar,
      AdditionalParams.DefaultValues.EmbraceAllWithQuotes ? "1" : "0",
      AdditionalParams.DefaultValues.EmbraceAllWithQuotes ? OnValueName : OffValueName );
   HConsole.PrintExpressive(HLocal.TEXT(
      "{0} SqlToCsvExport.IncorrectCharSubst {0}{0} substituition for incorrect character "+
      "(default: \"{1}\");",
      "{0} SqlToCsvExport.IncorrectCharSubst {0}{0} заместитель некорректной литеры "+
      "(по умолчанию: \"{1}\");" ), DashChar,
      AdditionalParams.DefaultValues.IncorrectCharSubst );
   HConsole.PrintExpressive(HLocal.TEXT(
      "{0} SqlToCsvExport.RecNoCaption {0}{0} CSV header for record no "+
      "(default: \"{1}\");",
      "{0} SqlToCsvExport.RecNoCaption {0}{0} заголовок для поля номера записи CSV "+
      "(по умолчанию: \"{1}\");" ), DashChar,
      AdditionalParams.DefaultValues.RecNoCaption );
   HConsole.PrintExpressive(HLocal.TEXT(
      "{0} SqlToCsvExport.SuppressAlienLetters {0}{0} suppress alien letters: "+
      "1/0 (default: {1}, {0} {2});",
      "{0} SqlToCsvExport.SuppressAlienLetters {0}{0} подавлять инородные буквы: "+
      "1/0 (по умолчанию: {1}, {0} {2});" ), DashChar,
      AdditionalParams.DefaultValues.SuppressAlienLetters ? "1" : "0",
      AdditionalParams.DefaultValues.SuppressAlienLetters ? OnValueName : OffValueName );
   HConsole.PrintExpressive(HLocal.TEXT(
      "{0} SqlToCsvExport.ExportInUTF8 {0}{0} save result in UTF-8 (with BOM-prefix) instead of ANSI-CP: "+
      "1/0 (default: {1}, {0} {2}).",
      "{0} SqlToCsvExport.ExportInUTF8 {0}{0} экспорт в кодировке UTF-8 (с префиксом), вместо системной ANSI: "+
      "1/0 (по умолчанию: {1}, {0} {2})." ), DashChar,
      AdditionalParams.DefaultValues.ExportInUTF8 ? "1" : "0",
      AdditionalParams.DefaultValues.ExportInUTF8 ? OnValueName : OffValueName );
}

//--------------------------------------------------------------------------------------------------

static string ComposeCSVHeaderRow(SqlDataReader dataReader)
{
   var sb = new StringBuilder();
   if ( InsertRecNoColumn )
   {
      sb.Append( NormalizeCSVField( CsvRecNoCaption, CSVNormalizationFlags.IsHeaderRow ) );
      sb.Append( CsvSepChar );
   }
   for (int idx=0; idx<dataReader.FieldCount; idx++)
   {
      if ( idx > 0 ) sb.Append( CsvSepChar );
      sb.Append( NormalizeCSVField( dataReader.GetName( idx ),
                 CSVNormalizationFlags.IsHeaderRow ) );
   }
   return sb.ToString();
}

static string ComposeCSVDataRow(SqlDataReader dataReader,int recordNo)
{
   var sb = new StringBuilder();
   if ( InsertRecNoColumn )
   {
      sb.Append( NormalizeCSVField( HInt32.ToNormalString( recordNo ) ));
      sb.Append( CsvSepChar );
   }
   for (int idx=0; idx<dataReader.FieldCount; idx++)
   {
      if ( idx > 0 ) sb.Append( CsvSepChar );
      object fieldValue = dataReader[ idx ];
      sb.Append( NormalizeCSVField( FieldDataToStr( fieldValue ),
         fieldValue is string ? CSVNormalizationFlags.IsStringFieldData : 0 ));
   }
   return sb.ToString();
}

//--------------------------------------------------------------------------------------------------

static string FieldDataToStr(object fieldValue)
{
   if ( fieldValue == null )
      return "";
   if ( fieldValue is int )
      return ((int)fieldValue).ToString( "D", NumberFormatInfo.InvariantInfo );
   if ( fieldValue is DateTime )
      return ((DateTime)fieldValue).ToString( "yyyy-MM-dd HH:mm:ss" );
   if ( fieldValue is byte[] )
      return FormatBinaryFieldData( (byte[])fieldValue );
   return fieldValue.ToString();
}

static string FormatBinaryFieldData(byte[] bytes)
{
   var sb = new StringBuilder( "0x" );
   foreach (byte b in bytes)
      sb.Append( b.ToString( "X2" ) );
   return sb.ToString();
}

//--------------------------------------------------------------------------------------------------

static string NormalizeCSVField(string value,CSVNormalizationFlags flags = 0)
{
   bool
      isHeaderRow       = (flags & CSVNormalizationFlags.IsHeaderRow      ) != 0,
      isStringFieldData = (flags & CSVNormalizationFlags.IsStringFieldData) != 0;
   value = isHeaderRow ? value.Trim() : value.TrimEnd();
   bool embraceInQuotes = EmbraceAllWithQuotes ||
      value.IndexOfAny( new char[]{ CsvSepChar, '"' } ) >= 0;
   var sb = new StringBuilder();
   if ( embraceInQuotes )
      sb.Append( '\"' );
   for (int idx=0; idx<value.Length; idx++)
   {
      char ch = value[ idx ];
      bool isQuote = ch == '\"';
      if ( ch < 0x20 ||
           !embraceInQuotes && HOps.IsOneOf( ch, CsvSepChar, '\"' ) ||
           !isHeaderRow && SuppressAlienLetters &&
           char.IsLetter( ch ) && !HChar.IsInSystemACP( ch ) )
         ch = CsvIncorrectCharSubst;
      sb.Append( ch );
      if ( ch=='\"' && embraceInQuotes )
         sb.Append( ch );
   }
   if ( embraceInQuotes )
      sb.Append( '\"' );
   return sb.ToString();
}

//==================================================================================================

static string NormalizeSQLQueryText(string text)
{
   return NormalizeSQLQueryText( true, text );
}

static string NormalizeSQLQueryText(bool eliminateMarginalSpaces,string text)
{
   if ( eliminateMarginalSpaces )
      text = text.Trim();
   var sb = new StringBuilder();
   foreach (char ch in text)
      if ( ch == '"' ) sb.Append( "\"\"" );
      else sb.Append( ch );
   return sb.ToString();
}

//--------------------------------------------------------------------------------------------------

static string PrepareSQLQueryText(string queryTextFmt,params object[] parameters)
{
   return NormalizeSQLQueryText(string.Format( queryTextFmt, parameters ));
}

//==================================================================================================

const char DashChar = HConsole.DashChar;

static string OnValueName  => HLocal.TEXT( "on", "вкл." );
static string OffValueName => HLocal.TEXT( "off", "выкл." );

static HAppFailureException IncorrectParamsError =>
   new HAppFailureException(HLocal.TEXT(
      "Incorrect command line parameters (look syntax).",
      "Некорректные параметры командной строки (см.синтаксис)." ));

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} // Program
//**************************************************************************************************

} // SqlToCsvExport
