#' @title Add vertices to a node in the igraph object
#' @description Add vertices to a node in the igraph object
#' @return Completed graph

add_vertices_sunset <- function(graph,
                            name = NULL,
                            nv = 1,
                            label = NULL,
                            className = NULL,
                            attr = NULL) {
  if (class(graph) != "igraph") stop("The input graph has not a valid format")
  if (is.null(name)) stop("The 'name' attribute is required", call. = FALSE)
  if (!name %in% vertex_attr(graph, name = "name")) {
    if (is.null(className)) 
      stop("The 'className' attribute is required", call. = FALSE)
    if (is.null(label)) stop("The 'label' attribute is required", call. = FALSE)
    if (is.null(attr)) {
      graph <- add_vertices(graph,
                            nv = nv,
                            name = name,
                            label = label,
                            className = className)
    } else {
      graph <- add_vertices(graph,
                            nv = nv,
                            name = name,
                            label = label,
                            className = className,
                            attr = attr)
    }
  }
  return(graph)
}

embedJSON <- function(png.in, json.file, png.out) {
    txt <- scan(json.file,
                what = character(), strip.white = TRUE,
                quote = "'", blank.lines.skip = TRUE)
    metadata <- paste(txt, collapse = " ") %>% charToRaw() %>% memCompress() %>% as.character() %>% paste(collapse = "")
    metadata <- c(metaclip = metadata)
    img <- png::readPNG(source = png.in)
    # Embed compressed metadata
    png::writePNG(image = img,
                  text = metadata,
                  target = png.out)
}

#' @title Generate provenance for Spatial Extent definition
#' @description Generate provenance information of the spatial coordinates of a climate data object in s2dv_cube format. Returns a graph containing a single SpatialExtent node. 
#' @param data The dataset containing spatial coordinates.
#' @param type The type of dataset (fcst, hcst, obs)
#' @param Reg Logical indicating if the operation is for regriddinging.
#' @param Cal Logical indicating if the operation is for calibration.
#' @param Downsc Logical indicating if the operation is for downscaling.
#' @return A list containing the graph and the parent node name.
#' @import igraph
#' @examples
#' # Example usage:
#' getSpatialExtent(data, type, Reg = TRUE)
#' getSpatialExtent(data, type, Cal = TRUE)
#' getSpatialExtent(data, type, Downsc = TRUE)
#' getSpatialExtent(data, type)

get_spatial_extent <- function(data, type, var, Reg = FALSE, Cal = FALSE, Downsc = FALSE, Aggr = FALSE) {

    graph <- make_empty_graph(0)
    
    

    # Get maximum and minimum Latitude values
    ymax <- max(as.numeric(data[[type]]$coords$lat))
    ymin <- min(as.numeric(data[[type]]$coords$lat))

    # Get maximum and minimum Longitude values
    xmax <- max(as.numeric(data[[type]]$coords$lon))
    xmin <- min(as.numeric(data[[type]]$coords$lon))

    # Calculate Latitude resolution and get units
    resY <- abs(as.numeric(data[[type]]$coords$lon)[2] - as.numeric(data[[type]]$coords$lon)[1])
    yunits <- data[[type]]$attrs$Variable$metadata[[var]]$dim[[1]]$units

    # Calculate Longitude resolution and get units
    resX <- abs(as.numeric(data[[type]]$coords$lat)[2] - as.numeric(data[[type]]$coords$lat)[1])
    xunits <- data[[type]]$attrs$Variable$metadata[[var]]$dim[[2]]$units


  #Time Resolution
  #tunits <- data[[type]]$attrs$Variable$metadata[[var]]$dim[[3]]$units

  if(Reg){
    spatextent.nodename <- paste0("SpatialExtent_", "Reggriding_", type, "_", var)
  }else if(Cal){
    spatextent.nodename <- paste0("SpatialExtent_", "Calibration_", type, "_", var)
  }else if(Downsc){
    spatextent.nodename <- paste0("SpatialExtent_", "Downscaling_", type, "_", var)
  }else if(Aggr){
    spatextent.nodename <- paste0("SpatialExtent_", "Aggregation_", type, "_", var)
  }else{
    spatextent.nodename <- paste0("SpatialExtent_", "DatasetSubset_", type, "_", var)
  }
  graph <-
    add_vertices_sunset(
                 graph,
                 nv = 1,
                 name = spatextent.nodename,
                 label = "SpatialExtent",
                 className = "ds:HorizontalExtent",
                 attr =
                 list(
                      "ds:xmin" = xmin,
                      "ds:xmax" = xmax,
                      "ds:ymin" = ymin,
                      "ds:ymax" = ymax,
                      "ds:hasHorizontalResX" = resX,
                      "ds:hasHorizontalResY" = resY,
                      "ds:hasProjection:" = NULL,
                      "ds:withUnits" =
                      paste0(
                             paste0("X:", xunits, sep = " "), "  ",
                             paste0("Y:", yunits, sep = " "), "  ")))
  return(list("graph" = graph, "parentnodename" = spatextent.nodename))
}

my_union_graph <- function(...) {
    graph.list <- list(...)
    graph <- graph.list[[1]]
    for (i in 2:length(graph.list)) {
        int <- intersect(vertex_attr(graph, name = "name"), vertex_attr(graph.list[[i]], name = "name")) %>% length()
        if (int == 0L) {
            graph <- disjoint_union(graph, graph.list[[i]])    
        } else {
            g <- igraph::union(graph, graph.list[[i]])
            graph <- my_union(g, graph, graph.list[[i]])
        }
    }
    invisible(graph)
}


#' @title Attribute-preserving igraph union
#' @description Transforms the resulting output from igraph::union to adequately recover the original attributes.
#' Apart from vertex attributes, the "label" attribute from edges, used to define object properties, is fixed.
#' @param g The union graph
#' @param g1 First input graph
#' @param g2 Second input graph
#' @return Invisible returns a new graph with fixed attributes
#' @author J. Bedia
#' @keywords internal
#' @export
#' @importFrom magrittr %>% 
#' @importFrom igraph vertex_attr vertex_attr_names delete_vertex_attr get.edgelist edge_attr delete_edges add_edges set_vertex_attr
#' @importFrom stats na.exclude

my_union <- function(g, g1, g2) {
    # Vertex attr adjustment
    ind.vertex <- match(vertex_attr(g, "name"), c(vertex_attr(g1, "name"), vertex_attr(g2, "name")))
    duplicated.names <- intersect(vertex_attr_names(g1), vertex_attr_names(g2))
    duplicated.names <- duplicated.names[-match("name", duplicated.names)]
    if (length(duplicated.names) > 0) {
        for (i in 1:length(duplicated.names)) {
            all.vnames <- c(vertex_attr(g1, duplicated.names[i]), vertex_attr(g2, duplicated.names[i]))
            g <- set_vertex_attr(graph = g, name = duplicated.names[i], value = all.vnames[ind.vertex])
            rmv <- grep(paste0(duplicated.names[i], "_[:1-2:]$"), vertex_attr_names(g), value = TRUE)
            g <- delete_vertex_attr(g, rmv[1])
            g <- delete_vertex_attr(g, rmv[2])
        }
        # Edge label adjustment
        if (!is.null(edge_attr(g, "label_1"))) {
            edgelist <- get.edgelist(g)
            edgelabels <- edge_attr(g, "label_2")
            ind <- which(is.na(edgelabels))
            if (length(ind) > 0) {
                edgelabels[ind] <- edge_attr(g, "label_1")[ind]                
            }
            g <- delete_edges(g, E(g))
            for (i in 1:nrow(edgelist)) {
                g <- add_edges(g,
                               c(getNodeIndexbyName(g, edgelist[i,1]),
                                 getNodeIndexbyName(g, edgelist[i,2])),
                               label = edgelabels[i])
            }
        }
    }
    invisible(g)
}

#' @title RDF graph serialization to JSON-LD (metaclipR)
#' @description Takes an igraph-class RDF graph and write it in JSON-LD
#' @param graph An i-graph class graph
#' @param output.file Character string. Output path 
#' @return A JSON-LD representation of the metadata structure
#' @importFrom igraph V vertex_attr E edge_attr head_of is_igraph
#' @export
#' @family graphical.outputs
#' @author J Bedia
#' @references A useful app to check/test JSON-LD mark <https://json-ld.org/playground/> 


graph2json <- function(graph, output.file) {
    if (!is_igraph(graph)) stop("'graph' is not an igraph-class object")
    f <- output.file    
    g <- graph
    z <- file(f, "w")
    cat(c("{","\n"), sep = "", file = z)
    cat(c("\t\"@context\": {", "\n"), sep = "", file = z)    
    # Metaclip imports
    cat(c("\t\t\"ds\": ", "\"http://www.metaclip.org/datasource/datasource.owl#\",\n"), 
        sep = "", file = z)    
    cat(c("\t\t\"ipcc\": ", "\"http://www.metaclip.org/ipcc_terms/ipcc_terms.owl#\",\n"), 
        sep = "", file = z)    
    cat(c("\t\t\"veri\": ", "\"http://www.metaclip.org/verification/verification.owl#\",\n"), 
        sep = "", file = z)    
    cat(c("\t\t\"cal\": ", "\"http://www.metaclip.org/calibration/calibration.owl#\",\n"), 
        sep = "", file = z)    
    cat(c("\t\t\"go\": ", "\"http://www.metaclip.org/graphical_output/graphical_output.owl#\",\n"), 
        sep = "", file = z)    
    # prov-o
    cat(c("\t\t\"prov\": ", "\"http://www.w3.org/ns/prov#\",\n"),
        sep = "", file = z)
    # rdf schema
    cat(c("\t\t\"rdfs\": ", "\"http://www.w3.org/2000/01/rdf-schema#\",\n"),
        sep = "", file = z)    
    # dublin core
    cat(c("\t\t\"dc\": ", "\"http://www.w3.org/2002/07/owl\",\n"),
        sep = "", file = z)   
    # skos
    cat(c("\t\t\"skos\": ", "\"http://www.w3.org/2004/02/skos/core#\"\n"),
         sep = "", file = z)    
    cat("\t},", file = z)
    cat("\n\t\"@graph\":[", file = z)
    # Todos los vertices del grafo
    vertices <- V(g)
    # Counter de las clases ya descritas
    describedNodeNames <- c()
    firstParentNode = TRUE
    for (i in 1:length(vertices)) {
        vid <- vertex_attr(g, name = "name", index = vertices[i])
        if (!vid %in% describedNodeNames) {
            if (!firstParentNode) {
                cat("\n,", file = z)
            }
            cat("{", file = z)
            describedNodeNames <- serializeVertex(g, vertex = vertices[i], describedNodeNames, connection = z)            
            cat("}", file = z)
            firstParentNode = FALSE
        }
    }
    cat("]}", file = z)
    close(z)  
}


#' @title Recursive function for graph serialization (metaclipR)        
#' @description Recursive function to trace and encode properties of nodes. From metaclipR package
#' @keywords internal
#' @importFrom Rgraphviz from

serializeVertex <- function(g, vertex, describedNodeNames, connection) {
    z <- connection
    cat("\n", file = z)
    vid <- vertex_attr(g, name = "name", index = vertex)    
    describedNodeNames <- c(describedNodeNames, vid)
    vclass <- vertex_attr(g, name = "className", index = vertex)    
    label <- vertex_attr(g, name = "label", index = vertex)    
    node.index <- getNodeIndexbyName(g, vid)
    if (isIndividualInstance(vid)) {
        cat("\t\"@id\": ", paste0("\"", vid, "\",\n"), file = z)    
    } else {
        cat("\t\"@id\": ", paste0("\"#", vid, "\",\n"), file = z)    
    }
    if (!is.null(vclass)) cat(paste0("\t\"@type\": \"", vclass, "\",\n"), file = z)
    if (!is.null(label)) cat(paste0("\t\"rdfs:label\": \"", label, "\""), file = z)
    # Propiedades planas
    pplist <- getPlainPropertyList(g, node.index)
    hasplainproperties <- FALSE
    if (length(pplist) > 0) {
        hasplainproperties <- TRUE    
        cat(",\n", file = z)
        for (j in 1:length(pplist)) {
            cat(paste0("\"", names(pplist)[j], "\":"), paste0("\"", pplist[[j]], "\""), file = z)
            if (j < length(pplist)) {
                cat(",\n", file = z)
            }   
        }
    }
    # Arcos que parten de este nodo: E(g)[from(getNodeIndexbyName(g, vid))]
    # full.list <- vertex_attr(g, index = vertices[i])
    arcs <- E(g)[from(node.index)]
    if (length(arcs) > 0) { # El nodo tiene hijos
        cat("\n", file = z)
        classproperties <- edge_attr(g, name = "label", arcs)
        for (j in 1:length(unique(classproperties))) {
            currentProperty <- unique(classproperties)[j]
            # numero de hijos con esa propiedad
            nchildren <- sum(classproperties %in% currentProperty)
            ind.arc <- which(classproperties %in% currentProperty)
            cat(paste0(",\n\"", currentProperty, "\": "), file = z)
            if (nchildren == 1) {
                cat("{", file = z)
                child.node <- head_of(g, arcs[ind.arc])
                child.node.name <- vertex_attr(g, name = "name", index = child.node)
                # Si child.node ya está en describedNodeNames, skip serialize and point to identifier
                # <https://www.w3.org/TR/2014/REC-json-ld-20140116/#node-identifiers>
                if (!child.node.name %in% describedNodeNames) {
                    describedNodeNames <- serializeVertex(g, vertex = child.node, describedNodeNames, z)    
                } else {
                    if (isIndividualInstance(child.node.name)) {
                        cat("\t\"@id\": ", paste0("\"", child.node.name, "\"\n"), file = z)    
                    } else {
                        cat("\t\"@id\": ", paste0("\"#", child.node.name, "\"\n"), file = z)    
                    }
                } 
                cat("}", file = z)
            } else {
                cat("[", file = z)
                for (k in 1:nchildren) {
                    child.node <- head_of(g, arcs[ind.arc[k]])
                    child.node.name <- vertex_attr(g, name = "name", index = child.node)
                    cat("{", file = z)
                    # Si child.node ya está en describedNodeNames, skip serialize and point to identifier
                    # <https://www.w3.org/TR/2014/REC-json-ld-20140116/#node-identifiers>
                    if (!child.node.name %in% describedNodeNames) {
                        describedNodeNames <- serializeVertex(g, child.node, describedNodeNames, z)
                    } else {
                        if (isIndividualInstance(child.node.name)) {
                            cat("\t\"@id\": ", paste0("\"", child.node.name, "\"\n"), file = z)    
                        } else {
                            cat("\t\"@id\": ", paste0("\"#", child.node.name, "\"\n"), file = z)
                        }
                    }
                    cat("}", file = z)
                    if (k < nchildren) cat(",", file = z)
                }
                cat("]", file = z)
            }
        }
    }
    return(describedNodeNames)
}


#' @title Individual identification (metaclipR)
#' @description Is the node representing and individual instance (\code{TRUE}), or a 
#'             generic class entity (\code{FALSE})?
#' @return A Logical flag
#' @keywords internal
#' @author J Bedia

isIndividualInstance <- function(node.name) grepl("^ds\\:|^veri\\:|^go\\:|^cal\\:|^ipcc\\:", node.name)


#' @title Get node indices (metaclipR)
#' @description Get the node index positions from their names
#' @param graph \code{\link[igraph]{igraph}} graph 
#' @param names Character vector of names of the nodes whose index is queried
#' @family graph.helpers
#' @export
#' @importFrom igraph V make_graph
#' @author J Bedia
#' @return A vector of index positions corresponding to the input names  
#' @examples 
#' require(igraph)
#' graph <- make_graph(c("A", "B", "B", "C", "C", "D", "Pepe"), directed = FALSE)
#' getNodeIndexbyName(graph, "Pepe")
#' getNodeIndexbyName(graph, c("A","D","Pepe"))

getNodeIndexbyName <- function(graph, names) {
    out <- sapply(1:length(names), function(x) which(V(graph)$name == names[x]))
    names(out) <- names
    return(out)
}

#' @title Return all node attributes (metaclipR)
#' @description Return a list of all (non-empty) node attributes
#' @param graph \code{\link[igraph]{igraph}} graph 
#' @param vertex.name Character string, with the target vertex name
#' @param vertex.index Alternatively, instead of \code{vertex.name}, an integer
#'  indicating the index position of the vertex in the graph. 
#' @family graph.helpers
#' @export
#' @seealso \code{\link{getNodeIndexbyName}}
#' @importFrom igraph vertex_attr
#' @author J Bedia
#' @return A list of (non-empty) attributes belonging to the vertex

getNodeAttributes <- function(graph, vertex.index = NULL, vertex.name = NULL) {
    if (is.null(vertex.index)) {
        if (is.null(vertex.name)) {
            stop("Either a valid 'vertex.index' or 'vertex.name' value is required", call. = FALSE)
        } else {
            vertex.index <- getNodeIndexbyName(graph, vertex.name)
        }
    } 
    lista <- vertex_attr(graph, index = vertex.index) 
    non.empty.id <- which(sapply(1:length(lista), function(x) !is.na(lista[[x]])))
    return(lista[non.empty.id])
}


#' @title Return most node attributes (metaclipR)
#' @description Return almost all node attributes, but the name, classname, etc...
#' @param g \code{\link[igraph]{igraph}} graph 
#' @param node.index An integer indicating the index position of the vertex in the graph. 
#' @family graph.helpers
#' @keywords internal
#' @importFrom igraph vertex_attr
#' @author J Bedia
#' @return A list of (non-empty) attributes belonging to the vertex (lists are vectorized)


getPlainPropertyList <- function(g, node.index) {
    attr.list <- vertex_attr(g, index = node.index)    
    # Remove already known attrs
    ind <- which(!(names(attr.list) %in% c("name", "label", "className", "description")))    
    attr.list.s <- attr.list[ind]
    plain.property.list <- attr.list.s[which(!is.na(unlist(attr.list.s)))]
    return(plain.property.list)
}



#' @importFrom utils read.csv
#' @keywords internal

pkgVersionCheck <- function(pkg, version) {
    pkgv <- package_version(version)
    ref <- file.path(find.package(package = "metaclipR"), "pkg_versions.csv") %>% read.csv(stringsAsFactors = FALSE)
    if (!pkg %in% ref$package) {
        message("NOTE: The specified package is not a known ds:Package. Package version info will be annotated but not checked")
    } else {
        v <- ref[match(pkg, ref$package), 2:3]
        if (pkgv >= package_version(v[1]) && pkgv <= package_version(v[2])) {
            message("Valid package version")
        } else {
            warning("The package version provided is not guaranteed to be properly handled by metaclipR\nType 'knownPackageVersions()' for details.")
        }
    }
}



#' @title List of valid package versions for metaclipR
#' @description Print a table with the valid version range of known packages. For the listed packages and version ranges, 
#' metaclipR fuctions are guaranteed to correctly map arguments/functions to the corresponding METACLIP classes/properties.
#' @importFrom utils read.csv
#' @importFrom magrittr %>% 
#' @export

knownPackageVersions <- function() find.package(package = "metaclipR") %>% file.path("pkg_versions.csv") %>% read.csv(stringsAsFactors = FALSE) %>% print()

#' @title Set a node name
#' @description Set a node name after checking whether it is an individual instance or not
#' @export
#' @param node.name Proposed node name. This ca be an individual instance
#' @param node.class Class of the node
#' @param vocabulary Vocabulary defining the \code{node.class}. Default to \code{"datasource"}.
#' @return A character string to be set as the \code{"name"} attribute by \code{\link{my_add_vertices}}

setNodeName <- function(node.name, node.class, vocabulary = "datasource") {
    suppressWarnings(
        ifelse(node.name %in% suppressMessages(knownClassIndividuals(node.class, vocabulary = vocabulary)),
               paste0("ds:", node.name),
               paste0(node.name, ".", randomName()))
    )
}

#' @title Generate a random character string
#' @description Generates a random character string (lowercase) of a specified length. 
#' Used to avoid duplicated node names in igraph
#' @param len length of the character string
#' @export
#' @keywords internal
#' @author J Bedia
#' @return A string with the specified number of characters
#' @importFrom magrittr %>% extract
#' @examples 
#' randomName()
#' randomName(10)

randomName <- function(len = 6) {
    ind <- sample(1:length(letters)) %>% extract(1:len) 
    letters[ind] %>% paste(collapse = "")
}


#' @title Table of User Data Gateway PUBLIC datasets
#' @description Show the internal table with known Datasets and relevant metadata
#' @export
#' @author J Bedia
#' @return A \code{data.frame}
#' @export
#' @importFrom utils read.csv
#' @examples showUDGDatasources()[c(2,35,60,81),]

showUDGDatasources <- function() {
    read.csv(file.path(find.package("metaclipR"), "datasource.csv"),
             stringsAsFactors = FALSE, na.strings = "")
}


#' @title List all individuals from a class
#' @description List defined individuals pertaining to a specific class from a METACLIP vocabulary
#' @param classname The parent class from which the individual instances are queried
#' @param vocabulary The target vocabulary name. Possible values are \code{"datasource"} (the default),
#'  \code{"calibration"}, \code{"verification"}, \code{"graphical_output"} and \code{"ipcc_terms"}.
#' @param source.vocab Name of the vocabulary the parent class of the individuals belong to. By default, this is the same as the
#' one provided in \code{vocabulary}.
#' @param verbose If set to TRUE, it will print on screen details about the open connections to the remote vocabularies.
#' For testing purposes only.
#' @importFrom utils URLencode
#' @importFrom magrittr %>% 
#' @details The function will check the existing individuals in the latest stable target
#' ontology release
#' @return A character vector of individuals for that class
#' @note The function won't work if there is no internet connection, or any other connection problem prevents
#'  read access to the ontology file.
#' @export
#' @author J Bedia, D. San-Martín
#' @family ontology.helpers
#' @examples 
#' knownClassIndividuals("ModellingCenter")
#' knownClassIndividuals("ETCCDI")
#' # In case a class is misspelled or it has no individuals, 
#' # an empty vector is returned, with a warning:
#' knownClassIndividuals("ETCDDI")
#' knownClassIndividuals("Dataset")

knownClassIndividuals <- function(classname, vocabulary = "datasource", source.vocab = NULL, verbose = FALSE) {
    vocabulary <- match.arg(vocabulary, choices = c("datasource",
                                                    "calibration",
                                                    "verification",
                                                    "graphical_output",
                                                    "ipcc_terms"))
    if (is.null(source.vocab)) source.vocab <- vocabulary
    stopifnot(is.logical(verbose))
    refURL <- paste0("http://www.metaclip.org/individuals?vocab=", vocabulary, "&sourceVocab=", source.vocab, "&class=")
    message("Reading remote ", vocabulary, " ontology file ...")
    destURL <- paste0(refURL, classname) %>% URLencode() %>% url() 
    on.exit(close(destURL))
    if (isTRUE(verbose)) message(print(destURL))
    out <- tryCatch(suppressWarnings(readLines(destURL, warn = FALSE)), error = function(er) {
        er <- NULL
        return(er)
    })
    if (!is.null(out)) {
        a <- gsub("\"|\\[|]", "", out) %>% strsplit(split = ",") %>% unlist() 
        if (length(a) == 0) warning("No individuals found:\nEither the class does not exist or there are no individuals associated to it")
        return(a)
    } else {
        message("Unreachable remote vocabulary\nLikely reason: unavailable internet connection")
    }
}

knownClass <- function(classname, vocabulary = "datasource", source.vocab = NULL, verbose = FALSE) {
    # Ensure the vocabulary argument is one of the predefined choices
    vocabulary <- match.arg(vocabulary, choices = c("datasource",
                                                    "calibration",
                                                    "verification",
                                                    "graphical_output",
                                                    "ipcc_terms"))
    browser()
    # If source.vocab is NULL, default to the value of vocabulary
    if (is.null(source.vocab)) source.vocab <- vocabulary
    
    # Ensure that the verbose argument is a logical value
    stopifnot(is.logical(verbose))
    
    # Construct the base URL for fetching MetaCLIP class data
    refURL <- paste0("http://www.metaclip.org/", vocabulary, "/", source.vocab, ".owl#")
    
    # Append the class name to the URL and encode it properly
    destURL <- URLencode(paste0(refURL, classname))
    destURL <- url(destURL)
    
    # Ensure the connection to the URL is closed when the function exits
    on.exit(close(destURL))
    
    # Print the constructed URL if verbose is TRUE
    if (isTRUE(verbose)) message(print(destURL))
    
    # Attempt to read the content from the URL
    out <- tryCatch(suppressWarnings(readLines(destURL, warn = FALSE)), error = function(er) {
        er <- NULL
        return(er)
    })
    
    # If content was successfully read, process the output
    if (!is.null(out)) {
        # Clean the output, removing quotes and brackets, and split it into class components
        class_info <- gsub("\"|\\[|]", "", out) %>% strsplit(split = ",") %>% unlist() 
        
        # If no class information was found, issue a warning
        if (length(class_info) == 0) {
            warning("Class not found in the METACLIP vocabularies.:\nEither the class does not exist or there are no classes associated with it.")
        }
        
        # Return the class information
        return(class_info)
    
    } else {
        # If the URL couldn't be reached, show an appropriate message
        message("Unreachable remote vocabulary\nLikely reason: unavailable internet connection or invalid class.")
    }
}


#' @title Identify the class belonging of an individual
#' @description Identifies the class belonging of an individual. Superclasses are ignored
#' @param individual.name Name of the individual
#' @param vocabulary The target vocabulary name. Possible values are \code{"datasource"} (the default),
#'  \code{"calibration"}, \code{"verification"} and \code{"graphical_output"}.
#' @importFrom utils URLencode
#' @importFrom magrittr %>% extract2
#' @details The function will check the existing individuals in the latest stable target 
#' ontology release
#' @return A character string with the most direct class belonging (superclasses are not indicated)
#' @note The function won't work if there is no internet connection, or any other connection problem prevents
#'  read access to the ontology file.
#' @export
#' @author J Bedia, D. San-Martín
#' @family ontology.helpers
#' @examples
#' getIndividualClass("UDG") # The default vocabulary is datasource (can be omitted)
#' getIndividualClass("EQM", vocabulary = "calibration")

getIndividualClass <- function(individual.name, vocabulary = "datasource") {
    vocabulary <- match.arg(vocabulary, choices = c("datasource",
                                                    "calibration",
                                                    "verification",
                                                    "graphical_output",
                                                    "ipcc_terms"))
    refURL <- paste0("http://www.metaclip.org/individual?vocab=", vocabulary, "&id=")
    message("Reading remote ", vocabulary, " ontology file ...")
    destURL <- paste0(refURL, individual.name) %>% URLencode() %>% url() 
    on.exit(close(destURL))
    out <- tryCatch(suppressWarnings(readLines(destURL, warn = FALSE)), error = function(er) {
        er <- NULL
        return(er)
    })
    if (!is.null(out)) {
        aux <- gsub("\"|\\[|]", "", out)
        if (aux == "") {
            a <- NULL
        } else {
            a <-  aux %>% strsplit(split = ",") %>% unlist() %>% extract2(1) %>% gsub(pattern = "http://www.metaclip.org/.*\\.owl#", replacement = "")
            if (length(a) == 0) warning("No class found:\nEither the individual does not exist or there are no associated classes to it")
        }
        return(a)
    } else {
        message("Unreachable remote vocabulary\nLikely reason: unavailable internet connection")
    }
}


#' @title Encapsulate JSON in a NetCDF File
#' @description Stores JSON data as a global attribute in a NetCDF file
#' @param json.file Character string. Path to the JSON file to be encapsulated
#' @param output.nc Character string. Output path for the NetCDF file
#' @return The function saves a .nc file with JSON encapsulated as metadata
#' @importFrom RNetCDF create.nc put.att.nc close.nc
#' @export
#' @family netcdf.outputs
#' @examples
#' encapsulate_json_in_netcdf("metadata.json", "output.nc")
#' 
encapsulate_json_in_netcdf <- function(json.file, output.nc) {
    # Load necessary library
    if (!requireNamespace("RNetCDF", quietly = TRUE)) {
        stop("RNetCDF package is required but not installed. Please install it.")
    }
    
    # Read JSON content
    json_content <- readLines(json.file, warn = FALSE)
    json_string <- paste(json_content, collapse = "\n")
    
    # Create a new NetCDF file
    nc <- RNetCDF::create.nc(output.nc)
    
    # Add JSON data as a global attribute
    RNetCDF::put.att.nc(nc, "NC_GLOBAL", "metadata_json", "text", json_string)
    
    # Close the NetCDF file
    RNetCDF::close.nc(nc)
    
    message("[", Sys.time(), "] The NetCDF file '", output.nc, "' with encapsulated JSON metadata was successfully created.")
}



