/**
 * ***************************************************************************
 * Copyright (c) 2010 Qcadoo Limited
 * Project: Qcadoo MES
 * Version: 1.4
 *
 * This file is part of Qcadoo.
 *
 * Qcadoo is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation; either version 3 of the License,
 * or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 * ***************************************************************************
 */
package com.qcadoo.mes.basic;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.qcadoo.mes.basic.constants.AddressFields;
import com.qcadoo.mes.basic.constants.BasicConstants;
import com.qcadoo.mes.basic.constants.CompanyFields;
import com.qcadoo.model.api.DataDefinition;
import com.qcadoo.model.api.DataDefinitionService;
import com.qcadoo.model.api.Entity;
import com.qcadoo.model.api.search.SearchRestrictions;
import com.qcadoo.model.constants.DictionaryFields;
import com.qcadoo.model.constants.DictionaryItemFields;
import com.qcadoo.model.constants.QcadooModelConstants;

@Service
public class BasicService {

    private static final String L_MAIN = "01main";

    private static final String L_SHIPPING = "02shipping";

    private static final String L_ADDRESS_TYPE = "addressType";

    private static final String L_GET_NUMBERS_QUERY_TEMPLATE = "SELECT "
            + "SUBSTRING(${NUMBER_FIELD}, '${PREFIX}([0-9]+)') AS ${NUM_PROJECTION_ALIAS}, '' AS nullResultFix "
            + "FROM #basic_address " + "WHERE ${COMPANY_FIELD}.id = ${COMPANY_VALUE} " + "ORDER BY numProjection DESC";

    private static final String L_NUM_PROJECTION_ALIAS = "numProjection";

    private static final String L_DASH = "-";

    @Autowired
    private DataDefinitionService dataDefinitionService;

    public String getMainAddressType() {
        String addressType = null;

        Optional<Entity> mayBeMainAddressType = getAddressTypeDictionaryItem(L_MAIN);

        if (mayBeMainAddressType.isPresent()) {
            Entity mainAddressType = mayBeMainAddressType.get();

            addressType = mainAddressType.getStringField(DictionaryItemFields.NAME);
        }

        return addressType;
    }

    public String getShippingAddressType() {
        String addressType = null;

        Optional<Entity> mayBeShippingAddressType = getAddressTypeDictionaryItem(L_SHIPPING);

        if (mayBeShippingAddressType.isPresent()) {
            Entity shippingAddressType = mayBeShippingAddressType.get();

            addressType = shippingAddressType.getStringField(DictionaryItemFields.NAME);
        }

        return addressType;
    }

    private Optional<Entity> getAddressTypeDictionaryItem(final String technicalCode) {
        Optional<Entity> mayBeAddressType = getAddressTypeDictionary();

        if (mayBeAddressType.isPresent()) {
            Entity addressType = mayBeAddressType.get();

            return Optional.ofNullable(findAddressTypeDictionaryItem(addressType, technicalCode));
        }

        return Optional.empty();
    }

    private Entity findAddressTypeDictionaryItem(final Entity addressType, final String technicalCode) {
        return getDictionaryItemDD().find().add(SearchRestrictions.belongsTo(DictionaryItemFields.DICTIONARY, addressType))
                .add(SearchRestrictions.eq(DictionaryItemFields.TECHNICAL_CODE, technicalCode)).setMaxResults(1).uniqueResult();
    }

    private Optional<Entity> getAddressTypeDictionary() {
        return Optional.ofNullable(findAddressTypeDictionary());
    }

    private Entity findAddressTypeDictionary() {
        return getDictionaryDD().find().add(SearchRestrictions.eq(DictionaryFields.NAME, L_ADDRESS_TYPE)).setMaxResults(1)
                .uniqueResult();
    }

    public Optional<Entity> getMainAddress(final Entity company) {
        if (company.getId() != null) {
            String addressType = getMainAddressType();

            if (StringUtils.isNotEmpty(addressType)) {
                return Optional.ofNullable(findMainAddress(company, addressType));
            }
        }

        return Optional.empty();
    }

    private Entity findMainAddress(final Entity company, final String addressType) {
        return getAddressDD().find().add(SearchRestrictions.belongsTo(AddressFields.COMPANY, company))
                .add(SearchRestrictions.eq(AddressFields.ADDRESS_TYPE, addressType)).setMaxResults(1).uniqueResult();
    }

    public String generateAddressNumber(final Entity company) {
        String addressNumber = "";

        Long greatestNumber = 0L;

        if (company.getId() != null) {
            Collection<Entity> numberProjections = getNumbersProjection(company);

            Collection<Long> numericValues = extractNumericValues(numberProjections);

            if (!numericValues.isEmpty()) {
                greatestNumber = Ordering.natural().max(numericValues);
            }
        }

        addressNumber = generateNumber(company, greatestNumber + 1);

        return addressNumber;
    }

    public String updateAddressNumber(final Entity address, final Entity company) {
        String addressNumber = address.getStringField(AddressFields.NUMBER);

        String[] parts = addressNumber.split(L_DASH);

        int length = parts.length;

		if (length > 1) {
            Long number = Long.valueOf(parts[length - 1]);

            addressNumber = generateNumber(company, number);
        }

        return addressNumber;
    }

    private Collection<Entity> getNumbersProjection(final Entity company) {
        List<Entity> numbersProjection = Lists.newArrayList();

        if (company != null) {
            String hqlQuery = buildQuery(company);

            numbersProjection = getAddressDD().find(hqlQuery).list().getEntities();
        }

        return numbersProjection;
    }

    private String buildQuery(final Entity company) {
        String query = "";

        if (company != null) {
            Map<String, String> placeholderValues = Maps.newHashMap();

            placeholderValues.put("NUMBER_FIELD", AddressFields.NUMBER);
            placeholderValues.put("PREFIX", createPrefix(company));
            placeholderValues.put("NUM_PROJECTION_ALIAS", L_NUM_PROJECTION_ALIAS);
            placeholderValues.put("COMPANY_FIELD", AddressFields.COMPANY);
            placeholderValues.put("COMPANY_VALUE", company.getId().toString());

            StrSubstitutor substitutor = new StrSubstitutor(placeholderValues, "${", "}");

            query = substitutor.replace(L_GET_NUMBERS_QUERY_TEMPLATE).toString();
        }

        return query;
    }

    private String createPrefix(final Entity company) {
        String number = company.getStringField(CompanyFields.NUMBER);

        return escapeSql(number) + L_DASH;
    }

    private String escapeSql(final String number) {
        return number.replace("'", "''").replace("(", "\\(").replace(")", "\\)");
    }

    private Collection<Long> extractNumericValues(final Iterable<Entity> numberProjections) {
        List<Long> numericValues = Lists.newArrayList();

        for (Entity projection : numberProjections) {
            String numberFieldValue = projection.getStringField(L_NUM_PROJECTION_ALIAS);

            if (StringUtils.isNumeric(numberFieldValue)) {
                numericValues.add(Long.valueOf(numberFieldValue));
            }
        }

        return numericValues;
    }

    private String generateNumber(final Entity company, final Long number) {
        StringBuilder numberBuilder = new StringBuilder();

        numberBuilder.append(company.getStringField(CompanyFields.NUMBER));
        numberBuilder.append(L_DASH);
        numberBuilder.append(String.format("%02d", number));

        return numberBuilder.toString();
    }

    public boolean checkIfIsMainAddressType(final String addressType) {
        if (StringUtils.isNotEmpty(addressType)) {
            String mainAddressType = getMainAddressType();

            if (StringUtils.isNotEmpty(mainAddressType)) {
                if (mainAddressType.equals(addressType)) {
                    return true;
                }
            }
        }

        return false;
    }

    public DataDefinition getDictionaryDD() {
        return dataDefinitionService.get(QcadooModelConstants.PLUGIN_IDENTIFIER, QcadooModelConstants.MODEL_DICTIONARY);
    }

    public DataDefinition getDictionaryItemDD() {
        return dataDefinitionService.get(QcadooModelConstants.PLUGIN_IDENTIFIER, QcadooModelConstants.MODEL_DICTIONARY_ITEM);
    }

    public DataDefinition getAddressDD() {
        return dataDefinitionService.get(BasicConstants.PLUGIN_IDENTIFIER, BasicConstants.MODEL_ADDRESS);
    }

}
