package ru.yandex.wmconsole.data;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import ru.yandex.wmconsole.util.UrlErrorGrouper;


/**
 * ATTENTION!!!
 * All information in UserErrorOptions class is stored WITH groups.
 * For example, there will be no errors, that are a part of group "-1".
 * Instead of it, the group "-1" will be presented here.
 * <p/>
 * Created by IntelliJ IDEA.
 * User: baton
 * Date: 27.07.2007
 * Time: 15:11:37
 */
public class UserErrorOptions implements Cloneable {
    private static UrlErrorGrouper grouper = UrlErrorGrouper.getInstance();

    private final Long userId;
    private final List<Integer> okSeverityCodes = new ArrayList<Integer>();
    private final Map<Integer, SeverityEnum> allSeverities = new HashMap<Integer, SeverityEnum>();

    private final List<Integer> siteErrorCodes = new LinkedList<Integer>();
    private final List<Integer> disallowedByUser = new LinkedList<Integer>();
    private final List<Integer> unsupportedByRobot = new LinkedList<Integer>();

    public UserErrorOptions(Long userId) {
        this(userId, true);
    }

    public UserErrorOptions(Long userId, boolean init) {
        this.userId = userId;

        if (init) {
            clear();
            defaultInit();
        }
    }

    public void addError(SeverityEnum severity, Integer code) throws IllegalArgumentException {
        // check for groups and restricted codes
        if (grouper.isRestricted(code)) {
            throw new IllegalArgumentException("Code " + code + " is restricted, but was added to list!");
        }
        if (!grouper.mapErrorToErrorGroup(code).equals(code)) {
            throw new IllegalArgumentException("Single code " + code + " was added to list, while group must be added!");
        }

        // check presence and remove
        if (allSeverities.containsKey(code)) {
            SeverityEnum oldSeverity = allSeverities.get(code);

            switch (oldSeverity) {
                case SITE_ERROR:
                    siteErrorCodes.remove(code);
                    break;
                case DISALLOWED_BY_USER:
                    disallowedByUser.remove(code);
                    break;
                case UNSUPPORTED_BY_ROBOT:
                    unsupportedByRobot.remove(code);
                    break;
                case OK:
                    okSeverityCodes.remove(code);
                    break;
                default:
                    throw new AssertionError("Unknown severity type: " + severity);
            }
        }

        // put
        allSeverities.put(code, severity);
        switch (severity) {
            case SITE_ERROR:
                siteErrorCodes.add(code);
                break;
            case DISALLOWED_BY_USER:
                disallowedByUser.add(code);
                break;
            case UNSUPPORTED_BY_ROBOT:
                unsupportedByRobot.add(code);
                break;
            case OK:
                okSeverityCodes.add(code);
                break;
            default:
                throw new AssertionError("Unknown severity type: " + severity);
        }
    }

    /**
     * Adds an error code severity into structure. If it was already present in structure,
     * it will be replaced by new value.
     *
     * @param errorSeverity ...
     */
    public void addErrorSeverity(ErrorSeverity errorSeverity) {
        addError(errorSeverity.getSeverity(), errorSeverity.getCode());
    }

    public void addList(SeverityEnum severity, Integer... codes) {
        for (Integer code : codes) {
            addError(severity, code);
        }
    }

    public void addRange(SeverityEnum severity, Integer minInclusive, Integer maxExclusive) {
        for (Integer code = minInclusive; code < maxExclusive; code++) {
            addError(severity, code);
        }
    }

    private void defaultInit() {
        // Ошибки на стороне сайта
        addList(SeverityEnum.SITE_ERROR, 403, 1001, 1004, 2006, 2014, 2020, 2024);
        addRange(SeverityEnum.SITE_ERROR, 500, 600);
        addRange(SeverityEnum.SITE_ERROR, 1006, 1008);
        addError(SeverityEnum.SITE_ERROR, -2); // группа из 1008 и 1009 (неверный http заголовок)
        addError(SeverityEnum.SITE_ERROR, 1010);
        addError(SeverityEnum.SITE_ERROR, -3); // группа из 1013, 1015 (Неверная длина сообщения в HTTP-заголовке)
        addError(SeverityEnum.SITE_ERROR, 1014);
        addError(SeverityEnum.SITE_ERROR, -4); // группа из 1016, 1017, 1018, 1019 (Передано неверное количество данных)
        addList(SeverityEnum.SITE_ERROR, 1020, 1021);

        // Страницы запрещены Вебмастером к индексированию в html-коде, файле robots.txt или не существуют
        addRange(SeverityEnum.DISALLOWED_BY_USER, 400, 403);
        addRange(SeverityEnum.DISALLOWED_BY_USER, 404, 500);
        addList(SeverityEnum.DISALLOWED_BY_USER, 1003, 2005, 2025);

        // Не поддерживается роботом
        addError(SeverityEnum.UNSUPPORTED_BY_ROBOT, -1); // группа из 1002 и 2015 (слишком большой документ)
        addList(SeverityEnum.UNSUPPORTED_BY_ROBOT, 1005, 2007);
        addRange(SeverityEnum.UNSUPPORTED_BY_ROBOT, 2010, 2013);
        addError(SeverityEnum.UNSUPPORTED_BY_ROBOT, 2016);
    }

    private void clear() {
        siteErrorCodes.clear();
        disallowedByUser.clear();
        unsupportedByRobot.clear();

        allSeverities.clear();
    }

    public Long getUserId() {
        return userId;
    }

    public Map<Integer, SeverityEnum> getAllSeverities() {
        return allSeverities;
    }

    public List<Integer> getNotOkSeverityCodesWithGroups() {
        List<Integer> result = new ArrayList<Integer>();
        result.addAll(siteErrorCodes);
        result.addAll(disallowedByUser);
        result.addAll(unsupportedByRobot);
        return result;
    }

    public SeverityEnum getSeverityByCode(Integer code) {
        SeverityEnum result = allSeverities.get(code);
        if (result == null) {
            result = SeverityEnum.OK;
        }
        return result;
    }

    /**
     * Returns an object, containing only options, that were changed or
     * inserted in newOptions, comparing to this object.
     *
     * @param newOptions ...
     * @return ...
     */
    public UserErrorOptions whatsNew(UserErrorOptions newOptions) {
        UserErrorOptions result = new UserErrorOptions(userId, false);

        for (Map.Entry<Integer, SeverityEnum> entry : newOptions.getAllSeverities().entrySet()) {
            SeverityEnum oldSeverity = allSeverities.get(entry.getKey());
            if ((oldSeverity == null) || (!oldSeverity.equals(entry.getValue()))) {
                // add new error codes or add changed error codes
                result.addError(entry.getValue(), entry.getKey());
            }
        }

        return result;
    }

    @Override
    public UserErrorOptions clone() throws CloneNotSupportedException {
        super.clone();

        UserErrorOptions result = new UserErrorOptions(userId, false);
        for (Map.Entry<Integer, SeverityEnum> entry : allSeverities.entrySet()) {
            result.addError(entry.getValue(), entry.getKey());
        }

        return result;
    }
}
