<?php error_reporting(0); 
$schema=$_GET['schema'];
if(!$schema){
    $schema=str_replace('/doc','/',$_SERVER['REQUEST_URI']);
}
if(!$schema){
    die("No schema found. Specify in querystring, ?schema=/uri/of/schema or arrange so that /path/to/schema/doc points here.");
}

?><!DOCTYPE html>
<html lang="">
<head>
<title>SchemaDoc</title>
<style>
body { background-color:#ccc; font-family:verdana,arial,helvetica,sans-serif; }
div.object { border:1px solid #000; padding:0.25em; margin:.5em .5em 1em 2em; background-color:#fff; }
h2 { color:#600; }
h3 { background-color:#666; color:#ccc; font-weight:bold; margin:0 0 .25em 0; line-height:1.5em; }
div.property { clear:both; background-color:#ddd; margin:0 0 .25em 0; min-height:1.5em;}
div.property span.label { display:inline-block; width:18em; font-family:monospace; font-weight:bold; }
div.property span.proptype {  display:inline-block; margin-left:1em; }
div.property div.description { padding-left:20px; border-top:1px solid #999; }
div.property div.description+div.description { border-top:none; }
div.example { padding-left:20px; font-size:80%; color:#333; }
div.clear { font-size:0; clear:both; }
div.required {}
span.required { font-weight:bold; color:#c00; }
a { font-weight:bold; color:#600; }
</style>
<script type="text/javascript" src="/js/prototype1_7_2.js"></script>
<script type="text/javascript">
    const schemaUri = "<?php echo $schema; ?>";
    let schema = null;

    function getSchema(){
	new Ajax.Request(schemaUri,{
		method:'get',
		onSuccess:renderSchema,
		onFailure:handleFetchError
	});
}

function handleFetchError(transport){
    document.body.innerHTML="Could not find JSON schema. HTTP response code: "+transport.status;
}

function renderSchema(transport){
	document.body.innerHTML='';

	let dv=document.createElement("div");
	dv.classList.add("object");
	dv.innerHTML='<p>Documentation of schema found at <a href="'+schemaUri+'">'+schemaUri+'</a></p>';
	document.body.appendChild(dv);
	
	schema=transport.responseJSON;
	renderObject(schema, document.body);
	renderDefinitions();
}

function renderDefinitions(){
	if(!schema["definitions"]){ return false; }
	const dv=document.createElement("div");
	dv.classList.add("object");

    const title = document.createElement("h3");
    title.innerHTML="Definitions";
	dv.appendChild(title);

    const keys = Object.keys(schema["definitions"]).sort();
    for(let i=0;i<keys.length;i++){
		let def=document.createElement("div");
		def.classList.add("property");
		def.innerHTML+=getObjectLinkByPath('#/definitions/'+keys[i])+" ";
		dv.appendChild(def);
	}
	document.body.appendChild(dv);
}

function renderObject(obj, parentElement){
    const dv = document.createElement("div");
    dv.classList.add("object");
	
	if(obj.title){
        const title = document.createElement("h2");
        title.innerHTML=obj.title;
		dv.appendChild(title);
	}
	if(obj.description){
        const desc = document.createElement("div");
        desc.innerHTML=obj.description;
		dv.appendChild(desc);
	}
	if(obj["oneOf"]){
    	renderObjectOneOf(obj["oneOf"], dv);
	}
	if(obj["anyOf"]){
    	renderObjectAnyOf(obj["anyOf"], dv);
	}
	if(obj["allOf"]){
    	renderObjectAllOf(obj["allOf"], dv);
	}
	if(obj["properties"]){
        const propHead = document.createElement("h3");
        propHead.innerHTML="Properties";
		dv.appendChild(propHead);
    	Object.keys(obj["properties"]).forEach(function(k){
            const isRequired = (obj.required && -1 < obj.required.indexOf(k));
            renderProperty(obj["properties"], k, dv, isRequired);
    	});
	}
	parentElement.appendChild(dv);
}

function renderProperty(properties, key, parentElement, isRequired){
    const d = document.createElement("div");
    d.classList.add("property");
	if(isRequired){
		d.classList.add("required");
	}
    const prop = properties[key];
    const propType = prop.type;
    if("object"===propType){
		renderObject(prop,d);
	}
    const lbl = document.createElement("span");
    lbl.classList.add("label");
	lbl.innerHTML=key;
	d.appendChild(lbl);

    const pt = document.createElement("span");
    pt.classList.add("proptype");
	if(undefined!==propType){
		pt.innerHTML="";
		if(isRequired){
			pt.innerHTML+='<span class="required">Required.</span> ';
		}
		if("array"===propType){
			let objects="";
			if(prop["items"]["$ref"]){
				pt.innerHTML+="Array of "+getObjectLinkByPath(prop["items"]["$ref"])+" objects. ";
			} else {
				pt.innerHTML+="Array of "+objects+" objects. ";
			}
			if(prop["minItems"]){
				pt.innerHTML+="Minimum "+prop["minItems"]+". ";
			}
			if(prop["maxItems"]){
				pt.innerHTML+="Minimum "+prop["maxItems"]+". ";
			}
		} else if("string"===propType){
			if(prop.format){
				switch(prop.format){
					case 'email':
						pt.innerHTML+="Valid email address. ";
						break;
					case 'uri':
						pt.innerHTML+="Valid URI. ";
						break;
					case 'date':
						pt.innerHTML+="Date in YYYY-MM-DD format. ";
						break;
					default: 
						pt.innerHTML+=prop.format+". ";
				}
			} else {
				pt.innerHTML+="String. ";
			}
			if(prop['enum']){
				if(1===prop['enum'].length){
					pt.innerHTML+='Must be "'+prop['enum'][0]+'". '; 
				} else {
					pt.innerHTML+='Must be one of: "'+prop['enum'].join('", "')+'". '; 
				}
			}
		} else if("integer"===propType || "number"===propType){
			if("integer"===propType){
				pt.innerHTML+="Integer. ";
			} else {
				pt.innerHTML+="Number. ";
			}
			if(undefined!==prop["multipleOf"]){
				pt.innerHTML+="Multiple of "+prop["multipleOf"]+". ";
			}
			if(undefined!==prop["minimum"]){
				if(undefined!==prop["exclusiveMinimum"]){
					pt.innerHTML+="Greater than "+prop["minimum"]+". ";
				} else {
					pt.innerHTML+="Minimum "+prop["minimum"]+". ";
				}
			}
			if(undefined!==prop["maximum"]){
    			if(undefined!==prop["exclusiveMaximum"]){
    				pt.innerHTML+="Less than "+prop["maximum"]+". ";
    			} else {
    				pt.innerHTML+="Maximum "+prop["maximum"]+". ";
    			}
			}
		} else {
			pt.innerHTML+=propType;
		}
	} else if(prop['$ref']){
        /*
        const ref = getObjectByPath(prop['$ref']);
        const parts = prop['$ref'].split("/");
        let objType = parts[parts.length - 1];
        if(ref['title']){
			objType=ref['title'];
		}
		*/
		if(isRequired){
			pt.innerHTML+='<span class="required">Required.</span> ';
		}
		pt.innerHTML+="Object: "+getObjectLinkByPath(prop["$ref"]);
	}
	d.appendChild(pt);
	
	if(prop.description){
        const desc = document.createElement("div");
        desc.classList.add("description");
        desc.innerHTML='' + prop.description;
		d.appendChild(desc);
	}

	if("array"===propType && prop.items){
		if(prop.items["oneOf"]){
	    	renderArrayOneOf(prop.items["oneOf"], d);
		}
		if(prop.items["anyOf"]){
	    	renderArrayAnyOf(prop.items["anyOf"], d);
		}
		if(prop.items["allOf"]){
	    	renderArrayAllOf(prop.items["allOf"], d);
		}
	}

	if(prop["examples"]){
        const ex = document.createElement("div");
        ex.classList.add("example");
		let txt='';
		txt+="Examples:<ul><li>"+prop["examples"].join("</li><li>")+"</li></ul>";
		ex.innerHTML=txt;
		d.appendChild(ex);
	}
	
	let cl=document.createElement("div");
	cl.classList.add("clear");
	cl.innerHTML="&nbsp;";
	d.appendChild(cl);

	
	parentElement.appendChild(d);
}

function renderArrayOneOf(oneOf, parentElement){
	renderArrayConditions(oneOf, parentElement, "Each array item must meet exactly one of the following conditions:");
}
function renderArrayAllOf(allOf, parentElement){
	renderArrayConditions(allOf, parentElement, "Each array item must meet all of the following conditions:");
}
function renderArrayAnyOf(anyOf, parentElement){
	renderArrayConditions(anyOf, parentElement, "Each array item must meet at least one of the following conditions:");
}
function renderArrayConditions(conditions, parentElement, header){
    const dv = document.createElement("div");
    dv.classList.add("description");
    const oo = document.createElement("h3");
    oo.innerHTML=header;
	dv.appendChild(oo);
	for(let i=0;i<conditions.length;i++){
		let cond=conditions[i];
		let txt="";
		if(cond.required){
			txt="Property <strong>"+cond.required+"</strong> must be specified";
		} else if(cond["$ref"]){
            let title = cond["$ref"];
            const ob = getObjectByPath(cond["$ref"]);
            if(ob.title){ title=ob.title; }
			txt="Array item is a "+getObjectLinkByPath(cond["$ref"])+"";
		}
		let d=document.createElement("div");
		d.classList.add("property");
		d.innerHTML=txt;
		dv.appendChild(d);
	}
	parentElement.appendChild(dv);
}
function renderObjectOneOf(oneOf, parentElement){
	renderObjectConditions(oneOf, parentElement, "Exactly one of the following conditions must be met:");
}
function renderObjectAllOf(allOf, parentElement){
	renderObjectConditions(allOf, parentElement, "All of the following conditions must be met:");
}
function renderObjectAnyOf(anyOf, parentElement){
	renderObjectConditions(anyOf, parentElement, "At least one of the following conditions must be met:");
}
function renderObjectConditions(conditions, parentElement, header){
	let oo=document.createElement("h3");
	oo.innerHTML=header;
	parentElement.appendChild(oo);
	for(let i=0;i<conditions.length;i++){
		let cond=conditions[i];
		let txt="";
		if(cond.required){
			txt="Property <strong>"+cond.required+"</strong> must be specified";
		} else if(cond["$ref"]){
			let title=cond["$ref"];
			let ob=getObjectByPath(cond["$ref"]);
			if(ob.title){ title=ob.title; }
			txt="A "+getObjectLinkByPath(cond["$ref"])+" object must be present";
		}
		let d=document.createElement("div");
		d.classList.add("property");
		d.innerHTML=txt;
		parentElement.appendChild(d);
	}
}


function toggleChild(elem, path){
	let wrap=elem.closest("div");
	if(!wrap.obj){
		wrap.obj=getObjectByPath(path);
	}
	let objDiv=wrap.querySelector("div.object");
	if(objDiv){ 
		objDiv.remove();
	} else {
		renderObject(wrap.obj, wrap);
	}
}

function getObjectLinkByPath(path){
	let obj=getObjectByPath(path);
	let parts=path.split("/");
	let title=parts[parts.length-1];
	if(obj.title){ title=obj.title; }
    return "<a href=\"#\" onclick=\"toggleChild(this,'" + path + "');return false\">" + title + "</a>";
}

function getObjectByPath(path){
	let parts=path.split("/");
	let obj=schema;
	for(let i=0;i<parts.length;i++){
		let part=parts[i];
		if("#"===part){ continue; }
		if(!obj[part]){
			return { "error":"No object defined at "+path };
		}
		obj=obj[part];
	}
	return obj;
}

</script>
</head>
<body>
loading...
<script type="text/javascript">
window.setTimeout(getSchema, 100);
</script>
<footer>SchemaDoc v1.0 &copy; 2019 Ed Daniel</footer>
</body>
</html>
