/*
 *
  ***** BEGIN LICENSE BLOCK *****
 
  Copyright (C) 2009-2019 Olof Hagsand
  Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
  Copyright (C) 2021 Siklu Ltd (YANG patch code)

  This file is part of CLIXON.

  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.

  Alternatively, the contents of this file may be used under the terms of
  the GNU General Public License Version 3 or later (the "GPL"),
  in which case the provisions of the GPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of the GPL, and not to allow others to
  use your version of this file under the terms of Apache License version 2, 
  indicate your decision by deleting the provisions above and replace them with
  the  notice and other provisions required by the GPL. If you do not delete
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the Apache License version 2 or the GPL.

  ***** END LICENSE BLOCK *****
  
 * Restconf YANG PATCH implementation (RFC8072)
 */


#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <time.h>
#include <signal.h>
#include <limits.h>
#include <sys/time.h>
#include <sys/wait.h>

/* cligen */
#include <cligen/cligen.h>

/* clicon */
#include <clixon/clixon.h>

#include "restconf_lib.h"
#include "restconf_handle.h"
#include "restconf_api.h"
#include "restconf_err.h"
#include "restconf_methods.h"
#include "restconf_methods_post.h"
#include "restconf_methods_patch.h"

#ifdef CLIXON_YANG_PATCH

enum yang_patch_op{
    YANG_PATCH_OP_CREATE,
    YANG_PATCH_OP_DELETE,
    YANG_PATCH_OP_INSERT,
    YANG_PATCH_OP_MERGE,
    YANG_PATCH_OP_MOVE,
    YANG_PATCH_OP_REPLACE,
    YANG_PATCH_OP_REMOVE
};
typedef enum yang_patch_op yang_patch_op_t;

/* Yang patch operations according to RFC 8072
 */
static const map_str2int yang_patch_op_map[] = {
    {"create",     YANG_PATCH_OP_CREATE},
    {"delete",     YANG_PATCH_OP_DELETE},
    {"insert",     YANG_PATCH_OP_INSERT},
    {"merge",      YANG_PATCH_OP_MERGE},
    {"move",       YANG_PATCH_OP_MOVE},
    {"replace",    YANG_PATCH_OP_REPLACE},
    {"remove",     YANG_PATCH_OP_REMOVE},
    {NULL,         -1}
};

static const yang_patch_op_t
yang_patch_op2int(char *op)
{
    return clicon_str2int(yang_patch_op_map, op);
}

/*! Add square brackets after the surrounding curly brackets in JSON
  * Needed, in order to modify the result of clixon_json2cbuf() to be valid input
  * to api_data_post() and api_data_write()
  * @param[in]  x_simple_patch  a cxobj to pass to clixon_json2cbuf()
  * @retva      cbuf            With the modified json
  * @retva      NULL            Error
  */
static cbuf*
yang_patch_xml2json_modified_cbuf(cxobj *x_simple_patch)
{
    cbuf *json_simple_patch = NULL;
    cbuf *cb = NULL;
    char *json_simple_patch_tmp;
    int   brace_count = 0;
    size_t len;
        
    json_simple_patch = cbuf_new();
    if (json_simple_patch == NULL)
        return NULL;
    cb = cbuf_new();
    if (clixon_json2cbuf(cb, x_simple_patch, 0, 0) < 0)
        goto done;

    // Insert a '[' after the first '{' to get the JSON to match what api_data_post/write() expect
    json_simple_patch_tmp = cbuf_get(cb);
    len = strlen(json_simple_patch_tmp);
    for (int l = 0; l < len; l++) {
        char c = json_simple_patch_tmp[l];
        if (c == '{') {
            brace_count++;
            if (brace_count == 2) { // We've reached the second brace, insert a '[' before it
                cbuf_append(json_simple_patch,(int)'[');
            }
        }
        cbuf_append(json_simple_patch,(int)c);
    }

    // Insert a ']' before the last '}' to get the JSON to match what api_data_post() expects
    for (int l = cbuf_len(json_simple_patch) - 1; l >= 0; l--) {
        char c = cbuf_get(json_simple_patch)[l];
        if (c == '}') {
            // Truncate and add a string, as there is not a function to insert a char into a cbuf
            cbuf_trunc(json_simple_patch, l);
            cbuf_append_str(json_simple_patch, "]}");
            break;
        }
    }
    cbuf_free(cb);
    return json_simple_patch;
}

/*!yang_patch_strip_after_last_slash 
 *
 * Strip /... from end  of val
 * so that e.g. "/interface=eth2" becomes "/"
 * or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/"
 *
 * @param[in]  val     value to strip
 * @retval     cbuf    with the stripped string
 * @retval     NULL    error
 */
static cbuf*
yang_patch_strip_after_last_slash(char* val)
{
    cbuf *cb;
    cbuf *val_tmp;
    int   idx;
    
    cb = cbuf_new();
    val_tmp = cbuf_new();
    cbuf_append_str(val_tmp, val);
    idx = cbuf_len(val_tmp);
    for (int l = cbuf_len(val_tmp) - 1; l>= 0; l--) {
        if (cbuf_get(val_tmp)[l] == '/') {
            idx = l;
            break;
        }
    }
    if (idx == cbuf_len(val_tmp)) // Didn't find a slash in the loop above
        return NULL;
    cbuf_trunc(val_tmp, idx + 1);
    if (cbuf_append_str(cb, cbuf_get(val_tmp)) < 0)
        return NULL;
    cbuf_free(val_tmp);
    return cb;
}

/*! YANG PATCH replace method
 * @param[in]  h         Clixon handle
 * @param[in]  req       Generic Www handle
 * @param[in]  pi        Offset, where to start api-path
 * @param[in]  qvec      Vector of query string (QUERY_STRING)
 * @param[in]  pretty    Set to 1 for pretty-printed xml/json output
 * @param[in]  media_out Output media
 * @param[in]  ds       0 if "data" resource, 1 if rfc8527 "ds" resource
 * @param[in]  simplepatch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
 * @param[in]  target_val       value in "target" field of edit in YANG patch
 * @param[in]  value_vec_len    number of elements in the "value" array of an edit in YANG patch
 * @param[in]  value_vec        pointer to the "value" array of an edit in YANG patch
 * @param[in]  x_simple_patch   pointer to XML containing module name, e.g. <ietf-interfaces:interface/>
 */
static int
yang_patch_do_replace(clicon_handle  h,
                      void          *req,
                      int            pi,
                      cvec          *qvec,
                      int            pretty,
                      restconf_media media_out,
                      ietf_ds_t      ds,
                      cbuf          *simple_patch_request_uri,
                      char          *target_val,       
                      int            value_vec_len,
                      cxobj        **value_vec,
                      cxobj         *x_simple_patch
                      )
{
    int    retval = -1;
    cxobj *value_vec_tmp = NULL;
    cbuf  *delete_req_uri = NULL;
    cbuf  *post_req_uri = NULL;
    cbuf  *json_simple_patch = NULL;
    
    if ((delete_req_uri = cbuf_new()) == NULL){
        clicon_err(OE_UNIX, errno, "cbuf_new");
        goto done;
    }
    if ((json_simple_patch = cbuf_new()) == NULL){
        clicon_err(OE_UNIX, errno, "cbuf_new");
        goto done;
    }
    // Make delete_req_uri something like "/restconf/data/ietf-interfaces:interfaces"
    if (cbuf_append_str(delete_req_uri, cbuf_get(simple_patch_request_uri)) < 0){
        clicon_err(OE_UNIX, errno, "cbuf_append_str");
        goto done;
    }

    // Add the target to delete_req_uri,
    // so it's something like "/restconf/data/ietf-interfaces:interfaces/interface=eth2"
    if (cbuf_append_str(delete_req_uri, target_val) <  0){
        clicon_err(OE_UNIX, errno, "cbuf_append_str");
        goto done;
    }

    // Delete the object with the old values
    if (api_data_delete(h, req, cbuf_get(delete_req_uri), pi, pretty, YANG_DATA_JSON, ds) < 0)
        goto done;

    // Now set up for the post request.
    // Strip /... from end  of target val
    // so that e.g. "/interface=eth2" becomes "/"
    // or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/"
    if ((post_req_uri = yang_patch_strip_after_last_slash(target_val)) == NULL)
        goto done;

    // Make post_req_uri something like "/restconf/data/ietf-interfaces:interfaces"
    if (cbuf_append_str(simple_patch_request_uri, cbuf_get(post_req_uri)) < 0){
        clicon_err(OE_UNIX, errno, "cbuf_append_str");
        goto done;
    }
    // Now insert the new values into the data
    // (which will include the key value and all other mandatory values)
    for (int k = 0; k < value_vec_len; k++) {
        if (value_vec[k] != NULL) {
            value_vec_tmp = xml_dup(value_vec[k]);
            xml_addsub(x_simple_patch, value_vec_tmp);
        }
    }
    // Convert the data to json
    if (clixon_json2cbuf(json_simple_patch, x_simple_patch, 0, 0) < 0)
        goto done;

    // Send the POST request
    if (api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, ds ) < 0)
        goto done;
    retval = 0;
 done:
    if (post_req_uri)
        cbuf_free(post_req_uri);
    if (delete_req_uri)
        cbuf_free(delete_req_uri);
    if (json_simple_patch)
        cbuf_free(json_simple_patch);
    if (value_vec_tmp)
        xml_free(value_vec_tmp);
    return retval;
}

/*! YANG PATCH create method
 * @param[in]  h         Clixon handle
 * @param[in]  req       Generic Www handle
 * @param[in]  pi        Offset, where to start api-path
 * @param[in]  qvec      Vector of query string (QUERY_STRING)
 * @param[in]  pretty    Set to 1 for pretty-printed xml/json output
 * @param[in]  media_out Output media
 * @param[in]  ds       0 if "data" resource, 1 if rfc8527 "ds" resource
 * @param[in]  simplepatch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
 * @param[in]  value_vec_len    number of elements in the "value" array of an edit in YANG patch
 * @param[in]  value_vec        pointer to the "value" array of an edit in YANG patch
 * @param[in]  x_simple_patch   pointer to XML containing module name, e.g. <ietf-interfaces:interface/>
 */
static int
yang_patch_do_create(clicon_handle  h,
                     void          *req,
                     int            pi,
                     cvec          *qvec,
                     int            pretty,
                     restconf_media media_out,
                     ietf_ds_t      ds,
                     cbuf          *simple_patch_request_uri,
                     int            value_vec_len,
                     cxobj        **value_vec,
                     cxobj         *x_simple_patch
                     )
{
    int    retval = -1;
    cxobj *value_vec_tmp = NULL;
    cbuf  *cb = NULL;
    
    // Send the POST request
    if ((cb = cbuf_new()) == NULL){
        clicon_err(OE_UNIX, errno, "cbuf_new");
        goto done;
    }
    for (int k = 0; k < value_vec_len; k++) {
        if (value_vec[k] != NULL) {
            if ((value_vec_tmp = xml_dup(value_vec[k])) == NULL)
                goto done;
            xml_addsub(x_simple_patch, value_vec_tmp);
        }
    }
    if (clixon_json2cbuf(cb, x_simple_patch, 0, 0) < 0)
        goto done;
    if (api_data_post(h, req, cbuf_get(simple_patch_request_uri),
                      pi, qvec,
                      cbuf_get(cb), pretty, YANG_DATA_JSON, media_out, ds) < 0)
        goto done;
     retval = 0;
 done:
     if (cb)
         cbuf_free(cb);
    return retval;
}

/*! YANG PATCH insert method
 * @param[in]  h         Clixon handle
 * @param[in]  req       Generic Www handle
 * @param[in]  pi        Offset, where to start api-path
 * @param[in]  pretty    Set to 1 for pretty-printed xml/json output
 * @param[in]  media_out Output media
 * @param[in]  ds       0 if "data" resource, 1 if rfc8527 "ds" resource
 * @param[in]  simple_patch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
 * @param[in]  value_vec_len    number of elements in the "value" array of an edit in YANG patch
 * @param[in]  value_vec        pointer to the "value" array of an edit in YANG patch
 * @param[in]  x_simple_patch   pointer to XML containing module name, e.g. <ietf-interfaces:interface/>
 * @param[in]  where_val       value in "where" field of edit in YANG patch
 * @param[in]  api_path        full API path, e.g. "/restconf/data/example-jukebox:jukebox/playlist=Foo-One" 
 * @param[in]  point_val       value in "point" field of edit in YANG patch
 */
static int
yang_patch_do_insert(clicon_handle  h,
                     void          *req,
                     int            pi,
                     int            pretty,
                     restconf_media media_out,
                     ietf_ds_t      ds,
                     cbuf          *simple_patch_request_uri,
                     int            value_vec_len,
                     cxobj        **value_vec,
                     cxobj         *x_simple_patch,
                     char          *where_val,
                     char          *api_path,
                     char          *point_val
                     )
{
    int     retval = -1;
    cxobj  *value_vec_tmp = NULL;
    cbuf   *json_simple_patch;
    cg_var *cv;
    cbuf   *point_str = NULL;
    cvec   *qvec_tmp = NULL;
    
    if ((point_str = cbuf_new()) == NULL){
        clicon_err(OE_UNIX, errno, "cbuf_new");
        goto done;
    }
    if ((qvec_tmp = cvec_new(0)) == NULL){
        clicon_err(OE_UNIX, errno, "cbuf_new");
        goto done;
    }
    // Loop through the XML, and get each value
    for (int k = 0; k < value_vec_len; k++) {
        if (value_vec[k] != NULL) {
            value_vec_tmp = xml_dup(value_vec[k]);
            xml_addsub(x_simple_patch, value_vec_tmp);
        }
    }
    if ((json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch)) == NULL)
        goto done;

    // Set the insert attributes
    if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){
        clicon_err(OE_UNIX, errno, "cvec_add");
        goto done;
    }
    cv_name_set(cv, "insert");
    if (where_val)
        cv_string_set(cv, where_val);
    cbuf_append_str(point_str, api_path);
    if (point_val)
        cbuf_append_str(point_str, point_val);
    if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){
        clicon_err(OE_UNIX, errno, "cvec_add");
        goto done;
    }
    cv_name_set(cv, "point");
    cv_string_set(cv, cbuf_get(point_str));

    // Send the POST request
    if (api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec_tmp, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, ds)<  0)
        goto done;
    retval = 0;
 done:
    if (qvec_tmp)
        cvec_free(qvec_tmp);
    if (point_str)
        cbuf_free(point_str);
    if (json_simple_patch)
        cbuf_free(json_simple_patch);
    return retval;
}

/*! YANG PATCH merge method
 * @param[in]  h         Clixon handle
 * @param[in]  req       Generic Www handle
 * @param[in]  pi        Offset, where to start api-path
 * @param[in]  qvec      Vector of query string (QUERY_STRING)
 * @param[in]  pretty    Set to 1 for pretty-printed xml/json output
 * @param[in]  media_out Output media
 * @param[in]  ds       0 if "data" resource, 1 if rfc8527 "ds" resource
 * @param[in]  simple_patch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
 * @param[in]  value_vec_len    number of elements in the "value" array of an edit in YANG patch
 * @param[in]  value_vec        pointer to the "value" array of an edit in YANG patch
 * @param[in]  x_simple_patch   pointer to XML containing module name, e.g. "<ietf-interfaces:interface/>"
 * @param[in]  where_val       value in "where" field of edit in YANG patch
 * @param[in]  key_xn          XML with key tag and value, e.g. "<name>Foo-One</name>"
 */
static int
yang_patch_do_merge(clicon_handle  h,
                    void          *req,
                    int            pi,
                    cvec          *qvec,
                    int            pretty,
                    restconf_media media_out,
                    ietf_ds_t      ds,
                    cbuf          *simple_patch_request_uri,
                    int            value_vec_len,
                    cxobj        **value_vec,
                    cxobj         *x_simple_patch,
                    cxobj         *key_xn
                    )
{
    int    retval = -1;
    cxobj *value_vec_tmp = NULL;
    cbuf  *cb = NULL;
    cbuf  *json_simple_patch = NULL;
        
    if ((cb = cbuf_new()) == NULL){
        clicon_err(OE_UNIX, errno, "cbuf_new");
        goto done;
    }
    if (key_xn != NULL)
        xml_addsub(x_simple_patch, key_xn);
    
    // Loop through the XML, create JSON from each one, and submit a simple patch
    for (int k = 0; k < value_vec_len; k++) {
        if (value_vec[k] != NULL) {
            value_vec_tmp = xml_dup(value_vec[k]);
            xml_addsub(x_simple_patch, value_vec_tmp);
        }
        cbuf_reset(cb); /* reuse cb */
        if (clixon_json2cbuf(cb, x_simple_patch, 0, 0) < 0)
            goto done;

        if ((json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch)) == NULL)
            goto done;
        // Send the simple patch request
        if (api_data_write(h, req, cbuf_get(simple_patch_request_uri), pi, qvec, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, 1, ds ) < 0)
            goto done;
        if (json_simple_patch){
            cbuf_free(json_simple_patch);
            json_simple_patch = NULL;
        }
    }
    retval = 0;
 done:
    if (cb)
        cbuf_free(cb);
    if (json_simple_patch)
        cbuf_free(json_simple_patch);
    return retval;
}

/*! Process a single yang patch "edit/value" element
 *
 * @param[in]  xn        XML value element
 * @retval     0         OK
 * @retval    -1         Error
 */
static int
yang_patch_do_value(clicon_handle  h,
                    void          *req,
                    int            pi,
                    cvec          *qvec,
                    int            pretty,
                    restconf_media media_out,
                    ietf_ds_t      ds,
                    cxobj         *xn,
                    char          *modname,
                    yang_patch_op_t operation,
                    char          *where_val,
                    char          *point_val,
                    cbuf          *simple_patch_request_uri,
                    char          *target_val,
                    char          *api_path,
                    cxobj         *key_xn
                    )
{
    int     retval = -1;
    cxobj **values_child_vec;
    char   *key_node_id;
    cbuf   *patch_header = NULL;
    cxobj  *x_simple_patch = NULL;
    int     value_vec_len;
    cxobj **value_vec;

    values_child_vec = xml_childvec_get(xn);
    key_node_id = xml_name(*values_child_vec);
    /* Create cbufs:s */
    if ((patch_header = cbuf_new()) == NULL){
        clicon_err(OE_UNIX, errno, "cbuf_new");
        goto done;
    }
    cprintf(patch_header, "%s:%s", modname, key_node_id);
    if ((x_simple_patch = xml_new(cbuf_get(patch_header), NULL, CX_ELMNT)) == NULL)
        goto done;
    value_vec_len = xml_child_nr(*values_child_vec);
    value_vec = xml_childvec_get(*values_child_vec);
    switch (operation){
    case YANG_PATCH_OP_REPLACE:
        if (yang_patch_do_replace(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, target_val, value_vec_len, value_vec, x_simple_patch) < 0)
            goto done;
        break;
    case YANG_PATCH_OP_CREATE:
        if (yang_patch_do_create(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch) < 0)
            goto done;
        break;
    case YANG_PATCH_OP_INSERT:
        if (yang_patch_do_insert(h, req, pi, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch, where_val, api_path, point_val) < 0)
            goto done;
        break;
    case YANG_PATCH_OP_MERGE:
        if (yang_patch_do_merge(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch, key_xn) < 0)
            goto done;
        break;
    default:
        break;
    }
    retval = 0;
 done:
    if (x_simple_patch)
        xml_free(x_simple_patch);
    if (patch_header)
        cbuf_free(patch_header);
    return retval;
}

/*! Process a single yang patch "edit" element
 *
 * @param[in]  h         Clixon handle
 * @param[in]  req       Generic Www handle
 * @param[in]  pi        Offset, where to start api-path
 * @param[in]  qvec      Vector of query string (QUERY_STRING)
 * @param[in]  pretty    Set to 1 for pretty-printed xml/json output
 * @param[in]  media_out Output media
 * @param[in]  xn        XML edit element
 * @retval     0         OK
 * @retval    -1         Error
 */
static int
yang_patch_do_edit(clicon_handle  h,
                   void          *req,
                   int            pi,
                   cvec          *qvec,
                   int            pretty,
                   restconf_media media_out,
                   ietf_ds_t      ds,
                   yang_stmt     *yspec,
                   cxobj         *xn,
                   char          *uripath0,
                   char          *api_path
                   )
{
    int        retval = -1;
    cxobj    **vec = NULL;
    size_t     veclen = 0;
    int        ret;
    cxobj     *xerr = NULL;    /* malloced must be freed */
    cxobj     *xtop; 
    cxobj     *xbot = NULL;
    yang_patch_op_t operation;
    char      *where_val = NULL;
    char      *point_val = NULL;
    char      *target_val = NULL;
    char      *modname;
    cxobj    **key_vec = NULL;
    cxobj     *key_xn = NULL;
    int        i;
    cxobj     *x; /* general purpose xml-tree pointer */
    cbuf      *simple_patch_request_uri = NULL;
    cbuf      *api_path_target = NULL;
    yang_stmt *ybot = NULL;
    yang_stmt *ymod;

    clicon_debug_xml(1, xn, "%s %d xn:", __FUNCTION__, __LINE__);
    /* Create cbufs:s */
    if ((simple_patch_request_uri = cbuf_new()) == NULL){
        clicon_err(OE_UNIX, errno, "cbuf_new");
        goto done;
    }
    if ((api_path_target = cbuf_new()) == NULL){
        clicon_err(OE_UNIX, errno, "cbuf_new");
        goto done;
    }
    if ((x = xpath_first(xn, NULL, "target")) == NULL){
        clicon_err(OE_YANG, 0, "target mandatory element not found");
        goto done;
    }
    target_val = xml_body(x);
    if ((x = xpath_first(xn, NULL, "operation")) == NULL){
        clicon_err(OE_YANG, 0, "operation mandatory element not found");
        goto done;
    }
    operation = yang_patch_op2int(xml_body(x));
    /* target and operation are mandatory */
    if (target_val == NULL){
        clicon_err(OE_YANG, 0, "operation/target: mandatory element not found");
        goto done;
    }
    if (operation == YANG_PATCH_OP_INSERT){
        if ((x = xpath_first(xn, NULL, "point")) != NULL)
            point_val = xml_body(x);
        if ((x = xpath_first(xn, NULL, "where")) != NULL)
            where_val = xml_body(x);
        if (point_val == NULL || where_val == NULL){
            clicon_err(OE_YANG, 0, "point/where: expected element not found");
            goto done;
        }       
    }
    // Construct request URI
    cprintf(simple_patch_request_uri, "%s", uripath0);
    cprintf(api_path_target, "%s", api_path);
    if (operation == YANG_PATCH_OP_MERGE) {
        cbuf_append_str(api_path_target, target_val);
        cbuf_append_str(simple_patch_request_uri, target_val);
    }

    if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
        goto done;

    // Get key field
    /* Translate api_path to xml in the form of xtop/xbot */
    xbot = xtop;
    if ((ret = api_path2xml(cbuf_get(api_path_target), yspec, xtop, YC_DATANODE, 1, &xbot, &ybot, &xerr)) < 0)
        goto done;
    if (ret == 0){ /* validation failed */
        if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
            goto done;
        goto ok;
    }
    /* Get module name */
    if (ys_real_module(ybot, &ymod) < 0)
        goto done;
    modname = yang_argument_get(ymod);
    // XXX this seems to be hardcoded to a yang list?
    if ((key_vec = xml_childvec_get(xbot)) != NULL)
        key_xn = key_vec[0];
    // Get values (for "delete" and "remove", there are no values)
    xpath_vec(xn, NULL, "value", &vec, &veclen);

    // Loop through the values
    for (i = 0; i < veclen; i++) {
        if (yang_patch_do_value(h, req, pi, qvec, pretty, media_out, ds,
                                vec[i], modname,
                                operation, where_val, point_val, simple_patch_request_uri, target_val,
                                api_path, key_xn) < 0)
            goto done;
    }
    if (operation == YANG_PATCH_OP_DELETE ||
        operation == YANG_PATCH_OP_REMOVE){
        cbuf_append_str(simple_patch_request_uri, target_val);
        if (operation == YANG_PATCH_OP_DELETE) {
            // TODO - send error
        } else {
            // TODO - do not send error
        }
        api_data_delete(h, req, cbuf_get(simple_patch_request_uri), pi, pretty, YANG_DATA_JSON, ds); 
    }
 ok:
    retval = 0;
 done:
    if (vec)
        free(vec);
    if (simple_patch_request_uri)
        cbuf_free(simple_patch_request_uri);
    if (api_path_target)
        cbuf_free(api_path_target);
    if (xtop)
        xml_free(xtop);
    if (xerr)
        xml_free(xerr);
    return retval;
}

/*! YANG PATCH method
 * @param[in]  h         Clixon handle
 * @param[in]  req       Generic Www handle
 * @param[in]  api_path0 According to restconf (Sec 3.5.3.1 in rfc8040)
 * @param[in]  pi        Offset, where to start api-path
 * @param[in]  qvec      Vector of query string (QUERY_STRING)
 * @param[in]  data      Stream input data
 * @param[in]  pretty    Set to 1 for pretty-printed xml/json output
 * @param[in]  media_in Input media
 * @param[in]  media_out Output media
 * @param[in]  ds       0 if "data" resource, 1 if rfc8527 "ds" resource
 * @retval     0         OK
 * @retval    -1         Error
 * Netconf:  <edit-config> (nc:operation="merge")
 * @see RFC8072
 * YANG patch can be used to "create", "delete", "insert", "merge", "move", "replace", and/or
   "remove" a resource within the target resource.
 * Currently "move" not supported
 */
int
api_data_yang_patch(clicon_handle  h,
                    void          *req,
                    char          *api_path0,
                    int            pi,
                    cvec          *qvec,
                    char          *data,
                    int            pretty,
                    restconf_media media_in,
                    restconf_media media_out,
                    ietf_ds_t      ds)
{
    int            retval = -1;
    int            i;
    cxobj         *xpatch = NULL; 
    yang_stmt     *yspec;
    char          *api_path;
    cxobj         *xerr = NULL;    /* malloced must be freed */
    int            ret;
    char          *uripath0 = NULL;
    size_t         veclen;
    cxobj        **vec = NULL;

    clicon_debug(1, "%s api_path:\"%s\"",  __FUNCTION__, api_path0);
    if ((yspec = clicon_dbspec_yang(h)) == NULL){
        clicon_err(OE_FATAL, 0, "No DB_SPEC");
        goto done;
    }
    api_path=api_path0;
    /* strip /... from start */
    for (i=0; i<pi; i++)
        api_path = index(api_path+1, '/');
    if (data == NULL || strlen(data) == 0){
        if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0)
            goto done;
        if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
            goto done;
        goto ok;
    }
    switch (media_in){
    case YANG_PATCH_XML:
        ret = clixon_xml_parse_string(data, YB_MODULE, yspec, &xpatch, &xerr);
        break;
    case YANG_PATCH_JSON:       /* RFC 8072 patch */
        ret = clixon_json_parse_string(data, 1, YB_MODULE, yspec, &xpatch, &xerr);
        break;
    default:
        restconf_unsupported_media(h, req, pretty, media_out);
        goto ok;
        break;
    }
    /* Common error handling for json/xml parsing above */
    if (ret < 0){
        if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
            goto done;
        if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
            goto done;
        goto ok;
    }
    if (ret == 0){
        if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
            goto done;
        goto ok;
    }
    /* 
     * RFC 8072 2.1: The message-body MUST identify exactly one resource instance
     */
    if (xml_child_nr_type(xpatch, CX_ELMNT) != 1){
        if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0)
            goto done;
        if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
            goto done;
        goto ok;
    }
    /* Save original uripath (will be changed by sub-calls) */
    if ((uripath0 = restconf_uripath(h)) == NULL)
        goto done;
    /* Find all edit operations and loop over them
     */    
    if (xpath_vec(xpatch, NULL, "yang-patch/edit", &vec, &veclen) < 0)
        goto done;
    for (i = 0; i < veclen; i++) {
        if (yang_patch_do_edit(h, req, pi, qvec, pretty, media_out, ds,
                               yspec,
                               vec[i],
                               uripath0, api_path) < 0)
            goto done;
    }
 ok:
    retval = 0;
 done:
    if (uripath0)
        free(uripath0);
    if (vec)
        free(vec);
    if (xerr)
        xml_free(xerr);
    if (xpatch)
        xml_free(xpatch);
    return retval;
}

#else // CLIXON_YANG_PATCH

int
api_data_yang_patch(clicon_handle h,
                    void         *req,
                    char         *api_path0,
                    int           pi,
                    cvec         *qvec,
                    char         *data,
                    int           pretty,
                    restconf_media media_in,
                    restconf_media media_out,
                    ietf_ds_t     ds)
{
    clicon_err(OE_RESTCONF, 0, "Not implemented");
    return -1;
}
#endif // CLIXON_YANG_PATCH
