/**
 * 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 "core/htrace.h"
#include "core/htracer.h"
#include "core/scope.h"
#include "core/span.h"
#include "receiver/receiver.h"
#include "sampler/sampler.h"
#include "util/log.h"
#include "util/rand.h"
#include "util/string.h"
#include "util/time.h"

#include <inttypes.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

/**
 * @file scope.c
 *
 * Implementation of HTrace scopes.
 */

struct htrace_scope* htrace_start_span(struct htracer *tracer,
        struct htrace_sampler *sampler, const char *desc)
{
    struct htrace_scope *cur_scope, *scope = NULL, *pscope;
    struct htrace_span *span = NULL;
    struct htrace_span_id span_id;

    // Validate the description string.  This ensures that it doesn't have
    // anything silly in it like embedded double quotes, backslashes, or control
    // characters.
    if (!validate_json_string(tracer->lg, desc)) {
        htrace_log(tracer->lg, "htrace_span_alloc(desc=%s): invalid "
                   "description string.\n", desc);
        return NULL;
    }
    cur_scope = htracer_cur_scope(tracer);
    if ((!cur_scope) || (!cur_scope->span)) {
        if (!sampler->ty->next(sampler)) {
            return NULL;
        }
        htrace_span_id_generate(&span_id, tracer->rnd, NULL);
    } else {
        htrace_span_id_generate(&span_id, tracer->rnd,
                                &cur_scope->span->span_id);
    }
    span = htrace_span_alloc(desc, now_ms(tracer->lg), &span_id);
    if (!span) {
        htrace_log(tracer->lg, "htrace_span_alloc(desc=%s): OOM\n", desc);
        return NULL;
    }
    scope = malloc(sizeof(*scope));
    if (!scope) {
        htrace_span_free(span);
        htrace_log(tracer->lg, "htrace_start_span(desc=%s): OOM\n", desc);
        return NULL;
    }
    scope->tracer = tracer;
    scope->span = span;

    // Search enclosing trace scopes for the first one that hasn't disowned
    // its trace span.
    for (pscope = cur_scope; pscope; pscope = pscope->parent) {
        struct htrace_span *pspan = pscope->span;
        if (pspan) {
            span->parent.single = pspan->span_id;
            span->num_parents = 1;
            break;
        }
        pscope = pscope->parent;
    }
    if (htracer_push_scope(tracer, cur_scope, scope) != 0) {
        htrace_span_free(span);
        free(scope);
        return NULL;
    }
    return scope;
}

struct htrace_span *htrace_scope_detach(struct htrace_scope *scope)
{
    struct htrace_span *span = scope->span;

    if (span == NULL) {
        htrace_log(scope->tracer->lg, "htrace_scope_detach: attempted to "
                   "detach a scope which was already detached.\n");
        return NULL;
    }
    scope->span = NULL;
    return span;
}

struct htrace_scope* htrace_restart_span(struct htracer *tracer,
                                         struct htrace_span *span)
{
    struct htrace_scope *cur_scope, *scope = NULL;
    char buf[HTRACE_SPAN_ID_STRING_LENGTH + 1];

    scope = malloc(sizeof(*scope));
    if (!scope) {
        htrace_span_id_to_str(&span->span_id, buf, sizeof(buf));
        htrace_log(tracer->lg, "htrace_start_span(desc=%s, parent_id=%s"
                   "): OOM\n", span->desc, buf);
        htrace_span_free(span);
        return NULL;
    }
    scope->tracer = tracer;
    scope->parent = NULL;
    scope->span = span;
    cur_scope = htracer_cur_scope(tracer);
    if (htracer_push_scope(tracer, cur_scope, scope) != 0) {
        htrace_span_free(span);
        free(scope);
        return NULL;
    }
    return scope;
}

void htrace_scope_get_span_id(const struct htrace_scope *scope,
                              struct htrace_span_id *id)
{
    struct htrace_span *span;

    if (!scope) {
        htrace_span_id_clear(id);
        return;
    }
    span = scope->span;
    if (!span) {
        htrace_span_id_clear(id);
        return;
    }
    htrace_span_id_copy(id, &span->span_id);
}

void htrace_scope_close(struct htrace_scope *scope)
{
    struct htracer *tracer;

    if (!scope) {
        return;
    }
    tracer = scope->tracer;
    if (htracer_pop_scope(tracer, scope) == 0) {
        struct htrace_span *span = scope->span;
        if (span) {
            struct htrace_rcv *rcv = tracer->rcv;
            span->end_ms = now_ms(tracer->lg);
            rcv->ty->add_span(rcv, span);
            htrace_span_free(span);
        }
        free(scope);
    }
}

// vim:ts=4:sw=4:et
