/* -*- mode:c -*- */
/** @file
 ** @brief Maestro Attribute Schema ingest and data structures
 **/
/*
 * Copyright (C) 2020 Cray Computer GmbH
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#include "maestro/status.h"
#include "maestro/logging.h"
#include "maestro/i_uthash.h"
#include "protocols/mstro_pool.pb-c.h"

#include "yaml.h"

#include <errno.h>
#include <stdlib.h>

/* A single attribute definition */
struct mstro_attribute_entry {
  UT_hash_handle *hh; /**< hashable, by key, using magic hash function
                       * below */
  char *key; /**< attribute key, also key in hash table */
  /** The type permitted for this attribute */
  Mstro__Pool__AVal__ValCase val_type;
};
  
/* Data structure for (a fragment of) a schema */
struct mstro_attribute_schema {
  /** The name of this schema */
  char *name;
  /** Its version number */
  size_t version;
  /** the set of declarations in this schema */
  struct mstro_attribite_entry *declarations;
};

/* The (merge of) all loaded schemata. This is conceptually read-only
 * after mstro_init(), and holds the information on all defined
 * attribute keys and value types */
static
struct mstro_attribute_schema *g_mstro_attribute_schema = NULL;


/** Initialize schema module */
mstro_status
mstro_schema_init(void)
{
  return MSTRO_OK;
}

/** finalize schema module */
mstro_status
mstro_schema_finalize(void)
{
  if(g_mstro_attribute_schema!=NULL) {
    WARN("Not deallocating schema tree\n");
  }
  return MSTRO_OK;
}

static inline
mstro_status
mstro_schema__parse(yaml_parser_t parser,
                    struct mstro_attribute_schema **result)
{
  yaml_token_t  token;
  char *last_key=NULL;
  enum {
    INVALID,
    KEY, VALUE,
    BLOCKSEQ, BLOCKENT, BLOCKMAP
  } last_kind = INVALID;
  ssize_t depth = -1;

  *result = malloc(sizeof(struct mstro_attribute_schema));
  if(*result == NULL) {
    return MSTRO_NOMEM;
  }

  /* for now, let's see if token-based scanning is sufficient */
  do {
    yaml_parser_scan(&parser, &token);
    switch(token.type) {
      /* Stream start/end */
      case YAML_STREAM_START_TOKEN:
        DEBUG("STREAM START\n");
        depth++;
        break;
      case YAML_STREAM_END_TOKEN:
        DEBUG("STREAM END\n");
        depth--;
        break;

        /* Token types (read before actual token) */
      case YAML_KEY_TOKEN:
        DEBUG("(Key token)   \n");
        last_kind = KEY;
        break;
      case YAML_VALUE_TOKEN:
        DEBUG("(Value token) \n");
        last_kind = VALUE;
        break;

        /* Block delimeters */
      case YAML_BLOCK_SEQUENCE_START_TOKEN:
        DEBUG("Start Block (Sequence)\n");
        last_kind = BLOCKSEQ;
        depth++;
        break;
      case YAML_BLOCK_ENTRY_TOKEN:
        DEBUG("Start Block (Entry)\n");
        last_kind = BLOCKENT;
        break;
      case YAML_BLOCK_END_TOKEN:
        DEBUG("End block\n");
        depth--;
        break;
        
        /* Data */
      case YAML_BLOCK_MAPPING_START_TOKEN:
        DEBUG("[Block mapping]\n");
        last_kind=BLOCKMAP;
        depth++;
        break;
      case YAML_SCALAR_TOKEN:
        DEBUG("scalar %s (for kind %d)\n",
              token.data.scalar.value, last_kind);
        /* yaml_char_t is signed */
        const char *val = (const char*)token.data.scalar.value;
        switch(last_kind) {
          case KEY:
            /* store key for value coming up */
            if(last_key!=NULL) {
              ERR("two keys in succession: stack %s, here %s\n",
                  last_key, val);
              free(last_key);
            }
            last_key = strdup(val);
            if(last_key==NULL) {
              ERR("Failed to allocate key\n");
            }
            break;
          case VALUE:
            if(last_key==NULL) {
              ERR("No key preceeded this value\n");
            } else {
              /* This is where the built-in meta-schema is hiding */
              if(strcmp(last_key, "schema-name")==0) {
                (*result)->name = last_key;
                last_key=NULL;
              } else if(strcmp(last_key, "schema-version")==0) {
                (*result)->version = atol(val);
                free(last_key);
              } else {
                DEBUG("Value |%s| for unknown key |%s|\n", val, last_key);
                free(last_key);
              }
              last_key=NULL;
            }
            break;
          default:
            WARN("Unexpected last_kind value\n");
        }          
        break;
        
        /* Others */
      default:
        WARN("Got token of type %d\n", token.type);
    }
    if(token.type != YAML_STREAM_END_TOKEN)
      yaml_token_delete(&token);
  } while(token.type != YAML_STREAM_END_TOKEN);
  yaml_token_delete(&token);

  if(depth!=-1) {
    ERR("YAML stream nesting does not balance, depth %u at end (should be -1)\n",
        depth);
  }

  
  return MSTRO_UNIMPL;
}

/* merge SCHEMA into global schema tree. Consume argument, even on error */
static inline
mstro_status
mstro_schema__merge(struct mstro_attribute_schema *schema)
{
  return MSTRO_UNIMPL;
}


/** Add a file's content to schema tree */
mstro_status
mstro_schema_add_from_file(char *path)
{
  mstro_status s;
  if(path==NULL) {
    s=MSTRO_INVARG;
    goto BAILOUT;
  }
  
  FILE *f = fopen(path, "r");
  if(f==NULL) {
    ERR("Failed to open schema file %s: %d (%s)\n",
        path, errno, strerror(errno));
    s=MSTRO_FAIL;
    goto BAILOUT;
  }
  
  /* Initialize parser */
  yaml_parser_t parser;
  if(!yaml_parser_initialize(&parser)) {
    ERR("Failed to initialize YAML parser\n");
    s= MSTRO_FAIL;
    goto BAILOUT_CLOSE;
  }

  /* Set input file */
  yaml_parser_set_input_file(&parser, f);

  struct mstro_attribute_schema *schema;
  s=mstro_schema__parse(parser, &schema);
  yaml_parser_delete(&parser);
  
  if(s!=MSTRO_OK) {
    ERR("Failed to parse schema %s\n", path);
  } else {
    INFO("Parsed schema definition %s\n", schema->name);
    /* merge it (consumes argument, even on error) */
    s=mstro_schema__merge(schema);
    if(s!=MSTRO_OK) {
      ERR("Failed to merge schema\n");
    }
  }
     
BAILOUT_CLOSE:
  fclose(f);
BAILOUT:
  return s;
}