require 'time'
require 'time_with_zone'

module Embulk
  module Output
    class VerticaCSV < OutputPlugin
      class ValueConverterFactory
        attr_reader :schema_type, :value_type, :timestamp_format, :timezone, :zone_offset

        DEFAULT_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S %z"

        # @param [Schema] schema embulk defined column types
        # @param [String] default_timezone
        # @param [Hash]   column_options user defined column types
        # @return [Hash] hash whose key is column_name, and value is its converter (Proc)
        def self.create_converters(schema, default_timezone, column_options)
          Hash[schema.names.zip(schema.types).map do |column_name, schema_type|
            if column_options[column_name]
              value_type       = column_options[column_name]['value_type']
              timestamp_format = column_options[column_name]['timestamp_format'] || DEFAULT_TIMESTAMP_FORMAT
              timezone         = column_options[column_name]['timezone'] || default_timezone
              [column_name, self.new(schema_type, value_type, timestamp_format, timezone).create_converter]
            else
              [column_name, self.new(schema_type, nil, nil, default_timezone).create_converter]
            end
          end]
        end

        def initialize(schema_type, value_type = nil, timestamp_format = nil, timezone = nil)
          @schema_type = schema_type
          @value_type = value_type || schema_type.to_s
          if @schema_type == :timestamp || @value_type == 'timestamp'
            @timestamp_format = timestamp_format
            @timezone = timezone
            @zone_offset = TimeWithZone.zone_offset(@timezone) if @timezone
          end
        end

        def create_converter
          case schema_type
          when :boolean   then boolean_converter
          when :long      then long_converter
          when :double    then double_converter
          when :string    then string_converter
          when :timestamp then timestamp_converter
          else raise NotSupportedType, "embulk-output-verticacsv cannot take column type #{schema_type}"
          end
        end

        def boolean_converter
          case value_type
          when 'boolean' then Proc.new {|val| val }
          when 'string'  then Proc.new {|val| val.to_s }
          else raise NotSupportedType, "embulk-output-verticacsv cannot take column value_type #{value_type} for boolean column"
          end
        end

        def long_converter
          case value_type
          when 'boolean'    then Proc.new {|val| !!val }
          when 'long'       then Proc.new {|val| val }
          when 'double'     then Proc.new {|val| val.to_f }
          when 'string'     then Proc.new {|val| val.to_s }
          when 'timestamp'  then Proc.new {|val| val ? Time.at(val).localtime(zone_offset) : nil }
          else raise NotSupportedType, "embulk-output-verticacsv cannot take column value_type #{value_type} for long column"
          end
        end

        def double_converter
          case value_type
          when 'boolean'   then Proc.new {|val| !!val }
          when 'long'      then Proc.new {|val| val.to_i }
          when 'double'    then Proc.new {|val| val }
          when 'string'    then Proc.new {|val| val.to_s }
          when 'timestamp' then Proc.new {|val| val ? Time.at(val).localtime(zone_offset) : nil }
          else raise NotSupportedType, "embulk-output-verticacsv cannot take column value_type #{value_type} for double column"
          end
        end

        def string_converter
          case value_type
          when 'boolean'   then Proc.new {|val| !!val }
          when 'long'      then Proc.new {|val| val.to_i }
          when 'double'    then Proc.new {|val| val.to_f }
          when 'string'    then Proc.new {|val| val }
          when 'timestamp' then Proc.new {|val| val ? TimeWithZone.set_zone_offset(Time.strptime(val, timestamp_format), zone_offset) : nil }
          else raise NotSupportedType, "embulk-output-verticacsv cannot take column value_type #{value_type} for string column"
          end
        end

        def timestamp_converter
          case value_type
          when 'boolean'   then Proc.new {|val| !!val }
          when 'long'      then Proc.new {|val| val.to_i }
          when 'double'    then Proc.new {|val| val.to_f }
          when 'string'    then Proc.new {|val| val ? val.localtime(zone_offset).strftime(timestamp_format) : nil }
          when 'timestamp' then Proc.new {|val| val ? val.localtime(zone_offset) : nil }
          else raise NotSupportedType, "embulk-output-verticacsv cannot take column value_type #{value_type} for timesatmp column"
          end
        end
      end
    end
  end
end
