package de.ids_mannheim.korap.security.ac;

import de.ids_mannheim.korap.resources.KustvaktResource;
import de.ids_mannheim.korap.resources.ResourceFactory;
import de.ids_mannheim.korap.security.PolicyCondition;
import de.ids_mannheim.korap.security.PolicyContext;
import de.ids_mannheim.korap.security.SecurityPolicy;
import de.ids_mannheim.korap.utils.PrefixTreeMap;
import lombok.Data;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * @author hanl
 * @date 03/03/2014
 */
public class SecurityRowMappers {

    public static class PolicyRowMapper implements RowMapper<SecurityPolicy> {

        @Override
        public SecurityPolicy mapRow (ResultSet rs, int rowNum)
                throws SQLException {
            String perms = rs.getString("perm");
            SecurityPolicy p = new SecurityPolicy();
            p.setID(rs.getInt("pid")).setTarget(rs.getString("persistent_id"))
                    .setPOSIX(perms);

            PolicyContext context = new PolicyContext();
            context.setIPMask(rs.getString("iprange"));
            long enable = rs.getLong("enable");
            long expire = rs.getLong("expire");
            if (enable != -1)
                context.setEnableTime(enable);
            if (expire != -1)
                context.setExpirationTime(expire);
            //            context.addFlag("export", rs.getBoolean("export"));
            //            context.addFlag("sym_use", rs.getInt("sym_use"));
            p.setContext(context);
            return p;
        }
    }

    @Data
    public static class FlagContext extends PolicyContext {

        private Map<String, Object> flags;


        public FlagContext () {
            this.flags = new HashMap<>();
        }


        public FlagContext addFlag (String key, Object value) {
            this.flags.put(key, value);
            return this;
        }


        public FlagContext removeFlag (String key) {
            this.flags.remove(key);
            return this;
        }


        public FlagContext clearFlags () {
            this.flags.clear();
            return this;
        }
    }


    @SuppressWarnings(value = "all")
    public static List<SecurityPolicy>[] mapResourcePolicies (ResultSet rs)
            throws SQLException {
        List<SecurityPolicy>[] policyArray = null;
        List<Integer>[] idx = null;
        loop: while (rs.next()) {
            // user has no permission here, thus skip
            if (rs.getInt("allowed") == 0)
                continue loop;

            if (policyArray == null) {
                int v = rs.getInt("max_depth") + 1;
                policyArray = new List[v];
                idx = new List[v];
            }

            int depth = rs.getInt("depth");
            if (policyArray[depth] == null) {
                policyArray[depth] = new ArrayList<>();
                idx[depth] = new ArrayList<>();
            }

            Integer pid = rs.getInt("pid");
            String grouping = rs.getString("group_id");
            Integer index = idx[depth].indexOf(pid);

            SecurityPolicy policy;
            if (index == -1) {
                if (pid == -1 && grouping.equalsIgnoreCase("self")) {
                    policy = new SecurityPolicy.OwnerPolicy(
                            rs.getString("persistent_id"), rs.getInt("creator"));
                    policyArray[depth].add(0, policy);
                    idx[depth].add(0, pid);
                }
                else {
                    policy = new SecurityRowMappers.PolicyRowMapper().mapRow(
                            rs, 0);
                    policyArray[depth].add(policy);
                    idx[depth].add(pid);

                    //todo:
                    //                    if (policy.isActive(user)) {
                    //                        policyArray[depth].add(policy);
                    //                        idx[depth].add(pid);
                    //                    }
                }
            }
            else
                policy = policyArray[depth].get(index);

            PolicyCondition c = new PolicyCondition(rs.getString("group_id"));
            if (!policy.contains(c))
                policy.addCondition(c);
        }
        return policyArray;
    }


    public static List<SecurityPolicy> mapConditionPolicies (ResultSet rs)
            throws SQLException {
        Map<Integer, SecurityPolicy> policyMap = new HashMap<>();
        while (rs.next()) {
            if (rs.getInt("allowed") == 0)
                continue;

            Integer pid = rs.getInt("pid");
            SecurityPolicy policy;
            if ((policy = policyMap.get(pid)) == null) {
                policy = new SecurityRowMappers.PolicyRowMapper().mapRow(rs, 0);
                policyMap.put(pid, policy);
            }
            PolicyCondition c = new PolicyCondition(rs.getString("group_id"));

            if (!policy.contains(c))
                policy.addNewCondition(c);
        }
        return new ArrayList<>(policyMap.values());
    }


    @Deprecated
    public static List<SecurityPolicy>[] map (ResultSet rs) throws SQLException {
        Map<Integer, SecurityPolicy>[] policyArray = null;
        while (rs.next()) {
            // user has no permission here!
            if (rs.getInt("allowed") == 0)
                continue;

            if (policyArray == null)
                policyArray = new Map[rs.getInt("max_depth") + 1];

            int depth = rs.getInt("depth");
            Map<Integer, SecurityPolicy> cursor = policyArray[depth];
            if (cursor == null)
                cursor = new HashMap<>();

            Integer pid = rs.getInt("pid");
            SecurityPolicy policy = cursor.get(pid);
            if (policy == null) {
                policy = new SecurityRowMappers.PolicyRowMapper().mapRow(rs, 0);
                cursor.put(pid, policy);
            }
            PolicyCondition c = new PolicyCondition(rs.getString("group_id"));

            if (!policy.contains(c))
                policy.addNewCondition(c);
        }

        List<SecurityPolicy>[] results;
        if (policyArray == null) {
            results = new List[1];
            results[0] = new ArrayList<>();
        }
        else {
            results = new List[policyArray.length];
            for (int idx = 0; idx < policyArray.length; idx++) {
                if (policyArray[idx] != null)
                    results[idx] = new ArrayList<>(policyArray[idx].values());
                else
                    results[idx] = new ArrayList<>();
            }
        }
        return results;
    }

    public static class HierarchicalResultExtractor implements
            ResultSetExtractor<List<KustvaktResource.Container>> {

        private boolean _withpid;


        //        public HierarchicalResultExtractor(boolean wpid) {
        //            this._withpid = wpid;
        //        }

        // todo: in order for this to work, all parent flags need to be matched in sql!
        public List<KustvaktResource.Container> extractData (ResultSet rs)
                throws SQLException, DataAccessException {
            // contains the container with the highest available name_path to retrieve partial matches!
            PrefixTreeMap<KustvaktResource.Container[]> containerMap = new PrefixTreeMap<>();
            Map<Integer, SecurityPolicy> trace = new HashMap<>();

            while (rs.next()) {
                KustvaktResource.Container[] cursor;
                Integer pid = rs.getInt("pid");

                SecurityPolicy policy = trace.get(pid);
                if (policy == null | pid == -1) {
                    //                    Integer id = rs.getInt("id");
                    String persistentId = rs.getString("persistent_id");
                    int depth = rs.getInt("depth");
                    String namePath = rs.getString("name_path");
                    policy = new SecurityRowMappers.PolicyRowMapper().mapRow(
                            rs, 0);

                    //todo: put active status here!
                    trace.put(pid, policy);

                    //fixme: since leaves are mentioned first, maybe retrieve
                    SortedMap<String, KustvaktResource.Container[]> submatch;
                    if ((submatch = containerMap.getPrefixSubMap(namePath)) == null) {
                        //create container for last child node
                        cursor = new KustvaktResource.Container[depth + 1];
                        cursor[depth] = new KustvaktResource.Container(
                                persistentId, ResourceFactory.getResource(
                                        rs.getInt("type")).getClass());
                        containerMap.put(namePath, cursor);
                    }
                    else {
                        KustvaktResource.Container[] values = submatch
                                .get(submatch.firstKey());
                        values[depth] = new KustvaktResource.Container(
                                persistentId, ResourceFactory.getResource(
                                        rs.getInt("type")).getClass());
                    }
                }
            }

            List<KustvaktResource.Container> result = new ArrayList<>();
            for (KustvaktResource.Container[] values : containerMap.values()) {
                for (KustvaktResource.Container container : values)
                    if (container == null)
                        containerMap.remove(values);
                result.add(values[values.length - 1]);
            }
            return result;
        }
    }

}
