/*
// Copyright (c) 2016 Intel Corporation
//
// 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 <stddef.h>

#include "os/mynewt.h"

#include "oic/port/mynewt/config.h"
#include "messaging/coap/engine.h"
#include "oic/messaging/coap/oc_coap.h"
#include "oic/messaging/coap/separate.h"
#include "oic/oc_api.h"
#include "oic/oc_constants.h"
#include "oic/oc_core_res.h"

extern int oc_stack_errno;
// TODO:
// 0x01: Couldnt add platform
// 0x02: Couldnt add device
// 0x03: CBOR error

void
oc_add_device(const char *uri, const char *rt, const char *name,
              const char *spec_version, const char *data_model_version,
              oc_add_device_cb_t add_device_cb, void *data)
{
  oc_string_t *payload;

  payload = oc_core_add_new_device(uri, rt, name, spec_version,
                                   data_model_version, add_device_cb, data);
  if (!payload)
    oc_stack_errno |= 0x02;
}

void
oc_init_platform(const char *mfg_name, oc_init_platform_cb_t init_platform_cb,
                 void *data)
{
  oc_string_t *payload;

  payload = oc_core_init_platform(mfg_name, init_platform_cb, data);
  if (!payload)
    oc_stack_errno |= 0x01;
}

int
oc_get_query_value(oc_request_t *request, const char *key, char **value)
{
  return oc_ri_get_query_value(request->query, request->query_len, key, value);
}

static int
response_length(void)
{
  int size = oc_rep_finalize();
  return (size <= 2) ? 0 : size;
}

void
oc_send_response(oc_request_t *request, oc_status_t response_code)
{
  // FIX:: set errno if CBOR encoding failed.
  request->response->response_buffer->response_length = response_length();
  request->response->response_buffer->code = oc_status_code(response_code);
}

void
oc_ignore_request(oc_request_t *request)
{
  request->response->response_buffer->code = OC_IGNORE;
}

void
oc_process_baseline_interface(oc_resource_t *resource)
{
  oc_rep_set_string_array(root, rt, resource->types);
  oc_core_encode_interfaces_mask(oc_rep_object(root), resource->interfaces);
  oc_rep_set_uint(root, p, resource->properties & ~OC_PERIODIC);
}

#ifdef OC_SERVER
static int query_iterator;

// FIX: validate uri
oc_resource_t *
oc_new_resource(const char *uri, uint8_t num_resource_types, int device)
{
  oc_resource_t *resource;
  const char *start = uri;
  size_t end = strlen(uri);

  resource = oc_ri_alloc_resource();
  assert(resource);

  oc_alloc_string(&resource->uri, end + 1);
  strncpy((char *)oc_string(resource->uri), start, end);
  strcpy((char *)oc_string(resource->uri) + end, (const char *)"");
  oc_new_string_array(&resource->types, num_resource_types);
  resource->interfaces = OC_IF_BASELINE;
  resource->default_interface = OC_IF_BASELINE;
  resource->observe_period_mseconds = 0;
  resource->properties = OC_ACTIVE;
  resource->num_observers = 0;
  resource->device = device;
  return resource;
}

void
oc_resource_bind_resource_interface(oc_resource_t *resource, uint8_t interface)
{
  resource->interfaces |= interface;
}

void
oc_resource_set_default_interface(oc_resource_t *resource,
                                  oc_interface_mask_t interface)
{
  resource->default_interface = interface;
}

void
oc_resource_bind_resource_type(oc_resource_t *resource, const char *type)
{
  oc_string_array_add_item(resource->types, (char *)type);
}

#ifdef OC_SECURITY
void
oc_resource_make_secure(oc_resource_t *resource)
{
  resource->properties |= OC_SECURE;
}
#endif /* OC_SECURITY */

#if MYNEWT_VAL(OC_TRANS_SECURITY)
/**
 * Configures the specified resource with a minimum set of transport layer
 * security requirements.
 *
 * @param resource              The resource to configure.
 * @param enc                   Whether transport layer encryption is required.
 * @param auth                  Whether transport layer authentication is
 *                                  required.
 */
void
oc_resource_set_trans_security(oc_resource_t *resource, bool enc, bool auth)
{
    if (enc) {
        resource->properties |= OC_TRANS_ENC;
    } else {
        resource->properties &= ~OC_TRANS_ENC;
    }

    if (auth) {
        resource->properties |= OC_TRANS_AUTH;
    } else {
        resource->properties &= ~OC_TRANS_AUTH;
    }
}
#endif

void
oc_resource_set_discoverable(oc_resource_t *resource)
{
  resource->properties |= OC_DISCOVERABLE;
}

void
oc_resource_set_observable(oc_resource_t *resource)
{
  resource->properties |= OC_OBSERVABLE;
}

void
oc_resource_set_periodic_observable_ms(oc_resource_t *resource, uint32_t mseconds)
{
  resource->properties |= OC_OBSERVABLE | OC_PERIODIC;
  resource->observe_period_mseconds = mseconds;
}

void
oc_resource_set_periodic_observable(oc_resource_t *resource, uint16_t seconds)
{
  resource->properties |= OC_OBSERVABLE | OC_PERIODIC;
  resource->observe_period_mseconds = seconds * 1000;
}

void
oc_deactivate_resource(oc_resource_t *resource)
{
  resource->properties ^= OC_ACTIVE;
}

void
oc_resource_set_request_handler(oc_resource_t *resource, oc_method_t method,
                                oc_request_handler_t handler)
{
  switch (method) {
  case OC_GET:
    resource->get_handler = handler;
    break;
  case OC_POST:
    resource->post_handler = handler;
    break;
  case OC_PUT:
    resource->put_handler = handler;
    break;
  case OC_DELETE:
    resource->delete_handler = handler;
    break;
  default:
    break;
  }
}

bool
oc_add_resource(oc_resource_t *resource)
{
  return oc_ri_add_resource(resource);
}

void
oc_delete_resource(oc_resource_t *resource)
{
  oc_ri_delete_resource(resource);
}

void
oc_init_query_iterator(oc_request_t *request)
{
  query_iterator = 0;
}

int
oc_interate_query(oc_request_t *request, char **key, int *key_len, char **value,
                  int *value_len)
{
  if (query_iterator >= request->query_len)
    return -1;
  query_iterator += oc_ri_get_query_nth_key_value(
    request->query + query_iterator, request->query_len - query_iterator, key,
    key_len, value, value_len, 1);
  return 1;
}

#if MYNEWT_VAL(OC_SEPARATE_RESPONSES)
void
oc_indicate_separate_response(oc_request_t *request,
                              oc_separate_response_t *response)
{
    request->response->separate_response = response;
    oc_send_response(request, OC_STATUS_OK);
}

void
oc_set_separate_response_buffer(oc_separate_response_t *handle)
{
    assert(handle->buffer);
    oc_rep_new(handle->buffer); // check
}

void
oc_send_separate_response(oc_separate_response_t *handle,
                          oc_status_t response_code)
{
    oc_response_buffer_t response_buffer;
    coap_separate_t *cur, *next = NULL;
    coap_packet_t response[1];
    coap_transaction_t *t;

    response_buffer.buffer = handle->buffer;
    response_buffer.response_length = response_length();
    response_buffer.code = oc_status_code(response_code);

    for (cur = SLIST_FIRST(&handle->requests); cur; cur = next) {
        next = SLIST_NEXT(cur, next);
        if (cur->observe > 0) {
            t = coap_new_transaction(coap_get_mid(), &cur->endpoint);
            if (t) {
                coap_separate_resume(response, cur,
                  oc_status_code(response_code), t->mid);
                coap_set_header_content_format(response, APPLICATION_CBOR);
                if (cur->observe == 1) {
                    coap_set_header_observe(response, 1);
                }
                if (response_buffer.response_length > 0) {
                    coap_set_payload(response, handle->buffer,
                      response_buffer.response_length);
                }
                t->type = response->type;
                if (!coap_serialize_message(response, t->m)) {
                    coap_send_transaction(t);
                } else {
                    coap_clear_transaction(t);
                }
            }
            coap_separate_clear(handle, cur);
        } else {
            if (coap_notify_observers(NULL, &response_buffer,
                &cur->endpoint) == 0) {
                coap_separate_clear(handle, cur);
            }
        }
    }
    if (SLIST_FIRST(&handle->requests) == NULL) {
        handle->active = 0;
    }
}
#endif
int
oc_notify_observers(oc_resource_t *resource)
{
  return coap_notify_observers(resource, NULL, NULL);
}
#endif /* OC_SERVER */
