var profiles = {};
var profileNames = [];
var posts = [];

var activePost = null;
var curPostMinutes = 0;
var curCommentMinutes = 0;

var STYLE_IMAGE_FILLER = "linear-gradient(rgba(0, 0, 0, 0.527),rgba(0, 0, 0, 0.5)), url('$URL')";

var URL_IMG_POSTED = "Icons/Posted.png";
var URL_IMG_LIKES = "Icons/Likes.png";
var URL_IMG_COMMENTS = "Icons/Comments.png";
var URL_IMG_COMMUNITY = "Icons/Community.png";

var EMOJI_VERIFIED = "💎";

var REGEX_HANDLE = /([@#].+?(\s|$))/g;

// Hooking into the global document click, for grabbing just link clicks.
document.addEventListener("click", function(e) {
	let origin = e.target.closest("a");
	
	if (origin) {
		e.stopPropagation();
		
		if (e.target.id == "#allposts") {
			unhideAllPosts();
			return true;
		}
		
		let href = e.target.hash;
		let tagType = href[1]; // First character is always a #, to avoid link shenanigans. Second character is the tag type, and everything after is the actual tag-name.
		let tagContent = href.substring(2);
		
		// Tapping specific characters
		if (tagType == '@') {
			let taggedProfile = null;
			
			for (let i=0; i < profileNames.length; i++) {
				let internalName = profileNames[i];
				let displayName = profiles[internalName].displayName;
				
				if (displayName == tagContent) {
					taggedProfile = profiles[internalName];
					break;
				}
			}
			
			if (taggedProfile == null) {
				return true;
			}
			
			setActivePost(null);
			unhideAllPosts();
			
			for (let i=0; i < posts.length; i++) {
				let post = posts[i];
				
				if (post.postedBy != taggedProfile) {
					hidePost(post);
				}
			}
		}
		
		// Tapping specific tags
		if (tagType == '#') {
			setActivePost(null);
			unhideAllPosts();

			for (let i=0; i < posts.length; i++) {
				let post = posts[i];
				
				let hasTag = ((post.postCaption.includes(tagContent)) || (post.postText.includes(tagContent)))
				if (!hasTag) {
					hidePost(post);
				}
			}
		}
	}
});
	
// Helper functions
// https://stackoverflow.com/a/65084005
function abbreviateNumber(val) {
	let unitPowers = [-12,-9,-6,-3,0,3,6,9,12,15]
	let unitNames = ["T","G","M","K","","m","µ","n","p","f"];
	
	//let units = {-12: "T",-9: "G",-6: "M",-3: "K",0: "",3: "m",6: "µ",9: "n",12: "p",15: "f"}
	let i = 0;
	
	while ((val * (10.00 ** unitPowers[i])) < 1)
		i ++;
		
	let valuePowered = val * (10.00 ** unitPowers[i]);
	let valueUnit = unitNames[i];
	
	let valuePoweredString = valuePowered.toString().slice(0,4);
	if (valuePoweredString[3] == ".") valuePoweredString = valuePoweredString.slice(0,3);
	
	let valuePowerString = `${valuePoweredString}${valueUnit}`;
	
	return valuePowerString;
}

function generateFormattedTextSpan(strText, existingContainerSpan=null) {
	let containerSpan = existingContainerSpan;
	
	if (containerSpan === null) {
		containerSpan = document.createElement("span");
	}
	
	// Newline splitting.
	let stringSplit = strText.split("\\n");
	
	for (let i=0; i < stringSplit.length; i++) {
		let curString = stringSplit[i];
		
		let tagSplit = curString.split(REGEX_HANDLE);
		let isTag = 0; // The way the regex splits the string is it becomes [not tag, TAG, not tag, not tag, TAG, not tag, not tag, TAG, ...]. One not-tag, a tag, then a cycle of 2 not-tags and 1 tag. So this stupid thing counts to 3 to manage that.
		
		for (let j=0; j < tagSplit.length; j++) {
			let tagString = tagSplit[j];
			
			if (isTag == 1) {
				let postTagString = "";
				
				// If the tag is going to a character's internal name, then replace it with their handle.
				if (tagString[0] == "@") {
					for (let k=0; k < profileNames.length; k++) {
						let internalName = profileNames[k];
						let displayName = profiles[internalName].displayName
						
						if (tagString.includes(internalName)) {
							//tagString = tagString.replace(internalName, displayName);

							postTagString = tagString.replace("@" + internalName,"");
							tagString = "@" + displayName
						}
					}
				}

				let hrefNode = document.createElement("a");
				hrefNode.setAttribute("href","#" + tagString); // TODO: Actually make this do something later.
				
				let textNode = document.createTextNode(tagString);
				hrefNode.appendChild(textNode);

				containerSpan.appendChild(hrefNode);
				
				if (postTagString.length > 0) {
					let postTextNode = document.createTextNode(postTagString);
					containerSpan.appendChild(postTextNode);
				}
			} else {
				let textNode = document.createTextNode(tagString);
				
				containerSpan.appendChild(textNode);
			}
			
			isTag++;
			
			if (isTag > 2) {
				isTag = 0;
			}
		}
			
		let breakNode = document.createElement("br");
		containerSpan.appendChild(breakNode);
	}
	
	return containerSpan;
}

// https://stackoverflow.com/a/49844499
function getPostTime(timeInMinutes, resolution=5) {
	let minutes = 1;
	let hours = minutes * 60;
	let days = hours * 24;
	let weeks = days * 7;
	let months = weeks * 4;
	
	let numMonths = Math.floor(timeInMinutes / months);
	let numWeeks = Math.floor((timeInMinutes - (numMonths * months)) / weeks);
	let numDays = Math.floor((timeInMinutes - (numMonths * months) - (numWeeks * weeks)) / days);
	let numHours = Math.floor((timeInMinutes - (numMonths * months) - (numWeeks * weeks) - (numDays * days)) / hours);
	let numMinutes = timeInMinutes % hours;
	
	let timeValues = [numMonths, numWeeks, numDays, numHours, numMinutes];
	let timeLabels = ["mo", "wk", "day", "hr", "min"];
	
	let returnStr = "";
	
	for (let i=0; i < resolution; i++) {
		let curValue = timeValues[i];
		let curLabel = timeLabels[i];
		
		if (curValue < 1) {
			continue;
		}
		
		if (curValue > 1) {
			curLabel = curLabel + "s";
		}
		
		returnStr += `${curValue} ${curLabel} `;
	}
	
	returnStr += "ago";
	
	return returnStr;
}

function computeStat(timeInMinutes, statMultiplier, statBase) {
	return Math.floor(statMultiplier * (Math.log(timeInMinutes) / Math.log(statBase)));
}

function clearAllChildren(parentNode) {
	while (parentNode.lastChild) {
		parentNode.removeChild(parentNode.lastChild);
	}
}

function generateCaptionedPhoto(imageUrl, captionText) {
	let postImageContainerDiv = document.createElement("div");
	postImageContainerDiv.classList.add("post-image-container");
	postImageContainerDiv.classList.add("centered");
	
	postImageContainerDiv.imageUrl = imageUrl;
	
	let postImageFillerStyle = STYLE_IMAGE_FILLER.replace("$URL",imageUrl);					
	let postImageFillerDiv = document.createElement("div");
	postImageFillerDiv.classList.add("post-image-filler");
	postImageFillerDiv.classList.add("centered");
	postImageFillerDiv.classList.add("clickable");
	postImageFillerDiv.style.backgroundImage = postImageFillerStyle;
	postImageFillerDiv.setAttribute("title","Click to view full-size");
	postImageFillerDiv.addEventListener("click",function() {
		window.open(this.imageUrl,"Image");
	}.bind(postImageContainerDiv));
	postImageContainerDiv.appendChild(postImageFillerDiv);
	
	let postImageDiv = document.createElement("div");
	postImageDiv.classList.add("post-image");
	postImageContainerDiv.appendChild(postImageDiv);
	
	let postImageImg = document.createElement("img");
	postImageImg.classList.add("clickable");
	postImageImg.setAttribute("src",imageUrl);
	postImageImg.setAttribute("title","Click to view full-size");
	postImageImg.style.height = "100%";
	postImageDiv.appendChild(postImageImg);
	postImageImg.addEventListener("click",function() {
		window.open(this.imageUrl,"Image");
	}.bind(postImageContainerDiv));
	
	if (captionText != null) {
		let postImageCaptionSpan = document.createElement("span");
		generateFormattedTextSpan(captionText,postImageCaptionSpan);
		postImageCaptionSpan.classList.add("post-image-caption");
		postImageDiv.appendChild(postImageCaptionSpan);
	}
	
	return postImageContainerDiv;
}

function generateGalleryPhoto(imageUrl, imageIdx, postData) {
	let postImageContainerDiv = document.createElement("div");
	postImageContainerDiv.classList.add("gallery-image-container");
	postImageContainerDiv.classList.add("centered");
	
	postImageContainerDiv.postData = postData;
	postImageContainerDiv.imageIdx = imageIdx;
	
	let postImageFillerStyle = STYLE_IMAGE_FILLER.replace("$URL",imageUrl);					
	let postImageFillerDiv = document.createElement("div");
	postImageFillerDiv.classList.add("post-image-filler");
	postImageFillerDiv.classList.add("centered");
	postImageFillerDiv.classList.add("clickable");
	postImageFillerDiv.style.backgroundImage = postImageFillerStyle;
	postImageFillerDiv.setAttribute("title","Click to view image");
	postImageFillerDiv.addEventListener("click",function() {
		this.postData.updateGallery(imageIdx);
	}.bind(postImageContainerDiv));
	postImageContainerDiv.appendChild(postImageFillerDiv);
	
	let postImageDiv = document.createElement("div");
	postImageDiv.classList.add("post-image");
	postImageContainerDiv.appendChild(postImageDiv);
	
	let postImageImg = document.createElement("img");
	postImageImg.classList.add("clickable");
	postImageImg.setAttribute("src",imageUrl);
	postImageImg.setAttribute("title","Click to view full-size");
	postImageImg.style.height = "100%";
	postImageDiv.appendChild(postImageImg);
	postImageImg.addEventListener("click",function() {
		this.postData.updateGallery(imageIdx);
	}.bind(postImageContainerDiv));
	
	return postImageContainerDiv;	
}

function setActivePost(post) {
	let postsContainer = document.getElementById("posts");
	let commentsContainer = document.getElementById("comments");
	
	commentsContainer.style.display = "none";
	
	if (activePost !== null) activePost.postDiv.classList.remove("active");
	
	if (post !== null) {
		post.postDiv.classList.add("active");
		
		if (post.commentPosts.length > 0) {
			commentsContainer.style.display = "inline-block";
			
			clearAllChildren(commentsContainer);
			
			for (let i=0; i < post.commentPosts.length; i++) {
				let commentDiv = post.commentPosts[i].generate();
				
				commentsContainer.appendChild(commentDiv);
			}
		}
	}
	
	activePost = post;
}

function hidePost(post) {
	post.postDiv.classList.add("hidden");
}

function unhideAllPosts() {
	// Unhide all posts
	for (let i=0; i < posts.length; i++) {
		let post = posts[i];
		
		post.postDiv.classList.remove("hidden");
	}
}
// Classes
class Profile extends HTMLElement {
		
	constructor() {
		super();
		
		this.characterName = "Unnamed"
		this.displayName = "unnamed";
		this.profileTagline = "This is my tagline"
		this.profileImage = "https://ih1.redbubble.net/image.475950724.2535/st,small,507x507-pad,600x600,f8f8f8.u6.jpg"; // Missing image icon
		this.numFollowers = 42069;
		this.verified = false;
	}
	
	connectedCallback() {
		this.characterName = this.hasAttribute("charName") ? this.getAttribute("charName") : this.characterName;
		this.displayName = this.hasAttribute("displayName") ? this.getAttribute("displayName") : this.displayName;
		this.profileTagline = this.hasAttribute("tagline") ? this.getAttribute("tagline") : this.profileTagline;
		this.profileImage = this.hasAttribute("avatar") ? this.getAttribute("avatar") : this.profileImage;
		this.numFollowers = this.hasAttribute("followers") ? parseFloat(this.getAttribute("followers")) : this.numFollowers;
		this.verified = this.hasAttribute("verified");
		
		profiles[this.characterName] = this;
		profileNames.push(this.characterName);
	}
	
	generate(bPosted = false) {
		let rootDiv = document.createElement("div");
		rootDiv.classList.add("post-body");
		rootDiv.classList.add("profile-info");
		
		let avatarImg = document.createElement("img");
		avatarImg.classList.add("avatar");
		avatarImg.setAttribute("src",this.profileImage);
		rootDiv.appendChild(avatarImg);
		
		let nameSpan = document.createElement("span");
		nameSpan.classList.add("profile-name");
		rootDiv.appendChild(nameSpan);
		
		if (this.verified) {
			let verifiedText = document.createTextNode(EMOJI_VERIFIED);
			nameSpan.appendChild(verifiedText);
		}
		
		let nameText = document.createTextNode(this.displayName);
		nameSpan.appendChild(nameText);
		
		let taglineSpan = generateFormattedTextSpan(this.profileTagline);
		taglineSpan.classList.add("profile-tagline");
		rootDiv.appendChild(taglineSpan);
				
		if (bPosted) {
			let imgPostedContainer = document.createElement("div");
			imgPostedContainer.classList.add("posted-icon-container");
			
			// Followers
			let followersContainer = document.createElement("div");
			followersContainer.style.float = "left";
			
			let followersNumberSpan = document.createElement("span");
			followersNumberSpan.classList.add("profile-followers");
			followersNumberSpan.classList.add("profile-followers-bold");
			followersNumberSpan.innerText = abbreviateNumber(this.numFollowers);
			
			let followersTextSpan = document.createElement("span");
			followersTextSpan.classList.add("profile-followers");
			followersTextSpan.innerText = " Followers";
			
			let followersSpan = document.createElement("span");
			followersSpan.appendChild(followersNumberSpan);
			followersSpan.appendChild(followersTextSpan);
			
			let followersBreak = document.createElement("br");
			followersSpan.appendChild(followersBreak);
			
			// "Click to read all posts"
			let readAllLink = document.createElement("span");
			readAllLink.classList.add("profile-followers");
			readAllLink.classList.add("psuedolink");
			readAllLink.innerText = "Read All Posts"
			
			// "Posted" image
			let imgPosted = document.createElement("img");
			imgPosted.classList.add("posted-icon");
			imgPosted.setAttribute("src",URL_IMG_POSTED);
			
			followersContainer.appendChild(followersSpan);
			followersContainer.appendChild(readAllLink);
			
			imgPostedContainer.appendChild(followersContainer);
			imgPostedContainer.appendChild(imgPosted);
			rootDiv.appendChild(imgPostedContainer);
		}
		
		return rootDiv;
	}
}

class Post extends HTMLElement {
	constructor() {
		super();
		
		this.postedBy = null;
		this.postedTime = 37;
		this.postIdx = 0;
		this.timeFirst = 21;
		this.timeAfter = 666;
		this.postImage = "https://ih1.redbubble.net/image.475950724.2535/st,small,507x507-pad,600x600,f8f8f8.u6.jpg"; // Missing image icon
		this.postText = "Hello there! General Kenobi...";
		this.postCaption = null;
		this.numLikes = 420;
		this.numShares = 69;
		this.numComments = 18;
		this.likesMultiplier = 85;
		this.likesBase = 2;
		this.sharesMultiplier = 85;
		this.sharesBase = 2;
		this.commentsMultiplier = 85;
		this.commentsBase = 2;
		
		this.commentPosts = [];
		this.postImages = [];
	}
	
	connectedCallback() {
		this.postedBy = 			this.hasAttribute("from") 			? profiles[this.getAttribute("from")] 			: this.postedBy;
		this.timeFirst = 			this.hasAttribute("timeFirst") 		? parseFloat(this.getAttribute("timeFirst")) 	: this.timeFirst;
		this.timeAfter = 			this.hasAttribute("timeAfter") 		? parseFloat(this.getAttribute("timeAfter")) 	: this.timeAfter;
		this.postImage = 			this.hasAttribute("img") 			? this.getAttribute("img") 						: this.postImage;
		this.postText = 			this.hasAttribute("post") 			? this.getAttribute("post") 					: this.postText;
		this.postCaption = 			this.hasAttribute("caption") 		? this.getAttribute("caption") 					: this.postCaption;
		this.likesMultiplier = 		this.hasAttribute("likesMult") 		? parseFloat(this.getAttribute("likesMult")) 	: this.likesMultiplier;
		this.sharesMultiplier = 	this.hasAttribute("sharesMult") 	? parseFloat(this.getAttribute("sharesMult")) 	: this.sharesMultiplier;
		this.commentsMultiplier = 	this.hasAttribute("commentsMult") 	? parseFloat(this.getAttribute("commentsMult")) : this.commentsMultiplier;
		this.likesBase = 			this.hasAttribute("likesBase") 		? parseFloat(this.getAttribute("likesBase")) 	: this.likesBase;
		this.sharesBase = 			this.hasAttribute("sharesBase") 	? parseFloat(this.getAttribute("sharesBase")) 	: this.sharesBase;
		this.commentsBase = 		this.hasAttribute("commentsBase") 	? parseFloat(this.getAttribute("commentsBase")) : this.commentsBase;
		this.postDiv = null;
		
		this.postImages.push([this.postImage, this.postCaption]);
		this.curPostImage = 0;
		this.postGalleryContainerDiv = null;
		this.postImageContainerDiv = null;
		
		if (this.postedBy != null) {
			if (this.hasAttribute("active")) {
				activePost = this;
			}
			
			let i = posts.length;
			
			if (i == 0) {
				curPostMinutes = this.timeFirst;
			}
			
			this.postedTime = curPostMinutes;
			this.basePostedTime = curPostMinutes;
			this.numLikes = computeStat(this.postedTime, this.likesMultiplier, this.likesBase);
			this.numComments = computeStat(this.postedTime, this.commentsMultiplier, this.commentsBase);
			this.numShares = computeStat(this.postedTime, this.sharesMultiplier, this.sharesBase);
			this.postIdx = i;
			
			curPostMinutes += this.timeAfter;
			
			posts.push(this);
		}
	}
	
	updateGallery(galleryIdx) {
		this.curPostImage = galleryIdx;
		
		let postBodyDiv = this.postImageContainerDiv.parentNode;
		
		let newPostImageContainerDiv = generateCaptionedPhoto(this.postImages[galleryIdx][0], this.postImages[galleryIdx][1]);
		postBodyDiv.replaceChild(newPostImageContainerDiv, this.postImageContainerDiv);
		
		this.postImageContainerDiv = newPostImageContainerDiv;
		
		if (this.postImages.length > 1) {
			while (this.postGalleryContainerDiv.firstChild) {
				this.postGalleryContainerDiv.removeChild(this.postGalleryContainerDiv.firstChild);
			}

			for (let i=0; i < this.postImages.length; i++) {
				if (i == this.curPostImage) {
					continue;
				}
				
				let postGalleryImage = generateGalleryPhoto(this.postImages[i][0], i, this);
				this.postGalleryContainerDiv.append(postGalleryImage);
			}
		}
	}
	
	generate() {
		let postDiv = document.createElement("div");
		postDiv.classList.add("post");
			
		if (activePost == this) {
			postDiv.classList.add("active");
		}
		
		this.postDiv = postDiv;
		
		postDiv.addEventListener("click",function(e) {
			if (e.target.closest("a")) {
				return true;
			}
			
			if (activePost != this) {
				setActivePost(this);
				
				// Hide all of the posts that come before the active post.
				for (let i=0; i < posts.length; i++) {
					let post = posts[i];
					
					if (post == this) {
						break;
					}
					
					post.postDiv.classList.add("hidden");
				}
			} else {
				if (e.target.classList.contains("clickable")) {
					e.stopPropagation();
					return true;
				}
				
				setActivePost(null);
				unhideAllPosts();
			}
		}.bind(this));
		
		let profileDiv = this.postedBy.generate(true);
		profileDiv.addEventListener("click",function(e) {
			if (e.target.closest("a")) {
				return true;
			}
			
			if (activePost != this) {
				setActivePost(this);
				// Hide all posts that aren't made by this poster.
				for (let i=0; i < posts.length; i++) {
					let post = posts[i];
					
					if (post.postedBy != this.postedBy) {
						hidePost(post);
					}
				}
			} else {
				unhideAllPosts();
				setActivePost(null);
			}
			
			e.stopPropagation();
		}.bind(this));
		postDiv.appendChild(profileDiv);
		
		let postBodyDiv = document.createElement("div");
		postBodyDiv.classList.add("post-body");
		postBodyDiv.classList.add("centered");
		postDiv.appendChild(postBodyDiv);
		
		let timeSpan = document.createElement("span");
		let timeSpanText = document.createTextNode(getPostTime(this.postedTime));
		timeSpan.classList.add("posted-time");
		timeSpan.appendChild(timeSpanText);
		postBodyDiv.appendChild(timeSpan);
		
		let postImageContainerDiv = generateCaptionedPhoto(this.postImage, this.postCaption);
		postBodyDiv.appendChild(postImageContainerDiv);
		
		this.postImageContainerDiv = postImageContainerDiv;
		
		if (this.postImages.length > 1) {
			let postGalleryContainerDiv = document.createElement("div");
			postGalleryContainerDiv.classList.add("gallery-container");
			
			postBodyDiv.appendChild(postGalleryContainerDiv);
			
			this.postGalleryContainerDiv = postGalleryContainerDiv;
			
			for (let i=0; i < this.postImages.length; i++) {
				if (i == this.curPostImage) {
					continue;
				}
				
				let postGalleryImage = generateGalleryPhoto(this.postImages[i][0], i, this);
				postGalleryContainerDiv.append(postGalleryImage);
			}
		}
		
		let rowDiv = document.createElement("div");
		rowDiv.classList.add("row");
		postBodyDiv.appendChild(rowDiv);
		
		let columnLeftDiv = document.createElement("div");
		columnLeftDiv.classList.add("post-content-container");
		columnLeftDiv.classList.add("column");
		columnLeftDiv.classList.add("left");
		rowDiv.appendChild(columnLeftDiv);
		
		let postTextParagraph = document.createElement("p");
		generateFormattedTextSpan(this.postText, postTextParagraph);
		columnLeftDiv.appendChild(postTextParagraph);
		
		let columnRightDiv = document.createElement("div");
		columnRightDiv.classList.add("post-content-stats");
		columnRightDiv.classList.add("column");
		columnRightDiv.classList.add("right");
		rowDiv.appendChild(columnRightDiv);
		
		let imgStatLikes = document.createElement("img");
		let imgStatComments = document.createElement("img");
		let imgStatCommunity = document.createElement("img");
		
		imgStatLikes.classList.add("post-content-stat-symbol");
		imgStatComments.classList.add("post-content-stat-symbol");
		imgStatCommunity.classList.add("post-content-stat-symbol");
		
		imgStatLikes.setAttribute("src", URL_IMG_LIKES);
		imgStatComments.setAttribute("src", URL_IMG_COMMENTS);
		imgStatCommunity.setAttribute("src", URL_IMG_COMMUNITY);
		
		let likesText = document.createTextNode(this.numLikes);
		let commentsText = document.createTextNode(this.numComments);
		let communityText = document.createTextNode(this.numShares);
		
		let spanGenerators = [[likesText, imgStatLikes], [commentsText, imgStatComments], [communityText, imgStatCommunity]];
		
		for (let i=0; i < spanGenerators.length; i++) {
			let spanGenerator = spanGenerators[i];
			let spanText = spanGenerator[0];
			let spanImg = spanGenerator[1];
			
			let spanGenerated = document.createElement("span");
			spanGenerated.classList.add("post-content-stat");
			spanGenerated.appendChild(spanText);
			spanGenerated.appendChild(spanImg);
			columnRightDiv.appendChild(spanGenerated);
			
			let spanFontSizeOverride = 24;
			if (spanText.length > 4) {
				spanFontSizeOverride = spanFontSizeOverride - (3 * (spanText.length - 4)); // For every character after 4, we reduce the font-size of the span by 2, from the original 24.
				
				spanGenerated.style.fontSize = "" + spanFontSizeOverride + "px";
				
				spanFontSizeOverride = spanFontSizeOverride;
			}
		}
		
		return postDiv;
	}
}

class Comment extends HTMLElement {
	constructor() {
		super();
		
		this.postedBy = null;
		this.postedTimeAfter = 17;
		this.posterName = "Random Guy";
		this.posterTagline = "Random Tagline";
		this.posterAvatar = "Avatars/anonymous.png";
		this.posterVerified = false;
		this.postedTime = 17;
		this.postText = "This is a random comment!";
		this.hasCommentsBefore = false;
	}
	
	connectedCallback() {
		if ((this.parentElement) == undefined) return;
		
		this.parentElement.commentPosts.push(this);
		
		this.postedBy = 			this.hasAttribute("from") 				? profiles[this.getAttribute("from")] 				: this.postedBy;
		this.postedTimeAfter = 		this.hasAttribute("timeAfter") 			? parseFloat(this.getAttribute("timeAfter")) 		: this.postedTimeAfter;
		this.posterName = 			this.hasAttribute("posterName") 		? this.getAttribute("posterName") 					: this.posterName;
		this.posterTagline = 		this.hasAttribute("posterTagline") 		? this.getAttribute("posterTagline") 				: this.posterTagline;
		this.posterAvatar = 		this.hasAttribute("posterAvatar") 		? this.getAttribute("posterAvatar") 				: this.posterAvatar;
		this.postText = 			this.hasAttribute("post") 				? this.getAttribute("post") 						: this.post;
		this.hasCommentsBefore	=	this.hasAttribute("hasCommentsBefore");
		
		if (this.postedBy != null) {
			this.posterName = this.postedBy.displayName;
			this.posterTagline = this.postedBy.profileTagline;
			this.posterAvatar = this.postedBy.profileImage;
			this.posterVerified = this.postedBy.verified;
		}
		
		let prevElement = this.parentElement;
		if (this.parentElement.commentPosts.length > 1) { // There are comments before this comment.
			prevElement = this.parentElement.commentPosts[this.parentElement.commentPosts.length-2];
		}
		
		this.postedTime = prevElement.postedTime - this.postedTimeAfter;
		
		// Offset all previous comments, as well as the parent post and the overall time, by each comment's time-after.
		// This will ensure that comments never go into negative time, by making the final comment's post time be the original post's time, before comments were added.
		
		this.parentElement.postedTime += this.postedTimeAfter;
		curPostMinutes += this.postedTimeAfter;
		
		for (let i=0; i < this.parentElement.commentPosts.length; i++) {
			this.parentElement.commentPosts[i].postedTime += this.postedTimeAfter;
		}
	}
	
	generateProfile() {
		if (this.postedBy != null) {
			return (this.postedBy.generate(false));
		}
		
		// I probably need a better way to do this. Code duplication bad.
		let rootDiv = document.createElement("div");
		rootDiv.classList.add("post-body");
		rootDiv.classList.add("profile-info");
		
		let avatarImg = document.createElement("img");
		avatarImg.classList.add("avatar");
		avatarImg.setAttribute("src",this.posterAvatar);
		rootDiv.appendChild(avatarImg);
		
		let nameSpan = document.createElement("span");
		nameSpan.classList.add("profile-name");
		rootDiv.appendChild(nameSpan);
		
		if (this.postedVerified) {
			let verifiedText = document.createTextNode(EMOJI_VERIFIED);
			nameSpan.appendChild(verifiedText);
		}
		
		let nameText = document.createTextNode(this.posterName);
		nameSpan.appendChild(nameText);
		
		let taglineSpan = document.createElement("span");
		taglineSpan.classList.add("profile-tagline");
		rootDiv.appendChild(taglineSpan);
		
		let taglineText = document.createTextNode(this.posterTagline);
		taglineSpan.appendChild(taglineText);
		
		return rootDiv;
	}
	
	generate() {
		let postDiv = document.createElement("div");
		postDiv.classList.add("comment");
		
		let profileDiv = this.generateProfile();		
		profileDiv.classList.add("comment-scaled");
		profileDiv.addEventListener("click",function(e) {
			if (e.target.closest("a")) {
				return true;
			}
			
			setActivePost(null);
			unhideAllPosts();
			
			for (let i=0; i < posts.length; i++) {
				let post = posts[i];
				
				if (post.postedBy != this.postedBy) {
					hidePost(post);
				}
			}
			
			e.stopPropagation();
		}.bind(this));		
		postDiv.appendChild(profileDiv);
		
		let postBodyDiv = document.createElement("div");
		postBodyDiv.classList.add("post-body");
		postBodyDiv.classList.add("comment-body");
		postDiv.appendChild(postBodyDiv);
		
		let timeSpan = document.createElement("span");
		let timeSpanText = document.createTextNode(getPostTime(this.postedTime));
		timeSpan.classList.add("posted-time");
		timeSpan.classList.add("comment-time");
		timeSpan.appendChild(timeSpanText);
		//postBodyDiv.appendChild(timeSpan);
		profileDiv.appendChild(timeSpan);
		
		let postTextParagraph = document.createElement("p");
		generateFormattedTextSpan(this.postText, postTextParagraph);
		postBodyDiv.appendChild(postTextParagraph);

		return postDiv;
	}
}

class GalleryPost extends HTMLElement {
	constructor() {
		super();
		
		this.imageUrl = "https://ih1.redbubble.net/image.475950724.2535/st,small,507x507-pad,600x600,f8f8f8.u6.jpg"; // Missing image icon
		this.imageCaption = null;
	}
	
	connectedCallback() {
		if ((this.parentElement) == undefined) return;
		
		this.imageUrl = 			this.hasAttribute("image") 				? this.getAttribute("image") 				: this.imageUrl;
		this.imageCaption = 		this.hasAttribute("caption") 			? this.getAttribute("caption") 				: this.imageCaption;
		
		this.parentElement.postImages.push([this.imageUrl, this.imageCaption]);
	}
}

customElements.define("spon-profile",Profile);
customElements.define("spon-post",Post);
customElements.define("spon-comment",Comment);
customElements.define("gallery-post",GalleryPost);

function initialize() {
	let postContainer = document.getElementById("posts");
	
	for (let i=0; i < posts.length; i++) {
		let post = posts[i];
		
		let postDiv = post.generate();
		
		postContainer.appendChild(postDiv);
	}
}