--- Orgs: Base
-- Basic functions for orgs

modpol.orgs = modpol.orgs or
{
    count = 1,
    array = {}
}

-- sets modpol.orgs as its own fallback
modpol.orgs.__index = modpol.orgs

function temp_org()
    return {
        id = nil,
        name = nil,
        modules = modpol.util.copy_table(modpol.modules),
        processes = {},
        pending = {},
        members = {},
        parent = nil,
        children = {}
    }
end

-- ==================================================
-- returns org when given its id or name
function modpol.orgs.get_org(arg)
    if type(arg) == 'string' then
        for id, org in ipairs(modpol.orgs.array) do
            if org.name == arg then
                return org
            end
        end
    elseif type(arg) == 'number' then
        return modpol.orgs.array[arg]
    end
    return nil
end

-- ===============================================
-- returns a table list of all org names
function modpol.orgs.list_all()
   local org_table
   for k, v in ipairs(modpol.orgs.array) do
      if type(v) == 'table' then
         if org_table then 
            table.insert(org_table, v.name)
         else 
            org_table = {v.name}
         end
      end
   end
   return org_table
end

-- Function: modpol.orgs.user_orgs(user)
-- input: user (string)
-- output: table of strings of org names
function modpol.orgs.user_orgs(user)
   local all_orgs = modpol.orgs.list_all()
   local user_orgs = {}
   for i,v in ipairs(all_orgs) do
      local this_table = modpol.orgs.get_org(v)
      if this_table:has_member(user) then
         table.insert(user_orgs,v)
      end
   end
   return user_orgs
end

-- ===========================================
-- deletes all orgs except for the instance
function modpol.orgs.reset()
   local instance_members =
      modpol.util.copy_table(modpol.instance.members)
    for id, org in ipairs(modpol.orgs.array) do
        if id > 1 then
            modpol.orgs.array[id] = "removed"
        end
    end

    modpol.orgs.array[1] = nil
    modpol.instance = modpol.orgs.init_instance()
    modpol.instance.members = instance_members

    modpol.ocutil.log('All orgs reset')
    modpol.orgs:record('Resetting all orgs', 'org_reset')
end

-- ===================================================
-- initializes the instance (root org)
-- can only be run once, as only one instance can exist
function modpol.orgs.init_instance()
    local error_msg
    if modpol.orgs.array[1] then
        modpol.ocutil.log('Error in orgs.init_instance -> instance has already been initialized')
        return false
    end

    local instance = temp_org()
    instance.id = 1
    instance.name = "Root"
    
    setmetatable(instance, modpol.orgs)

    -- adding instance to org list
    modpol.orgs.array[1] = instance

    modpol.ocutil.log('Initialized the instance root org')
    modpol.orgs:record('Initialized the instance root org', 'create_instance')

    return instance
end


-- FUNCTIONS BEYOND HERE OPERATE ON ORG OBJECTS

-- =======================================================
-- records a log message to the modpol ledger
function modpol.orgs:record(msg, entry_type)
    local entry = {
        timestamp = '',
        entry_type = nil,
        action_msg = '',
        org_name = '',
        org_id = nil,
    }

    if type(msg) == 'string' and not(modpol.ocutil.str_empty(msg)) then
        entry.action_msg = msg
    else
        modpol.ocutil.log('Error in ' .. self.name .. ':record -> msg must be a non empty string')
        return false
    end

    if type(entry_type) == 'string' and not(modpol.ocutil.str_empty(entry_type)) then
        entry.entry_type = entry_type
    else
        modpol.ocutil.log('Error in ' .. self.name .. ':record -> entry_type must be a non empty string')
        modpol.ocutil.log(msg, entry_type)
        return false
    end

    entry.timestamp = os.time()
    entry.org_id = self.id
    entry.org_name = self.name

    table.insert(modpol.ledger, entry)
    modpol.store_data()
end

-- ==================================================
-- adds a new sub org to the org it is called on
-- input: name (string), user (string)
-- ex: instance:add_org('town hall')
function modpol.orgs:add_org(name, user)
    if self.id == nil then
        modpol.ocutil.log('Error in ' .. self.name .. ':add_org -> add_org can only be called by another org')
        return false
    end

    if modpol.ocutil.str_empty(name) then
        modpol.ocutil.log('Error in ' .. self.name .. ':add_org -> org name is required')
        return false
    end

    if modpol.orgs.get_org(name) then
        modpol.ocutil.log('Error in ' .. self.name .. ':add_org -> org name is already being used')
        return false
    end

    -- creating the child sub org
    modpol.orgs.count = modpol.orgs.count + 1
    local child_org = temp_org()
    child_org.id = modpol.orgs.count
    child_org.name = name
    child_org.parent = self.id
    child_org.processes = {}
    child_org.modules = modpol.util.copy_table(self.modules)
    
    setmetatable(child_org, modpol.orgs)

    -- adding child id to list of children
    table.insert(self.children, child_org.id)
    
    -- adding child to org list
    modpol.orgs.array[child_org.id] = child_org

    -- adding creator of org as the first member
    child_org:add_member(user)

    self:record('created sub org ' .. name, 'add_org')
    modpol.ocutil.log('Created ' .. name .. ' (suborg of ' .. self.name .. ')')

    return child_org
end

-- ========================================
-- recursively deletes an org and its suborgs
-- leaves entry in modpol.orgs.array as a string "removed"
-- note: "reason" param was removed, can be added back
function modpol.orgs:delete()
    if self.id == 1 then
        modpol.ocutil.log('Error in ' .. self.name .. ':delete -> cannot delete instance')
        return false
    end
    
    if #self.children > 0 then
        for i, child_id in pairs(self.children) do
            local child = modpol.orgs.get_org(child_id)
            modpol.ocutil.log(child_id, child)
            child:delete()
        end
    end

    modpol.orgs.array[self.id] = 'removed'
    modpol.ocutil.log('Deleted org ' .. self.name .. ': ' .. self.id)

    self:record('Deleted ' .. self.name .. ' and all child orgs', 'del_org')

end


-- ===========================================
-- internal function to get the index of a member name
function modpol.orgs:get_member_index(member)
    for k, v in ipairs(self.members) do
        if v == member then
            return k
        end
    end
end

-- ===========================================
-- adds a user to an org
function modpol.orgs:add_member(user)
    for id, name in ipairs(self.members) do
        if user == name then
            modpol.ocutil.log('Error in ' .. self.name .. ':add_member -> user already in org')
            return false
        end
    end
    -- trys to fill in empty spots first
    local empty_index = self:get_member_index('')
    if empty_index then
        self.members[empty_index] = user
    else
        -- adds to end if no empty spots
        table.insert(self.members, user)
    end
    
    modpol.ocutil.log('Added member ' .. user .. ' to ' .. self.name)
    self:record('Added member ' .. user, 'add_member')

end

-- =======================================
-- removes a user from an org
function modpol.orgs:remove_member(user)
    -- sets the array index to an empty string so that consecutive list is preserved
    -- empty spots will get filled in by new members
    local user_index = self:get_member_index(user)
    if user_index then
        self.members[user_index] = ''
    else
        modpol.ocutil.log('Error in ' .. self.name .. ':remove_member -> user not in org')
    end
    modpol.ocutil.log('Removed member ' .. user .. ' from ' .. self.name)
    self:record('Removed member ' .. user, 'del_member')
end

-- ===========================================
-- boolean check whether user is an org
function modpol.orgs:has_member(user)
    local user_index = self:get_member_index(user)
    if user_index then
        return true
    else
        return false
    end
end

-- ==================================
-- Function: modpol.orgs:list_members()
-- output: a table of the names (string) of members
function modpol.orgs:list_members()
   local members = {}
   for k, v in ipairs(self.members) do
      table.insert(members, v)
   end
   return members
end

-- ==============================
-- because member list uses lazy deletion, using #org.members will not show an accurate number
function modpol.orgs:get_member_count()
    local count = 0
    for k, v in ipairs(self.members) do
        -- the empty string represents a deleted member in the members list
        if v ~= '' then
            count = count + 1
        end
    end
    return count
end
-- ====================================
-- adds a new policy to the policy table
-- must define the policy type, process associated with it, and whether the request must be made by an org member
function modpol.orgs:set_policy(policy_type, process_type, must_be_member)
    local new_policy = {
        process_type = process_type,
        must_be_member = must_be_member
    }
    self.policies[policy_type] = new_policy
    modpol.ocutil.log('Added policy for ' .. policy_type .. ' in ' .. self.name)
    self:record('Added policy for ' .. policy_type, 'set_policy')
end