local v={}v.__index=v local m function v.new(u,q)local d=setmetatable({Name=u,Scope=q,Data={};DataStore=u and m.new(u,q)or nil,Flushing=false,Lock=false},v)d.Failed=d.Shared.Event.new()if d.DataStore then d._dsFailed=d.DataStore.Failed:Connect(function(v,m,u)d.Failed:Fire(v,m,u)end)end return d end function v.OnUpdate(u,v,m)if u.DataStore then return u.DataStore:OnUpdate(v,m)end end function v.Load(m,v)if m.DataStore then local u=m.DataStore:GetAsync(v)if u~=nil then local q={u,true}m.Data[v]=q return q end end end function v.Flush(u,v,m)if u.Lock or not u.DataStore then return end u.Flushing=true local q=u.Data[v]if q~=nil and(q[1]~=nil and(q[2]==false or type(q[1])=="\116\097\098\108\101"))then u.DataStore:SetAsync(v,q[1])q[2]=true end if not m then u.Flushing=false end end function v.FlushAll(v)if v.Lock or not v.DataStore then return end for m in pairs(v.Data)do v:Flush(m,true)end v.Flushing=false end function v.FlushAllConcurrent(v)if v.Lock or not v.DataStore then return end local m=coroutine.running()local u=0 local q=0 for v in pairs(v.Data)do u=u+1 end for d in pairs(v.Data)do spawn(function()v:Flush(d,true)q=q+1 if q==u then assert(coroutine.resume(m))end end)end coroutine.yield()end function v.Get(m,v)local u=m.Data[v]if u==nil and m.DataStore then u=m:Load(v)end return u and u[1]or nil end function v.Set(u,v,m)local q=u.Data[v]if q==nil then u.Data[v]={m;false}else q[1]=m q[2]=false end end function v.Remove(m,v)local u=m.Data[v]if u~=nil then m.DataStore:RemoveAsync(v)m.Data[v]=nil end end function v.Destroy(v)v.Failed:DisconnectAll()if v._dsFailed then v._dsFailed:Disconnect()end end function v.Init(v)m=require(script:WaitForChild("\083\097\102\101\068\097\116\097\083\116\111\114\101"))setmetatable(m,getmetatable(v))end return v
-- Cache
-- Crazyman32
-- February 3, 2017

--[[
	
	cache = Cache.new(name, scope)
	
	cache.Name
	cache.Scope
	
	cache:Get(key)
	cache:Set(key, value)
	cache:Remove(key)
	cache:Load(key)
	cache:OnUpdate(key, callback)
	cache:Flush(key)
	cache:FlushAll()
	cache:FlushAllConcurrent()
	
	cache.Failed(method, key, errorMessage)
	
--]]



local Cache = {}
Cache.__index = Cache

local SafeDataStore


function Cache.new(name, scope)
	
	local self = setmetatable({
		Name = name;
		Scope = scope;
		Data = {};
		DataStore = (name and SafeDataStore.new(name, scope) or nil);
		Flushing = false;
		Lock = false; -- Don't allow flushing if 'true'
	}, Cache)

	self.Failed = self.Shared.Event.new()

	if (self.DataStore) then
		self._dsFailed = self.DataStore.Failed:Connect(function(method, key, errMsg)
			self.Failed:Fire(method, key, errMsg)
		end)
	end
	
	return self
	
end


function Cache:OnUpdate(key, callback)
	if (self.DataStore) then
		return self.DataStore:OnUpdate(key, callback)
	end
end


function Cache:Load(key)
	if (self.DataStore) then
		local value = self.DataStore:GetAsync(key)
		if (value ~= nil) then
			local v = {value, true}
			self.Data[key] = v
			return v
		end
	end
end


function Cache:Flush(key, flushingAll)
	if (self.Lock or not self.DataStore) then return end
	self.Flushing = true
	local value = self.Data[key]
	if (value ~= nil and value[1] ~= nil and (value[2] == false or type(value[1]) == "table")) then
		self.DataStore:SetAsync(key, value[1])
		value[2] = true
	end
	if (not flushingAll) then
		self.Flushing = false
	end
end


function Cache:FlushAll()
	if (self.Lock or not self.DataStore) then return end
	for key in pairs(self.Data) do
		self:Flush(key, true)
	end
	self.Flushing = false
end


function Cache:FlushAllConcurrent()
	if (self.Lock or not self.DataStore) then return end
	local thread = coroutine.running()
	local numData = 0
	local numFlushed = 0
	for _ in pairs(self.Data) do
		numData = (numData + 1)
	end
	for key in pairs(self.Data) do
		spawn(function()
			self:Flush(key, true)
			numFlushed = (numFlushed + 1)
			if (numFlushed == numData) then
				assert(coroutine.resume(thread))
			end
		end)
	end
	coroutine.yield()
end


function Cache:Get(key)
	local value = self.Data[key]
	if (value == nil and self.DataStore) then
		value = self:Load(key)
	end
	return value and value[1] or nil
end


function Cache:Set(key, value)
	local v = self.Data[key]
	if (v == nil) then
		self.Data[key] = {value, false}
	else
		v[1] = value
		v[2] = false
	end
end


function Cache:Remove(key)
	local v = self.Data[key]
	if (v ~= nil) then
		self.DataStore:RemoveAsync(key)
		self.Data[key] = nil
	end
end


function Cache:Destroy()
	self.Failed:DisconnectAll()
	if (self._dsFailed) then
		self._dsFailed:Disconnect()
	end
end


function Cache:Init()
	SafeDataStore = require(script:WaitForChild("SafeDataStore"))
	setmetatable(SafeDataStore, getmetatable(self))
end


return Cache
