// 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 "exec/pl_task_root.h"

namespace doris {

ExchangeNode::ExchangeNode(
        ObjectPool* pool, 
        const TPlanNode& tnode,
        const DescriptorTbl& descs) : 
            ExecNode(pool, tnode, descs),
            _num_senders(0),
            _stream_recvr(NULL),
            _next_row_idx(0) {
}

ExchangeNode::~ExchangeNode() {
}

Status ExchangeNode::init(const TPlanNode& tnode, RuntimeState* state) {
    return ExecNode::init(tnode, state);
}

Status ExchangeNode::prepare(RuntimeState* state) {
    RETURN_IF_ERROR(ExecNode::prepare(state));

    _convert_row_batch_timer = ADD_TIMER(runtime_profile(), "ConvertRowBatchTime");

    // TODO: figure out appropriate buffer size
    DCHECK_GT(_num_senders, 0);
    _stream_recvr = state->create_recvr(_row_descriptor, _id, _num_senders,
                                       config::exchg_node_buffer_size_bytes, runtime_profile());
    return Status::OK;
}

Status ExchangeNode::open(RuntimeState* state) {
    SCOPED_TIMER(_runtime_profile->total_time_counter());
    RETURN_IF_ERROR(ExecNode::open(state));
    return Status::OK;
}

Status ExchangeNode::close(RuntimeState* state) {
    if (is_closed()) {
        return Status::OK;
    }
    return ExecNode::close(state);
}

Status ExchangeNode::get_next(RuntimeState* state, RowBatch* output_batch, bool* eos) {
    RETURN_IF_ERROR(exec_debug_action(TExecNodePhase::GETNEXT));
    SCOPED_TIMER(_runtime_profile->total_time_counter());

    if (reached_limit()) {
        *eos = true;
        return Status::OK;
    }

    ExprContext* const* ctxs = &_conjunct_ctxs[0];
    int num_ctxs = _conjunct_ctxs.size();

    while (true) {
        {
            SCOPED_TIMER(_convert_row_batch_timer);

            // copy rows until we hit the limit/capacity or until we exhaust _input_batch
            while (!reached_limit() && !output_batch->is_full()
                    && _input_batch.get() != NULL && _next_row_idx < _input_batch->capacity()) {
                TupleRow* src = _input_batch->get_row(_next_row_idx);

                if (ExecNode::eval_conjuncts(ctxs, num_ctxs, src)) {
                    int j = output_batch->add_row();
                    TupleRow* dest = output_batch->get_row(j);
                    // if the input row is shorter than the output row, make sure not to leave
                    // uninitialized Tuple* around
                    output_batch->clear_row(dest);
                    // this works as expected if rows from input_batch form a prefix of
                    // rows in output_batch
                    _input_batch->copy_row(src, dest);
                    output_batch->commit_last_row();
                    ++_num_rows_returned;
                }

                ++_next_row_idx;
            }

            COUNTER_SET(_rows_returned_counter, _num_rows_returned);

            if (reached_limit()) {
                *eos = true;
                return Status::OK;
            }

            if (output_batch->is_full()) {
                *eos = false;
                return Status::OK;
            }
        }

        // we need more rows
        if (_input_batch.get() != NULL) {
            _input_batch->transfer_resource_ownership(output_batch);
        }

        bool is_cancelled = true;
        _input_batch.reset(_stream_recvr->get_batch(&is_cancelled));
        VLOG_FILE << "exch: has batch=" << (_input_batch.get() == NULL ? "false" : "true")
                  << " #rows=" << (_input_batch.get() != NULL ? _input_batch->num_rows() : 0)
                  << " is_cancelled=" << (is_cancelled ? "true" : "false")
                  << " instance_id=" << state->fragment_instance_id();

        if (is_cancelled) {
            return Status::CANCELLED;
        }

        *eos = (_input_batch.get() == NULL);

        if (*eos) {
            return Status::OK;
        }

        _next_row_idx = 0;
        DCHECK(_input_batch->row_desc().is_prefix_of(output_batch->row_desc()));
    }
}

void ExchangeNode::debug_string(int indentation_level, std::stringstream* out) const {
    *out << string(indentation_level * 2, ' ');
    *out << "ExchangeNode(#senders=" << _num_senders;
    ExecNode::debug_string(indentation_level, out);
    *out << ")";
}

}

