# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

require "date"

module Arrow
  class ArrayBuilder
    class << self
      def build(values)
        if self != ArrayBuilder
          builder = new
          return builder.build(values)
        end

        builder_class = nil
        values.each do |value|
          case value
          when nil
            # Ignore
          when true, false
            return BooleanArray.new(values)
          when String
            return StringArray.new(values)
          when Float
            return DoubleArray.new(values)
          when Integer
            if value < 0
              builder = IntArrayBuilder.new
              return builder.build(values)
            else
              builder_class = UIntArrayBuilder
            end
          when Time
            data_type = TimestampDataType.new(:nano)
            builder = TimestampArrayBuilder.new(data_type)
            return builder.build(values)
          when DateTime
            return Date64Array.new(values)
          when Date
            return Date32Array.new(values)
          else
            return StringArray.new(values)
          end
        end
        if builder_class
          builder_class.new.build(values)
        else
          Arrow::StringArray.new(values)
        end
      end

      def buildable?(args)
        args.size == method(:build).arity
      end
    end

    def build(values)
      append(*values)
      finish
    end

    # @since 0.12.0
    def append(*values)
      value_convertable = respond_to?(:convert_to_arrow_value, true)
      start_index = 0
      current_index = 0
      status = :value

      values.each do |value|
        if value.nil?
          if status == :value
            if start_index != current_index
              target_values = values[start_index...current_index]
              if value_convertable
                target_values = target_values.collect do |v|
                  convert_to_arrow_value(v)
                end
              end
              append_values(target_values, nil)
              start_index = current_index
            end
            status = :null
          end
        else
          if status == :null
            append_nulls(current_index - start_index)
            start_index = current_index
            status = :value
          end
        end
        current_index += 1
      end
      if start_index != current_index
        if status == :value
          if start_index == 0 and current_index == values.size
            target_values = values
          else
            target_values = values[start_index...current_index]
          end
          if value_convertable
            target_values = target_values.collect do |v|
              convert_to_arrow_value(v)
            end
          end
          append_values(target_values, nil)
        else
          append_nulls(current_index - start_index)
        end
      end
    end

    def append_nulls(n)
      n.times do
        append_null
      end
    end
  end
end
