/*
 * 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.
 */

#include "converters.hpp"

namespace red_arrow {
  namespace {
    class ValuesBuilder : private Converter, public arrow::ArrayVisitor {
    public:
      explicit ValuesBuilder(VALUE values)
        : Converter(),
          values_(values),
          row_offset_(0) {
      }

      void build(const arrow::Array& array, VALUE rb_array) {
        rb::protect([&] {
          check_status(array.Accept(this),
                       "[array][values]");
          return Qnil;
        });
      }

      void build(const arrow::ChunkedArray& chunked_array,
                 VALUE rb_chunked_array) {
        rb::protect([&] {
          for (const auto& array : chunked_array.chunks()) {
            check_status(array->Accept(this),
                         "[chunked-array][values]");
            row_offset_ += array->length();
          }
          return Qnil;
        });
      }

#define VISIT(TYPE)                                                     \
      arrow::Status Visit(const arrow::TYPE ## Array& array) override { \
        convert(array);                                                 \
        return arrow::Status::OK();                                     \
      }

      VISIT(Null)
      VISIT(Boolean)
      VISIT(Int8)
      VISIT(Int16)
      VISIT(Int32)
      VISIT(Int64)
      VISIT(UInt8)
      VISIT(UInt16)
      VISIT(UInt32)
      VISIT(UInt64)
      VISIT(HalfFloat)
      VISIT(Float)
      VISIT(Double)
      VISIT(Binary)
      VISIT(String)
      VISIT(FixedSizeBinary)
      VISIT(Date32)
      VISIT(Date64)
      VISIT(Time32)
      VISIT(Time64)
      VISIT(Timestamp)
      VISIT(MonthInterval)
      VISIT(DayTimeInterval)
      VISIT(MonthDayNanoInterval)
      VISIT(List)
      VISIT(LargeList)
      VISIT(Struct)
      VISIT(Map)
      VISIT(SparseUnion)
      VISIT(DenseUnion)
      VISIT(Dictionary)
      VISIT(Decimal128)
      VISIT(Decimal256)
      // TODO
      // VISIT(Extension)

#undef VISIT

    private:
      template <typename ArrayType>
      void convert(const ArrayType& array) {
        const auto n = array.length();
        if (array.null_count() > 0) {
          for (int64_t i = 0, ii = row_offset_; i < n; ++i, ++ii) {
            auto value = Qnil;
            if (!array.IsNull(i)) {
              value = convert_value(array, i);
            }
            rb_ary_store(values_, ii, value);
          }
        } else {
          for (int64_t i = 0, ii = row_offset_; i < n; ++i, ++ii) {
            rb_ary_store(values_, ii, convert_value(array, i));
          }
        }
      }

      // Destination for converted values.
      VALUE values_;

      // The current row offset.
      int64_t row_offset_;
    };
  }

  VALUE
  array_values(VALUE rb_array) {
    auto garrow_array = GARROW_ARRAY(RVAL2GOBJ(rb_array));
    auto array = garrow_array_get_raw(garrow_array).get();
    const auto n_rows = array->length();
    auto values = rb_ary_new_capa(n_rows);

    try {
      ValuesBuilder builder(values);
      builder.build(*array, rb_array);
    } catch (rb::State& state) {
      state.jump();
    }

    return values;
  }

  VALUE
  chunked_array_values(VALUE rb_chunked_array) {
    auto garrow_chunked_array =
      GARROW_CHUNKED_ARRAY(RVAL2GOBJ(rb_chunked_array));
    auto chunked_array =
      garrow_chunked_array_get_raw(garrow_chunked_array).get();
    const auto n_rows = chunked_array->length();
    auto values = rb_ary_new_capa(n_rows);

    try {
      ValuesBuilder builder(values);
      builder.build(*chunked_array, rb_chunked_array);
    } catch (rb::State& state) {
      state.jump();
    }

    return values;
  }
}
