#ifdef FOSSIL_ENABLE_JSON
/*
** Copyright (c) 2011 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*/
#include "VERSION.h"
#include "config.h"
#include "json_tag.h"

#if INTERFACE
#include "json_detail.h"
#endif


static cson_value * json_tag_add();
static cson_value * json_tag_cancel();
static cson_value * json_tag_find();
static cson_value * json_tag_list();
/*
** Mapping of /json/tag/XXX commands/paths to callbacks.
*/
static const JsonPageDef JsonPageDefs_Tag[] = {
{"add", json_tag_add, 0},
{"cancel", json_tag_cancel, 0},
{"find", json_tag_find, 0},
{"list", json_tag_list, 0},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};

/*
** Implements the /json/tag family of pages/commands.
**
*/
cson_value * json_page_tag(){
  return json_page_dispatch_helper(&JsonPageDefs_Tag[0]);
}


/*
** Impl of /json/tag/add.
*/
static cson_value * json_tag_add(){
  cson_value * payV = NULL;
  cson_object * pay = NULL;
  char const * zName = NULL;
  char const * zCheckin = NULL;
  char fRaw = 0;
  char fPropagate = 0;
  char const * zValue = NULL;
  const char *zPrefix = NULL;

  if( !g.perm.Write ){
    json_set_err(FSL_JSON_E_DENIED,
                 "Requires 'i' permissions.");
    return NULL;
  }
  fRaw = json_find_option_bool("raw",NULL,NULL,0);
  fPropagate = json_find_option_bool("propagate",NULL,NULL,0);
  zName = json_find_option_cstr("name",NULL,NULL);
  zPrefix = fRaw ? "" : "sym-";
  if(!zName || !*zName){
    if(!fossil_has_json()){
      zName = json_command_arg(3);
    }
    if(!zName || !*zName){
      json_set_err(FSL_JSON_E_MISSING_ARGS,
                   "'name' parameter is missing.");
      return NULL;
    }
  }

  zCheckin = json_find_option_cstr("checkin",NULL,NULL);
  if( !zCheckin ){
    if(!fossil_has_json()){
      zCheckin = json_command_arg(4);
    }
    if(!zCheckin || !*zCheckin){
      json_set_err(FSL_JSON_E_MISSING_ARGS,
                   "'checkin' parameter is missing.");
      return NULL;
    }
  }


  zValue = json_find_option_cstr("value",NULL,NULL);
  if(!zValue && !fossil_has_json()){
    zValue = json_command_arg(5);
  }

  db_begin_transaction();
  tag_add_artifact(zPrefix, zName, zCheckin, zValue,
                   1+fPropagate,NULL/*DateOvrd*/,NULL/*UserOvrd*/);
  db_end_transaction(0);

  payV = cson_value_new_object();
  pay = cson_value_get_object(payV);
  cson_object_set(pay, "name", json_new_string(zName) );
  cson_object_set(pay, "value", (zValue&&*zValue)
                  ? json_new_string(zValue)
                  : cson_value_null());
  cson_object_set(pay, "propagate", cson_value_new_bool(fPropagate));
  cson_object_set(pay, "raw", cson_value_new_bool(fRaw));
  {
    Blob uu = empty_blob;
    int rc;
    blob_append(&uu, zName, -1);
    rc = name_to_uuid(&uu, 9, "*");
    if(0!=rc){
      json_set_err(FSL_JSON_E_UNKNOWN,"Could not convert name back to artifact hash!");
      blob_reset(&uu);
      goto error;
    }
    cson_object_set(pay, "appliedTo", json_new_string(blob_buffer(&uu)));
    blob_reset(&uu);
  }

  goto ok;
  error:
  assert( 0 != g.json.resultCode );
  cson_value_free(payV);
  payV = NULL;
  ok:
  return payV;
}


/*
** Impl of /json/tag/cancel.
*/
static cson_value * json_tag_cancel(){
  char const * zName = NULL;
  char const * zCheckin = NULL;
  char fRaw = 0;
  const char *zPrefix = NULL;

  if( !g.perm.Write ){
    json_set_err(FSL_JSON_E_DENIED,
                 "Requires 'i' permissions.");
    return NULL;
  }

  fRaw = json_find_option_bool("raw",NULL,NULL,0);
  zPrefix = fRaw ? "" : "sym-";
  zName = json_find_option_cstr("name",NULL,NULL);
  if(!zName || !*zName){
    if(!fossil_has_json()){
      zName = json_command_arg(3);
    }
    if(!zName || !*zName){
      json_set_err(FSL_JSON_E_MISSING_ARGS,
                   "'name' parameter is missing.");
      return NULL;
    }
  }

  zCheckin = json_find_option_cstr("checkin",NULL,NULL);
  if( !zCheckin ){
    if(!fossil_has_json()){
      zCheckin = json_command_arg(4);
    }
    if(!zCheckin || !*zCheckin){
      json_set_err(FSL_JSON_E_MISSING_ARGS,
                   "'checkin' parameter is missing.");
      return NULL;
    }
  }
  /* FIXME?: verify that the tag is currently active. We have no real
     error case unless we do that.
  */
  db_begin_transaction();
  tag_add_artifact(zPrefix, zName, zCheckin, NULL, 0, 0, 0);
  db_end_transaction(0);
  return NULL;
}


/*
** Impl of /json/tag/find.
*/
static cson_value * json_tag_find(){
  cson_value * payV = NULL;
  cson_object * pay = NULL;
  cson_value * listV = NULL;
  cson_array * list = NULL;
  char const * zName = NULL;
  char const * zType = NULL;
  char const * zType2 = NULL;
  char fRaw = 0;
  Stmt q = empty_Stmt;
  int limit = 0;
  int tagid = 0;

  if( !g.perm.Read ){
    json_set_err(FSL_JSON_E_DENIED,
                 "Requires 'o' permissions.");
    return NULL;
  }
  zName = json_find_option_cstr("name",NULL,NULL);
  if(!zName || !*zName){
    if(!fossil_has_json()){
      zName = json_command_arg(3);
    }
    if(!zName || !*zName){
      json_set_err(FSL_JSON_E_MISSING_ARGS,
                   "'name' parameter is missing.");
      return NULL;
    }
  }
  zType = json_find_option_cstr("type",NULL,"t");
  if(!zType || !*zType){
    zType = "*";
    zType2 = zType;
  }else{
    switch(*zType){
      case 'c': zType = "ci"; zType2 = "checkin"; break;
      case 'e': zType = "e"; zType2 = "event"; break;
      case 'w': zType = "w"; zType2 = "wiki"; break;
      case 't': zType = "t"; zType2 = "ticket"; break;
    }
  }

  limit = json_find_option_int("limit",NULL,"n",0);
  fRaw = json_find_option_bool("raw",NULL,NULL,0);

  tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='%s' || %Q",
                 fRaw ? "" : "sym-",
                 zName);

  payV = cson_value_new_object();
  pay = cson_value_get_object(payV);
  cson_object_set(pay, "name", json_new_string(zName));
  cson_object_set(pay, "raw", cson_value_new_bool(fRaw));
  cson_object_set(pay, "type", json_new_string(zType2));
  cson_object_set(pay, "limit", json_new_int(limit));

#if 1
  if( tagid<=0 ){
    cson_object_set(pay,"artifacts", cson_value_null());
    json_warn(FSL_JSON_W_TAG_NOT_FOUND, "Tag not found.");
    return payV;
  }
#endif

  if( fRaw ){
    db_prepare(&q,
               "SELECT blob.uuid FROM tagxref, blob"
               " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
               "   AND tagxref.tagtype>0"
               "   AND blob.rid=tagxref.rid"
               "%s LIMIT %d",
               zName,
               (limit>0)?"":"--", limit
               );
    while( db_step(&q)==SQLITE_ROW ){
      if(!listV){
        listV = cson_value_new_array();
        list = cson_value_get_array(listV);
      }
      cson_array_append(list, cson_sqlite3_column_to_value(q.pStmt,0));
    }
    db_finalize(&q);
  }else{
    char const * zSqlBase = /*modified from timeline_query_for_tty()*/
      " SELECT"
#if 0
      "   blob.rid AS rid,"
#endif
      "   uuid AS uuid,"
      "   cast(strftime('%s',event.mtime) as int) AS timestamp,"
      "   coalesce(ecomment,comment) AS comment,"
      "   coalesce(euser,user) AS user,"
      "   CASE event.type"
      "     WHEN 'ci' THEN 'checkin'"
      "     WHEN 'w' THEN 'wiki'"
      "     WHEN 'e' THEN 'event'"
      "     WHEN 't' THEN 'ticket'"
      "     ELSE 'unknown'"
      "   END"
      "   AS eventType"
      " FROM event, blob"
      " WHERE blob.rid=event.objid"
      ;
    /* FIXME: re-add tags. */
    db_prepare(&q,
               "%s"
               "  AND event.type GLOB '%q'"
               "  AND blob.rid IN ("
               "    SELECT rid FROM tagxref"
               "      WHERE tagtype>0 AND tagid=%d"
               "  )"
               " ORDER BY event.mtime DESC"
               "%s LIMIT %d",
               zSqlBase /*safe-for-%s*/, zType, tagid,
               (limit>0)?"":"--", limit
               );
    listV = json_stmt_to_array_of_obj(&q, NULL);
    db_finalize(&q);
  }

  if(!listV) {
    listV = cson_value_null();
  }
  cson_object_set(pay, "artifacts", listV);
  return payV;
}


/*
** Impl for /json/tag/list
**
** TODOs:
**
** Add -type TYPE (ci, w, e, t)
*/
static cson_value * json_tag_list(){
  cson_value * payV = NULL;
  cson_object * pay = NULL;
  cson_value const * tagsVal = NULL;
  char const * zCheckin = NULL;
  char fRaw = 0;
  char fTicket = 0;
  Stmt q = empty_Stmt;

  if( !g.perm.Read ){
    json_set_err(FSL_JSON_E_DENIED,
                 "Requires 'o' permissions.");
    return NULL;
  }

  fRaw = json_find_option_bool("raw",NULL,NULL,0);
  fTicket = json_find_option_bool("includeTickets","tkt","t",0);
  zCheckin = json_find_option_cstr("checkin",NULL,NULL);
  if( !zCheckin ){
    zCheckin = json_command_arg( g.json.dispatchDepth + 1);
    if( !zCheckin && cson_value_is_string(g.json.reqPayload.v) ){
      zCheckin = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v));
      assert(zCheckin);
    }
  }
  payV = cson_value_new_object();
  pay = cson_value_get_object(payV);
  cson_object_set(pay, "raw", cson_value_new_bool(fRaw) );
  if( zCheckin ){
    /**
       Tags for a specific check-in. Output format:

       RAW mode:

       {
           "sym-tagname": (value || null),
           ...other tags...
       }

       Non-raw:

       {
          "tagname": (value || null),
          ...other tags...
       }
    */
    cson_value * objV = NULL;
    cson_object * obj = NULL;
    int const rid = name_to_rid(zCheckin);
    if(0==rid){
      json_set_err(FSL_JSON_E_UNRESOLVED_UUID,
                   "Could not find artifact for check-in [%s].",
                   zCheckin);
      goto error;
    }
    cson_object_set(pay, "checkin", json_new_string(zCheckin));
    db_prepare(&q,
               "SELECT tagname, value FROM tagxref, tag"
               " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
               "   AND tagtype>%d"
               " ORDER BY tagname",
               rid,
               fRaw ? -1 : 0
               );
    while( SQLITE_ROW == db_step(&q) ){
      const char *zName = db_column_text(&q, 0);
      const char *zValue = db_column_text(&q, 1);
      if( fRaw==0 ){
        if( 0!=strncmp(zName, "sym-", 4) ) continue;
        zName += 4;
        assert( *zName );
      }
      if(NULL==objV){
        objV = cson_value_new_object();
        obj = cson_value_get_object(objV);
        tagsVal = objV;
        cson_object_set( pay, "tags", objV );
      }
      if( zValue && zValue[0] ){
        cson_object_set( obj, zName, json_new_string(zValue) );
      }else{
        cson_object_set( obj, zName, cson_value_null() );
      }
    }
    db_finalize(&q);
  }else{/* all tags */
    /* Output format:

    RAW mode:

    ["tagname", "sym-tagname2",...]

    Non-raw:

    ["tagname", "tagname2",...]

    i don't really like the discrepancy in the format but this list
    can get really long and (A) most tags don't have values, (B) i
    don't want to bloat it more, and (C) cson_object_set() is O(N)
    (N=current number of properties) because it uses an unsorted list
    internally (for memory reasons), so this can slow down appreciably
    on a long list. The culprit is really tkt- tags, as there is one
    for each ticket (941 in the main fossil repo as of this writing).
    */
    Blob sql = empty_blob;
    cson_value * arV = NULL;
    cson_array * ar = NULL;
    blob_append(&sql,
                "SELECT tagname FROM tag"
                " WHERE EXISTS(SELECT 1 FROM tagxref"
                "               WHERE tagid=tag.tagid"
                "                 AND tagtype>0)",
                -1
                );
    if(!fTicket){
      blob_append(&sql, " AND tagname NOT GLOB('tkt-*') ", -1);
    }
    blob_append(&sql,
                " ORDER BY tagname", -1);
    db_prepare(&q, "%s", blob_sql_text(&sql));
    blob_reset(&sql);
    cson_object_set(pay, "includeTickets", cson_value_new_bool(fTicket) );
    while( SQLITE_ROW == db_step(&q) ){
      const char *zName = db_column_text(&q, 0);
      if(NULL==arV){
        arV = cson_value_new_array();
        ar = cson_value_get_array(arV);
        cson_object_set(pay, "tags", arV);
        tagsVal = arV;
      }
      else if( !fRaw && (0==strncmp(zName, "sym-", 4))){
        zName += 4;
        assert( *zName );
      }
      cson_array_append(ar, json_new_string(zName));
    }
    db_finalize(&q);
  }

  goto end;
  error:
  assert(0 != g.json.resultCode);
  cson_value_free(payV);
  payV = NULL;
  end:
  if( payV && !tagsVal ){
    cson_object_set( pay, "tags", cson_value_null() );
  }
  return payV;
}
#endif /* FOSSIL_ENABLE_JSON */
