/* Copyright (c) 2026 The Khronos Group Inc.
 * Copyright (c) 2026 Valve Corporation
 * Copyright (c) 2026 LunarG, Inc.
 *
 * Licensed 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 "sync_access_map.h"

namespace syncval {

void AccessMap::Assign(const AccessMap& other) {
    auto temp_copy(other.impl_map_);
    impl_map_.swap(temp_copy);
}

AccessMap::iterator AccessMap::LowerBound(ResourceAddress range_begin) {
    auto it = impl_map_.lower_bound(AccessRange(range_begin, range_begin));
    return it;
}

AccessMap::const_iterator AccessMap::LowerBound(ResourceAddress range_begin) const {
    auto it = impl_map_.lower_bound(AccessRange(range_begin, range_begin));
    return it;
}

AccessMap::iterator AccessMap::Erase(const iterator& pos) {
    assert(pos != end());
    return impl_map_.erase(pos);
}

void AccessMap::Erase(iterator first, iterator last) {
    auto current = first;
    while (current != last) {
        assert(current != end());
        current = impl_map_.erase(current);
    }
}

AccessMap::iterator AccessMap::Insert(const_iterator hint, const AccessRange& range, const AccessState& access_state) {
    assert(range.non_empty());
    bool hint_open;
    const_iterator impl_next = hint;
    if (impl_map_.empty()) {
        hint_open = true;
    } else if (impl_next == impl_map_.cbegin()) {
        hint_open = range.strictly_less(impl_next->first);
    } else if (impl_next == impl_map_.cend()) {
        auto impl_prev = impl_next;
        --impl_prev;
        hint_open = range.strictly_greater(impl_prev->first);
    } else {
        auto impl_prev = impl_next;
        --impl_prev;
        hint_open = range.strictly_greater(impl_prev->first) && range.strictly_less(impl_next->first);
    }

    if (!hint_open) {
        // Hint was unhelpful, fall back to the non-hinted version
        auto plain_insert = Insert(range, access_state);
        return plain_insert.first;
    }

    auto impl_insert = impl_map_.insert(impl_next, {range, access_state});
    return iterator(impl_insert);
}

std::pair<AccessMap::iterator, bool> AccessMap::Insert(const AccessRange& range, const AccessState& access_state) {
    assert(range.non_empty());

    // Look for range conflicts (and an insertion point, which makes the lower_bound *not* wasted work)
    // we don't have to check upper if just check that lower doesn't intersect (which it would if lower != upper)
    auto lower = LowerBound(range.begin);
    if (lower == end() || !lower->first.intersects(range)) {
        // range is not even partially overlapped, and lower is strictly > than key
        return {impl_map_.emplace_hint(lower, range, access_state), true};
    }
    // We don't replace
    return {lower, false};
}

AccessMap::iterator AccessMap::InfillGap(const_iterator range_lower_bound, const AccessRange& range,
                                         const AccessState& access_state) {
    assert(LowerBound(range.begin) == range_lower_bound);
    assert(range_lower_bound == end() || range.strictly_less(range_lower_bound->first));
    return impl_map_.insert(range_lower_bound, {range, access_state});
}

void AccessMap::InfillGaps(const AccessRange& range, const AccessState& access_state) {
    AccessMapLocator pos(*this, range.begin);
    while (range.includes(pos.index)) {
        if (!pos.inside_lower_bound_range) {
            if (pos.lower_bound == end() || range.end <= pos.lower_bound->first.begin) {
                const AccessRange gap_range(pos.index, range.end);
                impl_map_.insert(pos.lower_bound, {gap_range, access_state});
                return;  // reached range.end
            } else {
                const AccessRange gap_range(pos.index, pos.lower_bound->first.begin);
                impl_map_.insert(pos.lower_bound, {gap_range, access_state});
                pos.Seek(pos.lower_bound->first.end);
            }
        } else {
            pos.Seek(pos.lower_bound->first.end);
        }
    }
}

AccessMap::iterator AccessMap::Split(const iterator split_it, const index_type& index) {
    const auto range = split_it->first;

    if (!range.includes(index)) {
        return split_it;  // If we don't have a valid split point, just return the iterator
    }

    AccessRange lower_range(range.begin, index);

    if (lower_range.empty()) {
        // This is a noop, we're keeping the upper half which is the same as split_it
        return split_it;
    }

    // Save the contents and erase
    auto value = split_it->second;
    auto next_it = impl_map_.erase(split_it);

    AccessRange upper_range(index, range.end);
    assert(!upper_range.empty());  // Upper range cannot be empty

    // Copy value to the upper range
    // NOTE: we insert from upper to lower because that's what emplace_hint can do in constant time
    assert(impl_map_.find(upper_range) == impl_map_.end());
    next_it = impl_map_.emplace_hint(next_it, std::make_pair(upper_range, value));

    // Move value to the lower range (we can move since the upper range already got a copy of value)
    assert(impl_map_.find(lower_range) == impl_map_.end());
    next_it = impl_map_.emplace_hint(next_it, std::make_pair(lower_range, std::move(value)));

    // Iterator to the beginning of the lower range
    return next_it;
}

AccessMap::iterator Split(AccessMap::iterator in, AccessMap& map, const AccessRange& range) {
    assert(in != map.end());  // Not designed for use with invalid iterators...
    const AccessRange in_range = in->first;
    const AccessRange split_range = in_range & range;

    if (split_range.empty()) {
        return map.end();
    }
    auto pos = in;
    if (split_range.begin != in_range.begin) {
        pos = map.Split(pos, split_range.begin);
        ++pos;
    }
    if (split_range.end != in_range.end) {
        pos = map.Split(pos, split_range.end);
    }
    return pos;
}

void Consolidate(AccessMap& map) {
    using It = AccessMap::iterator;

    It current = map.begin();
    const It map_end = map.end();

    // To be included in a merge range there must be no gap in the AccessRange space, and the mapped_type values must match
    auto can_merge = [](const It& last, const It& cur) {
        return cur->first.begin == last->first.end && cur->second == last->second;
    };

    while (current != map_end) {
        // Establish a trival merge range at the current location, advancing current. Merge range is inclusive of merge_last
        const It merge_first = current;
        It merge_last = current;
        ++current;

        // Expand the merge range as much as possible
        while (current != map_end && can_merge(merge_last, current)) {
            merge_last = current;
            ++current;
        }

        // Current isn't in the active merge range. If there is a non-trivial merge range, we resolve it here.
        if (merge_first != merge_last) {
            // IFF there is more than one range in (merge_first, merge_last)  <- again noting the *inclusive* last
            // Create a new Val spanning (first, last), substitute it for the multiple entries.

            const AccessRange merged_range(merge_first->first.begin, merge_last->first.end);
            AccessState access = merge_last->second;

            // Note that current points to merge_last + 1, and is valid even if at map_end for these operations
            map.Erase(merge_first, current);

            map.Insert(current, merged_range, std::move(access));
        }
    }
}

template <typename TAccessMap>
TAccessMapLocator<TAccessMap>::TAccessMapLocator(TAccessMap& map, index_type index) : map_(&map), index(index) {
    lower_bound = LowerBoundForIndex(index);
    inside_lower_bound_range = InsideLowerBoundRange();
}

template <typename TAccessMap>
TAccessMapLocator<TAccessMap>::TAccessMapLocator(TAccessMap& map, index_type index, const iterator& index_lower_bound)
    : map_(&map), index(index), lower_bound(index_lower_bound) {
    assert(LowerBoundForIndex(index) == index_lower_bound);
    inside_lower_bound_range = InsideLowerBoundRange();
}

template <typename TAccessMap>
void TAccessMapLocator<TAccessMap>::Seek(index_type seek_to) {
    if (TrySeekLocal(seek_to)) {
        return;
    }
    index = seek_to;
    lower_bound = LowerBoundForIndex(seek_to);  // Expensive part
    inside_lower_bound_range = InsideLowerBoundRange();
}

template <typename TAccessMap>
bool TAccessMapLocator<TAccessMap>::TrySeekLocal(index_type seek_to) {
    auto is_lower_than = [this](AccessMap::index_type index, const auto& it) { return it == map_->end() || index < it->first.end; };

    // Already here
    if (index == seek_to) {
        return true;
    }
    // The optimization is only for forward movement
    if (index < seek_to) {
        // Check if the current range is still a valid lower bound
        if (is_lower_than(seek_to, lower_bound)) {
            assert(lower_bound == LowerBoundForIndex(seek_to));
            index = seek_to;
            inside_lower_bound_range = InsideLowerBoundRange();
            return true;
        }
        // Check if the next range is a valid lower bound
        auto next_it = lower_bound;
        ++next_it;
        if (is_lower_than(seek_to, next_it)) {
            assert(next_it == LowerBoundForIndex(seek_to));
            index = seek_to;
            lower_bound = next_it;
            inside_lower_bound_range = InsideLowerBoundRange();
            return true;
        }
    }
    return false;  // Need to re-search lower bound
}

template <typename TAccessMap>
AccessMap::index_type TAccessMapLocator<TAccessMap>::DistanceToEdge() const {
    if (lower_bound == map_->end()) {
        return 0;
    }
    const index_type edge = inside_lower_bound_range ? lower_bound->first.end : lower_bound->first.begin;
    return edge - index;
}

// Explicit instantiation of const and non-const locators
template class TAccessMapLocator<AccessMap>;
template class TAccessMapLocator<const AccessMap>;

void ParallelIterator::OnCurrentRangeModified(const iterator& new_lower_bound) {
    // Only map A can be modified, map B is constant
    pos_A = AccessMapLocator(map_A_, range.begin, new_lower_bound);
    range.end = range.begin + ComputeDelta();
}

void ParallelIterator::SeekAfterModification(index_type index) {
    // Destination map locator must be reinitialized after modification.
    // Seek() (potentially more efficient) can only be used when there is no modification.
    pos_A = AccessMapLocator(map_A_, index);

    pos_B.Seek(index);
    range = AccessRange(index, index + ComputeDelta());
}

void ParallelIterator::NextRange() {
    const index_type start = range.end;
    const index_type delta = range.distance();
    assert(delta != 0);  // Trying to increment past end

    pos_A.Seek(pos_A.index + delta);
    pos_B.Seek(pos_B.index + delta);

    range = AccessRange(start, start + ComputeDelta());
    assert(pos_A.index == start);
    assert(pos_B.index == start);
}

ParallelIterator::index_type ParallelIterator::ComputeDelta() {
    const index_type delta_A = pos_A.DistanceToEdge();
    const index_type delta_B = pos_B.DistanceToEdge();

    // If either A or B are at end, there distance is *0*, so shouldn't be considered in the "distance to edge"
    if (delta_A == 0) {  // lower A is at end
        return delta_B;
    } else if (delta_B == 0) {  // lower B is at end
        return delta_A;
    } else {
        // Use the nearest edge, s.t. over this range A and B are both constant
        return std::min(delta_A, delta_B);
    }
}

}  // namespace syncval
