package ru.yandex.wmconsole.servantlet;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.validator.routines.EmailValidator;

import ru.yandex.common.framework.core.ServRequest;
import ru.yandex.common.framework.core.ServResponse;
import ru.yandex.wmconsole.data.wrappers.StringWrapper;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.error.UserProblem;
import ru.yandex.wmtools.common.servantlet.AbstractServantlet;

/**
 * Валидатор url и email.
 *
 * Параметры:
 *  type = 'url' | 'email' | 'phone'
 *  value = проверяемое значение
 * Результат: <data><status> ok | failed </status></data>
 *
 * User: azakharov
 * Date: 14.06.12
 * Time: 14:47
 */
public class ValidateValueServantlet extends AbstractServantlet {
    private static final String PARAM_TYPE = "type";
    private static final String PARAM_VALUE = "value";
    private static final String TYPE_URL = "url";
    private static final String TYPE_EMAIL = "email";
    private static final String TYPE_PHONE = "phone";

    private static final String TAG_STATUS = "status";
    private static final String STATUS_OK = "ok";
    private static final String STATUS_FAILED = "failed";

    private static final Pattern PHONE = Pattern.compile("^\\+(\\d{1,3})(-(\\d{3}))? \\((\\d{1,6})\\) ((\\d{2})|(\\d{1,2}-\\d{2})|(\\d{1,4}-\\d{2}-\\d{2}))$");
    private static final String EXT_RE = "(.*) ?\\(?[дД]о[бп](авочный|олнительный)?[.,: ]*([\\d -]+)(,[\\d -]+)*\\)?.*";

    private static final Pattern NOT_PHONE_SYMS = Pattern.compile("[^0-9()+]");
    private static final Pattern POSSIBLE_CORRECT_PHONE0 = Pattern.compile("^\\+?(7|375|380) (\\d+) ([0-9 -]+)$");
    private static final Pattern POSSIBLE_CORRECT_PHONE1 = Pattern.compile("^\\+?(7|375|380)-(\\d+)-([0-9 -]+)$");
    private static final Pattern POSSIBLE_CORRECT_PHONE2 = Pattern.compile("^(\\d+\\(\\d+\\)\\d+)$");
    private static final Pattern SPACES = Pattern.compile("\\s");
    private static final Pattern LPARENT = Pattern.compile("\\(");
    private static final Pattern RPARENT = Pattern.compile("\\)");
    private static final Pattern FREE_CALL_PATTERN  = Pattern.compile("^(\\+7) \\(\\d{3,5}\\) 8 (\\d{3}) (\\d{3} \\d{2} \\d{2})$");
    private static final Pattern BAD_MOBILE = Pattern.compile("^(\\+7) \\(\\d{3,5}\\) (9\\d{2})(\\d{7})$");

    private static final Map<String, Integer> COUNTRY_CODE_TO_SUM_PHONE_LENGTH = new LinkedHashMap <String, Integer>();

    static {
        COUNTRY_CODE_TO_SUM_PHONE_LENGTH.put("3", 0); // dummy code
        COUNTRY_CODE_TO_SUM_PHONE_LENGTH.put("8", 0); // dummy code
        COUNTRY_CODE_TO_SUM_PHONE_LENGTH.put("38", 0); // dummy code

        COUNTRY_CODE_TO_SUM_PHONE_LENGTH.put("7", 11); // Россия
        COUNTRY_CODE_TO_SUM_PHONE_LENGTH.put("375", 12); // Белоруссия
        COUNTRY_CODE_TO_SUM_PHONE_LENGTH.put("380", 12); // Украина
        COUNTRY_CODE_TO_SUM_PHONE_LENGTH.put("420", 12); // Чехия
        COUNTRY_CODE_TO_SUM_PHONE_LENGTH.put("49", 13); // Германия
        COUNTRY_CODE_TO_SUM_PHONE_LENGTH.put("372", 10); // Эстония
    }


    @Override
    protected void doProcess(final ServRequest req, final ServResponse res) throws UserException, InternalException {
        final String type = getRequiredStringParam(req, PARAM_TYPE);
        final String value = getRequiredStringParam(req, PARAM_VALUE);
        if (TYPE_URL.equals(type)) {
            try {
                prepareUrl(value, true);
                ok(res);
            } catch (UserException e) {
                failed(res);
            }
        } else if (TYPE_EMAIL.equals(type)) {
            final String[] emails = value.split(";\\s*");
            for (String email : emails) {
                if (!EmailValidator.getInstance().isValid(email)) {
                    failed(res);
                    return;
                }
            }
            ok(res);
        } else if (TYPE_PHONE.equals(type)) {
            if (!validatePhone(value)) {
                failed(res);
                return;
            }
            ok(res);
        } else {
            throw new UserException(UserProblem.ILLEGAL_PARAM_VALUE, PARAM_TYPE, type);
        }
    }

    private void failed(ServResponse response) {
        addStatus(response, STATUS_FAILED);
    }

    private void ok(ServResponse response) {
        addStatus(response, STATUS_OK);
    }

    private void addStatus(ServResponse response, String status) {
        response.addData(new StringWrapper(status, TAG_STATUS));
    }

    private boolean validatePhone(String phone) {
        if (!isValidPhone(phone)) {
            if (isValidPhone(trimPhone(phone)) || isValidPhone(addDashes(phone))) {
                return true;
            } else if (phone.matches(EXT_RE)) {
                return true;
            } else if (isValidPhone(phone.replaceAll("-", ""))) {
                return true;
            } else {
                return false;
            }
        } else {
            return true;
        }
    }


    public static String addDashes(final String newPhone) {
        String phone = newPhone;
        phone = FREE_CALL_PATTERN.matcher(phone).replaceFirst("$1($2)$3");
        phone = BAD_MOBILE.matcher(phone).replaceFirst("$1($2)$3");
        phone = POSSIBLE_CORRECT_PHONE0.matcher(phone).replaceFirst("+$1($2)$3");
        phone = POSSIBLE_CORRECT_PHONE1.matcher(phone).replaceFirst("+$1($2)$3");
        phone = NOT_PHONE_SYMS.matcher(phone).replaceAll("");
        phone = SPACES.matcher(phone).replaceAll("");
        phone = POSSIBLE_CORRECT_PHONE2.matcher(phone).replaceFirst("+$1");
        phone = LPARENT.matcher(phone).replaceAll(" (");
        phone = RPARENT.matcher(phone).replaceAll(") ");
        final int phoneLength = phone.contains(" ") ? phone.length() - phone.lastIndexOf(" ") - 1 : phone.length();
        if (phoneLength > 2) {
            phone = phone.substring(0, phone.length() - 2) + "-" + phone.substring(phone.length() - 2);
            if (phoneLength > 4) {
                phone = phone.substring(0, phone.length() - 5) + "-" + phone.substring(phone.length() - 5);
            }
        }
        return phone.trim();
    }

    private static String trimPhone(final String s) {
        return s.replace("^[ .,]+", "").replaceAll(" +", " ").replace("[ .,]+$", "");
    }

    private static boolean containsOnlyZeroes(final CharSequence str) {
        boolean result = true;
        for (int i = 0; i < str.length(); ++i) {
            if (str.charAt(i) != '0') {
                result = false;
                break;
            }
        }
        return result;
    }

    private static int digitsCount(final String value) {
        int result = 0;
        for (int i = 0; i < value.length(); i++) {
            if(Character.isDigit(value.charAt(i))) {
                result++;
            }
        }
        return result;
    }

    // TODO: код из валидатора баки
    public static boolean isValidPhone(final String phone) {
        final Matcher m = PHONE.matcher(phone);
        if (!m.matches()) {
            return false;
        }
        final String gr1 = m.group(1);
        final String gr4 = m.group(4);
        final String gr5 = m.group(5).replace("-", "");

        if(containsOnlyZeroes(gr5)) {
            return false;
        }

//        final int len = gr1.length() + gr4.length() + gr5.length();
        final int len = digitsCount(phone);

        // Ukraine, +380 (800) ***-**-** is correct
        // it's dirty hack
        if ("380".equals(gr1) && "800".equals(gr4) && gr5.length() == 7) {
            return true;
        }

        if ("7".equals(gr1) && "342".equals(gr4)) {
            if (gr5.matches("^\\d{2,3}|\\d{5}$")) {
                return true;
            }
        }

        if ("7".equals(gr1) && gr4.length() < 3) {
            return false;
        }

        // short phones like 112, 01, 02, 03, .. , 003, etc. are common correct
        if (gr5.matches("112|0\\d{1,2}")) {
            return true;
        }

        final Integer length = COUNTRY_CODE_TO_SUM_PHONE_LENGTH.get(gr1);
        return (length != null) ? (len == length) : (len == 11 || len == 12);
    }
}
