package ru.yandex.wmconsole.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import ru.yandex.common.util.collections.CollectionFactory;
import ru.yandex.wmconsole.data.SeverityEnum;
import ru.yandex.wmconsole.data.UserErrorOptions;
import ru.yandex.wmconsole.data.info.UrlErrorsWithCodeInfo;

/**
 * User: baton
 * Date: 11.04.2007
 * Time: 13:49:10
 * To change this template use File | Settings | File Templates.
 */
public class UrlErrorGrouper {
    public static class UrlErrorGroup {
        public final int groupId;
        public final int[] ids;

        public UrlErrorGroup(int groupId, int... ids) {
            assert (groupId < 0) && (groupId > -100): "group id is out of range.";
            for (int id : ids) {
                assert (id >= 0) : "each element in ids must be more than zero.";
            }

            this.groupId = groupId;
            this.ids = ids;
        }
    }

    /**
     * Map from error to error-group.
     * <p/>
     * If key is not present - current error code belongs to no group.
     * If key is present, but value is NULL - current error code is rectricted to be shown.
     * If key is present, and value is present - current error code belongs to specified group.
     */
    private static Map<Integer, Integer> errorGroupsMap = new HashMap<Integer, Integer>();

    private static int[] restricted = new int[]{1000, 1011, 1012, 1022, 2000, 2001, 2002, 2003,
                                                2008, 2009, 2013, 2017, 2018, 3021, 3022};

    private static Set<Integer> ignored = CollectionFactory.set(2004);

    private static Set<Integer> ok = CollectionFactory.set(
            200, 2000, 3021, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036);

    private static UrlErrorGroup[] groups = new UrlErrorGroup[]{
            new UrlErrorGroup(-1, 1002, 2015),
            new UrlErrorGroup(-2, 1008, 1009),
            new UrlErrorGroup(-3, 1013, 1015),
            new UrlErrorGroup(-4, 1016, 1017, 1018, 1019),
    };

    static {
        for (int res : restricted) {
            errorGroupsMap.put(res, null);
        }

        for (UrlErrorGroup group : groups) {
            for (int id : group.ids) {
                errorGroupsMap.put(id, group.groupId);
            }
        }
    }

    private static UrlErrorGrouper instance = new UrlErrorGrouper();

    public static UrlErrorGrouper getInstance() {
        return instance;
    }

    private UrlErrorGrouper() {
    }

    public static List<Integer> expandGroups(List<Integer> codes) {
        List<Integer> result = new ArrayList<Integer>();
        for (Integer code : codes) {
            if (code < 0) {
                UrlErrorGrouper.UrlErrorGroup group = UrlErrorGrouper.getGroupByGroupId(code);
                for (int id : group.ids) {
                    result.add(id);
                }
            } else if (code > 0) {
                result.add(code);
            } else {
                throw new AssertionError("code = 0");
            }
        }

        return result;
    }

    public static UrlErrorGroup getGroupByGroupId(int groupId) {
        for (UrlErrorGroup group : groups) {
            if (group.groupId == groupId) {
                return group;
            }
        }
        return null;
    }

    public boolean isRestricted(int errorCode) {
        for (int r : restricted) {
            if (errorCode == r) {
                return true;
            }
        }

        return false;
    }

    public Integer mapErrorToErrorGroup(int errorCode) {
        if (!errorGroupsMap.containsKey(errorCode)) {
            return errorCode;
        }
        return errorGroupsMap.get(errorCode);
    }


    /**
     * Creates new list of UrlErrorInfos based on given, where:
     * 1) does grouping error codes to groups
     * 2) deletes restricted error codes
     * 3) deletes error codes with severity "OK" for user
     *
     * @param errors           ...
     * @param options ...
     * @return ...
     */
    public List<UrlErrorsWithCodeInfo> mapErrorsToErrorGroups(Collection<UrlErrorsWithCodeInfo> errors,
            UserErrorOptions options) {
        List<UrlErrorsWithCodeInfo> result = new ArrayList<UrlErrorsWithCodeInfo>();
        int[] groupCount = new int[1000];

        for (UrlErrorsWithCodeInfo info : errors) {
            if (errorGroupsMap.containsKey(info.getCode())) {
                Integer value = errorGroupsMap.get(info.getCode());
                if (value != null) {
                    assert (value < 0) && (value > -1000) : "group id is out of range.";
                    if (!SeverityEnum.OK.equals(options.getSeverityByCode(value))) {
                        groupCount[-value] += info.getCount();
                    }
                } else {
                    // do nothing! this error code is restricted to be shown.
                }
            } else {
                if (!SeverityEnum.OK.equals(options.getSeverityByCode(info.getCode()))) {
                    result.add(info);
                }
            }
        }
        for (int i = 0; i < groupCount.length; i++) {
            if (groupCount[i] > 0) {
                result.add(new UrlErrorsWithCodeInfo(-i, groupCount[i], options.getSeverityByCode(-i), null));
            }
        }

        return result;
    }

    public static boolean isOk(int code) {
        return ok.contains(code);
    }

    public static boolean isIgnored(int code) {
        return ignored.contains(code);
    }
}
