{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://CSSPLICE.github.io/peml/schemas/PEML.json",
  "title": "PEML",
  "description": "This schema defines the data model for PEML, the Program Exercise Markup Language (https://cssplice.github.io/peml/). The data model is intended to be represented in PEML notation, but the same data model can easily be expressed in JSON or YAML or any similar structured data format, so this serves as a JSON Schema for the common data model behind the content.",

  "definitions": {
    "nonempty_string": {
      "$id": "nonempty_string",
      "type": "string",
      "minLength": 1
    },
    "string_no_whitespace": {
      "$id": "string_no_whitespace",
      "$ref": "#/definitions/nonempty_string",
      "pattern": "^[^\\s]+$"
    },
    "boolean": {
      "$id": "boolean",
      "oneOf": [
        { "type": "boolean" },
        { "type": "integer",  "minimum": "0", "maximum": "1" },
        {
          "type": "string",
          "pattern": "^(true|True|TRUE|false|False|FALSE|yes|Yes|YES|no|No|NO|on|On|ON|off|Off|OFF|0|1)$"
        }
      ]
    },
    "id": {
      "$id": "id",
      "description": "An id is a non-empty string containing no whitespace and no commas (which might be used as separators in strings denoting lists of ids).",
      "$ref": "#/definitions/nonempty_string",
      "pattern": "^[^\\s,]+$"
    },
    "id_list": {
      "$id": "id_list",
      "description": "Either a single id (no spaces or commas) or a comma-separated (or space-separated) list of multiple ids.",
      "$ref": "#/definitions/nonempty_string",
      "pattern": "^[^\\s,]+(\\s*[, ]\\s*[^\\s,]+)*$"
    },
    "timestamp": {
      "$id": "timestamp",
      "description": "A human-readable timestamp indicating the time at which this version of the exercise was last modified. For lack of a better option, at the moment this should be an RFC 3339/ISO 8601 UTC timestamp (if you know of something more user-friendly but equally unambiguous, let us know!). That format is: YYYY-MM-DDThh:mm:ss.nnn±hh:mm. See https://stackoverflow.com/questions/28020805/regex-validate-correct-iso8601-date-string-with-time for the regex info.",
      "$ref": "#/definitions/nonempty_string",
      "format": "date-time"
    },
    "email_address": {
      "$id": "email_address",
      "description": "An email_address is a non-empty string conforming to the idn-email format.",
      "$ref": "#/definitions/nonempty_string",
      "format": "idn-email"
     },
    "person": {
      "$id": "person",
      "description": "Represents an individual, including a unique email address and an optional name",
      "oneOf": [
        { "$ref": "#/definitions/email_address" },
        {
          "type": "object",
          "required": ["email"],
          "properties": {
            "email": { "$ref": "#/definitions/email_address" },
            "name": { "$ref": "#/definitions/nonempty_string" }
          }
        }
      ]
    },
    "tag_list": {
      "$id": "tag_list",
      "description": "Can be either a single string representing a list of semi-colon-delimited or comma-delimited tags/elements, or an array of strings representing the same content.",
      "oneOf": [
        { "$ref": "#/definitions/nonempty_string" },
        {
          "type": "array",
          "items": { "$ref": "#/definitions/nonempty_string" },
          "minItems": 1
        }
      ]
    },
    "location": {
      "$id": "location",
      "description": "A string that may also be a url(...) defining an addressable location.",
      "$ref": "#/definitions/nonempty_string"
    },
    "relative_location": {
      "$id": "relative_location",
      "description": "A string that intended to define a relative location, probably using a url(...).",
      "$ref": "#/definitions/location"
    },
    "file": {
      "$id": "file",
      "oneOf": [
        { "$ref": "#/definitions/location" },
        {
          "type": "object",
          "required": ["content"],
          "properties": {
            "content": {
              "oneOf": [
                { "type": "string" },
                { "type": "array" },
                { "type": "object" }
              ]
            },
            "name": { "$ref": "#/definitions/nonempty_string" },
            "type": { "$ref": "#/definitions/nonempty_string" },
            "content_encoding": { "$ref": "#/definitions/nonempty_string" }
          }
        }
      ]
    },
    "file_list": {
      "$id": "file_list",
      "oneOf": [
        { "$ref": "#/definitions/location" },
        {
          "type": "array",
          "items": { "$ref": "#/definitions/file" },
          "minItems": 1
        }
      ]
    },
    "repository": {
      "$id": "repository",
      "type": "object",
      "required": ["url"],
      "properties": {
        "url": { "$ref": "#/definitions/location" },
        "path": { "$ref": "#/definitions/nonempty_string" },
        "branch": { "$ref": "#/definitions/nonempty_string" },
        "tag": { "$ref": "#/definitions/nonempty_string" }
      }
    },
    "suite": {
      "$id": "suite",
      "oneOf": [
        { "$ref": "#/definitions/location" },
        {
          "type": "object",
          "anyOf": [
            { "required": ["content"] },
            { "required": ["cases"] }
          ],
          "properties": {
            "content": {
              "oneOf": [
                { "type": "string" },
                { "type": "array" },
                { "type": "object" }
              ]
            },
            "name": { "$ref": "#/definitions/nonempty_string" },
            "type": { "$ref": "#/definitions/nonempty_string" },
            "content_encoding": { "$ref": "#/definitions/nonempty_string" },
            "visibility": { "$ref": "#/definitions/nonempty_string" },
            "pattern": { "type": "object" },
            "template": { "$ref": "#/definitions/nonempty_string" },
            "cases": { "type": "array" }
          }
        }
      ]
    },
    "suite_list": {
      "$id": "suite_list",
      "oneOf": [
        { "$ref": "#/definitions/location" },
        {
          "type": "array",
          "items": { "$ref": "#/definitions/suite" },
          "minItems": 1
        }
      ]
    },
    "environment": {
      "$id": "environment",
      "type": "object",
      "properties": {
        "inherits": { "enum": ["start", "build", "run"] },
        "files": { "$ref": "#/definitions/file_list" },
        "repository": { "$ref": "#/definitions/repository" },
        "image": { "$ref": "#/definitions/nonempty_string" },
        "registry": { "$ref": "#/definitions/location" }
      }
    },
    "environment_list": {
      "$id": "environment_list",
      "type": "object",
      "properties": {
        "start": { "$ref": "#/definitions/environment" },
        "build": { "$ref": "#/definitions/environment" },
        "run":   { "$ref": "#/definitions/environment" },
        "test":  { "$ref": "#/definitions/environment" }
      }
    },
    "solution": {
      "$id": "solution",
      "oneOf": [
        { "$ref": "#/definitions/location" },
        {
          "type": "object",
          "properties": {
            "name": { "$ref": "#/definitions/nonempty_string" },
            "description": { "type": "string" },
            "visibility": { "$ref": "#/definitions/nonempty_string" },
            "correct": { "$ref": "#/definitions/boolean" },
            "reference": { "$ref": "#/definitions/boolean" },
            "files": { "$ref": "#/definitions/file_list" }
          }
        }
      ]
    },
    "solution_list": {
      "$id": "solution_list",
      "oneOf": [
        { "$ref": "#/definitions/location" },
        {
          "type": "array",
          "items": { "$ref": "#/definitions/solution" },
          "minItems": 1
        }
      ]
    },
    "system": {
      "$id": "system",
      "type": "object",
      "properties": {
        "language": { "$ref": "#/definitions/nonempty_string" },
        "version": { "$ref": "#/definitions/nonempty_string" },
        "environment": { "$ref": "#/definitions/environment_list" },
        "suites": { "$ref": "#/definitions/suite_list" },
        "src": {
          "type": "object",
          "properties": {
            "files": { "$ref": "#/definitions/file_list" },
            "starter": {
              "type": "object",
              "required": ["files"],
              "properties": { "files": { "$ref": "#/definitions/file_list" } }
            },
            "frame": {
              "type": "object",
              "required": ["files"],
              "properties": { "files": { "$ref": "#/definitions/file_list" } }
            },
            "solutions": { "$ref": "#/definitions/solution_list" }
          }
        }
      }
    },
    "system_list": {
      "$id": "system_list",
      "type": "array",
      "items": { "$ref": "#/definitions/system" },
      "minItems": 1
    }
  },

  "type": "object",

  "required": ["exercise_id", "title"],
  "allOf": [
    { "anyOf": [
      { "required": ["instructions"] },
      { "required": ["suites"] },
      { "required": ["systems"] }
    ] },
    { "anyOf": [
      { "required": ["author"] },
      { "required": ["authors"] },
      { "required": ["license"] }
    ] }
  ],

  "properties": {
    "exercise_id": { "$ref": "#/definitions/id" },
    "title": { "type": "string" },
    "author": { "$ref": "#/definitions/person" },
    "authors": {
      "description": "An array of multiple authors.",
      "type": "array",
      "items": { "$ref": "#/definitions/person" },
      "minItems": 1
    },
    "tag": {
      "type": "object",
      "properties": {
        "topics": { "$ref": "tag_list" },
        "prerequisites": {
          "oneOf": [
            { "$ref": "tag_list" },
            {
              "type": "object",
              "properties": {
                "exposure": { "$ref": "tag_list" },
                "familiarity": { "$ref": "tag_list" },
                "mastery": { "$ref": "tag_list" }
              }
            }
          ]
         },
        "style": { "$ref": "tag_list" },
        "course": { "$ref": "tag_list" },
        "book": { "$ref": "tag_list" },
        "personal": { "$ref": "tag_list" }
      }
    },
    "version": {
      "type": "object",
      "properties": {
        "timestamp": { "$ref": "#/definitions/timestamp" },
        "type": { "$ref": "#/definitions/nonempty_string" },
        "id": { "$ref": "#/definitions/nonempty_string" },
        "repository": { "$ref": "#/definitions/repository" },
        "location": { "$ref": "#/definitions/relative_location" }
      }
    },
    "license": {
      "type": "object",
      "required": ["id", "owner"],
      "properties": {
        "id": { "$ref": "#/definitions/nonempty_string" },
        "owner": { "$ref": "#/definitions/person" },
        "book": { "$ref": "#/definitions/nonempty_string" },
        "attribution": { "$ref": "#/definitions/nonempty_string" },
        "acknowledgements": { "type": "string" },
        "acknowledgments": { "type": "string" }
      }
    },
    "difficulty": {
      "type": "integer",
      "minimum": 0,
      "maximum": 100
    },
    "instructions": { "type": "string" },
    "public_html": { "$ref": "#/definitions/file_list" },
    "environment": { "$ref": "#/definitions/environment_list" },
    "suites": { "$ref": "#/definitions/suite_list" },
    "systems": { "$ref": "#/definitions/system_list" }
  }
}
