/*
 * 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 "paimon/common/io/offset_input_stream.h"

#include <utility>

#include "fmt/format.h"
#include "paimon/common/utils/math.h"
#include "paimon/macros.h"

namespace paimon {
Result<std::unique_ptr<OffsetInputStream>> OffsetInputStream::Create(
    const std::shared_ptr<InputStream>& wrapped, int64_t length, int64_t offset) {
    if (PAIMON_UNLIKELY(wrapped == nullptr)) {
        return Status::Invalid("input stream is null pointer");
    }
    PAIMON_ASSIGN_OR_RAISE(int64_t total_length, wrapped->Length());
    return Create(wrapped, length, offset, total_length);
}

Result<std::unique_ptr<OffsetInputStream>> OffsetInputStream::Create(
    const std::shared_ptr<InputStream>& wrapped, int64_t length, int64_t offset,
    int64_t total_length) {
    if (PAIMON_UNLIKELY(wrapped == nullptr)) {
        return Status::Invalid("input stream is null pointer");
    }
    PAIMON_RETURN_NOT_OK(ValidateValueNonNegative(offset, "offset"));
    PAIMON_RETURN_NOT_OK(ValidateValueNonNegative(length, "length"));
    PAIMON_RETURN_NOT_OK(ValidateValueNonNegative(total_length, "total length"));
    if (PAIMON_UNLIKELY(offset > total_length)) {
        return Status::Invalid(
            fmt::format("offset {} exceed total length {}", offset, total_length));
    }
    if (PAIMON_UNLIKELY(length > total_length - offset)) {
        return Status::Invalid(fmt::format("offset {} + length {} exceed total length {}", offset,
                                           length, total_length));
    }
    PAIMON_RETURN_NOT_OK(wrapped->Seek(offset, SeekOrigin::FS_SEEK_SET));
    return std::unique_ptr<OffsetInputStream>(new OffsetInputStream(wrapped, length, offset));
}

OffsetInputStream::OffsetInputStream(const std::shared_ptr<InputStream>& wrapped, int64_t length,
                                     int64_t offset)
    : wrapped_(wrapped), length_(length), offset_(offset) {}

Status OffsetInputStream::Seek(int64_t offset, SeekOrigin origin) {
    int64_t new_position = 0;
    switch (origin) {
        case SeekOrigin::FS_SEEK_SET: {
            new_position = offset;
            PAIMON_RETURN_NOT_OK(AssertBoundary(new_position));
            PAIMON_RETURN_NOT_OK(wrapped_->Seek(offset_ + new_position, SeekOrigin::FS_SEEK_SET));
            break;
        }
        case SeekOrigin::FS_SEEK_CUR: {
            new_position = inner_position_ + offset;
            PAIMON_RETURN_NOT_OK(AssertBoundary(new_position));
            PAIMON_RETURN_NOT_OK(wrapped_->Seek(offset, SeekOrigin::FS_SEEK_CUR));
            break;
        }
        case SeekOrigin::FS_SEEK_END: {
            new_position = length_ + offset;
            PAIMON_RETURN_NOT_OK(AssertBoundary(new_position));
            PAIMON_RETURN_NOT_OK(wrapped_->Seek(offset_ + new_position, SeekOrigin::FS_SEEK_SET));
            break;
        }
        default:
            return Status::Invalid(
                "invalid SeekOrigin, only support FS_SEEK_SET, FS_SEEK_CUR, and FS_SEEK_END");
    }
    inner_position_ = new_position;
    return Status::OK();
}

Result<int64_t> OffsetInputStream::Read(char* buffer, int64_t size) {
    PAIMON_RETURN_NOT_OK(AssertBoundary(inner_position_ + size));
    PAIMON_ASSIGN_OR_RAISE(int64_t actual_read_len, wrapped_->Read(buffer, size));
    inner_position_ += actual_read_len;
    return actual_read_len;
}

Result<int64_t> OffsetInputStream::Read(char* buffer, int64_t size, int64_t offset) {
    PAIMON_RETURN_NOT_OK(AssertBoundary(offset));
    PAIMON_RETURN_NOT_OK(AssertBoundary(offset + size));
    return wrapped_->Read(buffer, size, offset_ + offset);
}

void OffsetInputStream::ReadAsync(char* buffer, int64_t size, int64_t offset,
                                  std::function<void(Status)>&& callback) {
    auto status = AssertBoundary(offset);
    if (!status.ok()) {
        callback(status);
        return;
    }
    status = AssertBoundary(offset + size);
    if (!status.ok()) {
        callback(status);
        return;
    }
    wrapped_->ReadAsync(buffer, size, offset_ + offset, std::move(callback));
}

Status OffsetInputStream::Close() {
    return wrapped_->Close();
}

Result<std::string> OffsetInputStream::GetUri() const {
    return wrapped_->GetUri();
}

Result<int64_t> OffsetInputStream::GetPos() const {
    return inner_position_;
}

Result<int64_t> OffsetInputStream::Length() const {
    return length_;
}

Status OffsetInputStream::AssertBoundary(int64_t inner_pos) const {
    if (inner_pos < 0 || inner_pos > length_) {
        return Status::Invalid(
            fmt::format("OffsetInputStream assert boundary failed: inner pos {} exceed length {}",
                        inner_pos, length_));
    }
    return Status::OK();
}

}  // namespace paimon
