/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 *
 * Copyright 2005-2016 The Kuali Foundation
 *
 * This program 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, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.kfs.module.cam.document.service.impl;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.kns.service.DataDictionaryService;
import org.kuali.kfs.krad.datadictionary.DataDictionaryEntryBase;
import org.kuali.kfs.krad.document.DocumentBase;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.cam.CamsConstants;
import org.kuali.kfs.module.cam.CamsKeyConstants;
import org.kuali.kfs.module.cam.businessobject.Asset;
import org.kuali.kfs.module.cam.businessobject.AssetLocation;
import org.kuali.kfs.module.cam.businessobject.AssetType;
import org.kuali.kfs.module.cam.document.service.AssetLocationService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.rice.krad.bo.BusinessObject;
import org.kuali.rice.location.api.state.State;
import org.kuali.rice.location.api.state.StateService;

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

public class AssetLocationServiceImpl implements AssetLocationService {
    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AssetLocationService.class);

    private BusinessObjectService businessObjectService;
    private DataDictionaryService DataDictionaryService;

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetLocationService#setOffCampusLocation(org.kuali.kfs.module.cam.businessobject.Asset)
     */
    public void setOffCampusLocation(Asset asset) {
        List<AssetLocation> assetLocations = asset.getAssetLocations();
        AssetLocation offCampusLocation = null;

        for (AssetLocation location : assetLocations) {
            if (CamsConstants.AssetLocationTypeCode.OFF_CAMPUS.equalsIgnoreCase(location.getAssetLocationTypeCode())) {
                // We need a new instance for asset location. Otherwise, if we copy it from the assetLocations collection, it could
                // have newBO and oldBO pointing to the same AssetLocation instance which is bad.
                offCampusLocation = new AssetLocation(location);
                break;
            }
        }

        if (ObjectUtils.isNull(offCampusLocation)) {
            offCampusLocation = new AssetLocation(asset.getCapitalAssetNumber());
            offCampusLocation.setAssetLocationTypeCode(CamsConstants.AssetLocationTypeCode.OFF_CAMPUS);
        }
        asset.setOffCampusLocation(offCampusLocation);
    }

    /**
     * update existing offCampusLocation
     *
     * @see org.kuali.kfs.module.cam.document.service.AssetLocationService#updateOffCampusLocation(org.kuali.kfs.module.cam.businessobject.Asset)
     */
    public void updateOffCampusLocation(Asset asset) {
        AssetLocation offLocation = asset.getOffCampusLocation();
        boolean isOffCampusEmpty = isOffCampusLocationEmpty(offLocation);
        AssetLocation removableOffCampusLocation = null;

        for (AssetLocation location : asset.getAssetLocations()) {
            if (CamsConstants.AssetLocationTypeCode.OFF_CAMPUS.equalsIgnoreCase(location.getAssetLocationTypeCode())) {
                if (isOffCampusEmpty) {
                    removableOffCampusLocation = location;
                } else {
                    location.setAssetLocationCityName(offLocation.getAssetLocationCityName());
                    location.setAssetLocationContactIdentifier(offLocation.getAssetLocationContactIdentifier());
                    location.setAssetLocationContactName(offLocation.getAssetLocationContactName());
                    location.setAssetLocationCountryCode(offLocation.getAssetLocationCountryCode());
                    location.setAssetLocationInstitutionName(offLocation.getAssetLocationInstitutionName());
                    location.setAssetLocationPhoneNumber(offLocation.getAssetLocationPhoneNumber());
                    location.setAssetLocationStateCode(offLocation.getAssetLocationStateCode());
                    location.setAssetLocationStreetAddress(offLocation.getAssetLocationStreetAddress());
                    location.setAssetLocationZipCode(offLocation.getAssetLocationZipCode());
                    return;
                }
            }
        }

        if (removableOffCampusLocation != null) {
            asset.getAssetLocations().remove(removableOffCampusLocation);
        } else if (!isOffCampusEmpty) { // add the check for off-campus empty
            // new offCampusLocation, add it into assetLocation List
            asset.getAssetLocations().add(offLocation);
        }

    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetLocationService#isOffCampusLocationExists(org.kuali.kfs.module.cam.businessobject.AssetLocation)
     */
    public boolean isOffCampusLocationExists(AssetLocation offCampusLocation) {
        if (ObjectUtils.isNotNull(offCampusLocation)) {
            if (CamsConstants.AssetLocationTypeCode.OFF_CAMPUS.equalsIgnoreCase(offCampusLocation.getAssetLocationTypeCode())) {
                return true;
            }
        }
        return false;
    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetLocationService#isOffCampusLocationEmpty(org.kuali.kfs.module.cam.businessobject.AssetLocation)
     */
    public boolean isOffCampusLocationEmpty(AssetLocation offCampusLocation) {
        if (ObjectUtils.isNotNull(offCampusLocation)) {
            if (StringUtils.isNotBlank(offCampusLocation.getAssetLocationCityName()) || StringUtils.isNotBlank(offCampusLocation.getAssetLocationContactIdentifier()) || StringUtils.isNotBlank(offCampusLocation.getAssetLocationContactName()) || StringUtils.isNotBlank(offCampusLocation.getAssetLocationCountryCode()) || StringUtils.isNotBlank(offCampusLocation.getAssetLocationInstitutionName()) || StringUtils.isNotBlank(offCampusLocation.getAssetLocationPhoneNumber()) || StringUtils.isNotBlank(offCampusLocation.getAssetLocationStateCode()) || StringUtils.isNotBlank(offCampusLocation.getAssetLocationStreetAddress()) || StringUtils.isNotBlank(offCampusLocation.getAssetLocationZipCode())) {
                return false;
            }
        }
        return true;
    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetLocationService#validateLocation(java.lang.Object,
     * org.kuali.kfs.module.cam.businessobject.Asset, java.util.Map)
     */
    public boolean validateLocation(Map<LocationField, String> fieldMap, BusinessObject businessObject, boolean isCapital, AssetType assetType) {
        String campusCode = readPropertyValue(businessObject, fieldMap, LocationField.CAMPUS_CODE);
        String buildingCode = readPropertyValue(businessObject, fieldMap, LocationField.BUILDING_CODE);
        String roomNumber = readPropertyValue(businessObject, fieldMap, LocationField.ROOM_NUMBER);
        String subRoomNumber = readPropertyValue(businessObject, fieldMap, LocationField.SUB_ROOM_NUMBER);
        String contactName = readPropertyValue(businessObject, fieldMap, LocationField.CONTACT_NAME);
        String streetAddress = readPropertyValue(businessObject, fieldMap, LocationField.STREET_ADDRESS);
        String cityName = readPropertyValue(businessObject, fieldMap, LocationField.CITY_NAME);
        String stateCode = readPropertyValue(businessObject, fieldMap, LocationField.STATE_CODE);
        String zipCode = readPropertyValue(businessObject, fieldMap, LocationField.ZIP_CODE);
        String countryCode = readPropertyValue(businessObject, fieldMap, LocationField.COUNTRY_CODE);

        // businessObject parameter could be BusinessObjectEntry or TransactionalDocumentEntry
        DataDictionaryService ddService = this.getDataDictionaryService();
        DataDictionaryEntryBase ddEntry = null;
        if (DocumentBase.class.isAssignableFrom(businessObject.getClass())) {
            String docTypeName = ddService.getDocumentTypeNameByClass(businessObject.getClass());
            ddEntry = ddService.getDataDictionary().getDocumentEntry(docTypeName);
        } else {
            ddEntry = this.getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(businessObject.getClass().getName());
        }

        boolean onCampus = StringUtils.isNotBlank(buildingCode) || StringUtils.isNotBlank(roomNumber) || StringUtils.isNotBlank(subRoomNumber);
        boolean offCampus = StringUtils.isNotBlank(contactName) || StringUtils.isNotBlank(streetAddress) || StringUtils.isNotBlank(cityName) || StringUtils.isNotBlank(stateCode) || StringUtils.isNotBlank(zipCode) || StringUtils.isNotBlank(countryCode);

        boolean valid = true;
        if (onCampus && offCampus) {
            putError(fieldMap, LocationField.BUILDING_CODE, CamsKeyConstants.AssetLocation.ERROR_CHOOSE_LOCATION_INFO);
            valid &= false;
        } else {
            if (isCapital) {
                valid &= validateCapitalAssetLocation(assetType, fieldMap, campusCode, buildingCode, roomNumber, subRoomNumber, contactName, streetAddress, cityName, stateCode, zipCode, countryCode, onCampus, offCampus, ddEntry);
            } else {
                valid &= validateNonCapitalAssetLocation(fieldMap, contactName, streetAddress, cityName, stateCode, zipCode, countryCode, onCampus, offCampus);
            }
        }
        return valid;
    }


    protected boolean validateCapitalAssetLocation(AssetType assetType, Map<LocationField, String> fieldMap, String campusCode, String buildingCode, String roomNumber, String subRoomNumber, String contactName, String streetAddress, String cityName, String stateCode, String zipCode, String countryCode, boolean onCampus, boolean offCampus, DataDictionaryEntryBase businessObjectEntry) {
        boolean valid = true;
        if (ObjectUtils.isNull(assetType)) {
            GlobalVariables.getMessageMap().putErrorForSectionId(CamsConstants.LOCATION_INFORMATION_SECTION_ID, CamsKeyConstants.AssetLocation.ERROR_CHOOSE_ASSET_TYPE);
            valid &= false;
        } else {
            String label;
            if (assetType.isRequiredBuildingIndicator() && offCampus) {
                // off campus information not allowed
                if (StringUtils.isNotBlank(contactName)) {
                    label = businessObjectEntry.getAttributeDefinition(fieldMap.get(LocationField.CONTACT_NAME)).getLabel();
                    putError(fieldMap, LocationField.CONTACT_NAME, CamsKeyConstants.AssetLocation.ERROR_LOCATION_NOT_PERMITTED_ASSET_TYPE, new String[]{label, assetType.getCapitalAssetTypeDescription()});
                    valid &= false;
                }
                if (StringUtils.isNotBlank(streetAddress)) {
                    label = businessObjectEntry.getAttributeDefinition(fieldMap.get(LocationField.STREET_ADDRESS)).getLabel();
                    putError(fieldMap, LocationField.STREET_ADDRESS, CamsKeyConstants.AssetLocation.ERROR_LOCATION_NOT_PERMITTED_ASSET_TYPE, new String[]{label, assetType.getCapitalAssetTypeDescription()});
                    valid &= false;
                }

                if (StringUtils.isNotBlank(cityName)) {
                    label = businessObjectEntry.getAttributeDefinition(fieldMap.get(LocationField.CITY_NAME)).getLabel();
                    putError(fieldMap, LocationField.CITY_NAME, CamsKeyConstants.AssetLocation.ERROR_LOCATION_NOT_PERMITTED_ASSET_TYPE, new String[]{label, assetType.getCapitalAssetTypeDescription()});
                    valid &= false;
                }

                if (StringUtils.isNotBlank(stateCode)) {
                    label = businessObjectEntry.getAttributeDefinition(fieldMap.get(LocationField.STATE_CODE)).getLabel();
                    putError(fieldMap, LocationField.STATE_CODE, CamsKeyConstants.AssetLocation.ERROR_LOCATION_NOT_PERMITTED_ASSET_TYPE, new String[]{label, assetType.getCapitalAssetTypeDescription()});
                    valid &= false;
                }

                if (StringUtils.isNotBlank(zipCode)) {
                    label = businessObjectEntry.getAttributeDefinition(fieldMap.get(LocationField.ZIP_CODE)).getLabel();
                    putError(fieldMap, LocationField.ZIP_CODE, CamsKeyConstants.AssetLocation.ERROR_LOCATION_NOT_PERMITTED_ASSET_TYPE, new String[]{label, assetType.getCapitalAssetTypeDescription()});
                    valid &= false;
                }

                if (StringUtils.isNotBlank(countryCode)) {
                    label = businessObjectEntry.getAttributeDefinition(fieldMap.get(LocationField.COUNTRY_CODE)).getLabel();
                    putError(fieldMap, LocationField.COUNTRY_CODE, CamsKeyConstants.AssetLocation.ERROR_LOCATION_NOT_PERMITTED_ASSET_TYPE, new String[]{label, assetType.getCapitalAssetTypeDescription()});
                    valid &= false;
                }
            } else if (!assetType.isMovingIndicator() && !assetType.isRequiredBuildingIndicator() && onCampus) {
                // land information cannot have on-campus
                if (StringUtils.isNotBlank(buildingCode)) {
                    label = businessObjectEntry.getAttributeDefinition(fieldMap.get(LocationField.BUILDING_CODE)).getLabel();
                    putError(fieldMap, LocationField.BUILDING_CODE, CamsKeyConstants.AssetLocation.ERROR_LOCATION_NOT_PERMITTED_ASSET_TYPE, new String[]{label, assetType.getCapitalAssetTypeDescription()});
                    valid &= false;
                }

                if (StringUtils.isNotBlank(roomNumber)) {
                    label = businessObjectEntry.getAttributeDefinition(fieldMap.get(LocationField.ROOM_NUMBER)).getLabel();
                    putError(fieldMap, LocationField.ROOM_NUMBER, CamsKeyConstants.AssetLocation.ERROR_LOCATION_NOT_PERMITTED_ASSET_TYPE, new String[]{label, assetType.getCapitalAssetTypeDescription()});
                    valid &= false;
                }

                if (StringUtils.isNotBlank(subRoomNumber)) {
                    label = businessObjectEntry.getAttributeDefinition(fieldMap.get(LocationField.SUB_ROOM_NUMBER)).getLabel();
                    putError(fieldMap, LocationField.SUB_ROOM_NUMBER, CamsKeyConstants.AssetLocation.ERROR_LOCATION_NOT_PERMITTED_ASSET_TYPE, new String[]{label, assetType.getCapitalAssetTypeDescription()});
                    valid &= false;
                }
            } else if (onCampus) {
                valid = validateOnCampusLocation(fieldMap, assetType, campusCode, buildingCode, roomNumber, subRoomNumber);
            } else if (offCampus) {
                valid = validateOffCampusLocation(fieldMap, contactName, streetAddress, cityName, stateCode, zipCode, countryCode);
            } else if (assetType.isMovingIndicator() || assetType.isRequiredBuildingIndicator()) {
                putError(fieldMap, LocationField.BUILDING_CODE, CamsKeyConstants.AssetLocation.ERROR_LOCATION_INFO_REQUIRED);
                valid &= false;
            }
        }
        return valid;
    }

    protected boolean validateNonCapitalAssetLocation(Map<LocationField, String> fieldMap, String contactName, String streetAddress, String cityName, String stateCode, String zipCode, String countryCode, boolean onCampus, boolean offCampus) {
        boolean valid = true;
        if (offCampus) {
            valid = validateOffCampusLocation(fieldMap, contactName, streetAddress, cityName, stateCode, zipCode, countryCode);
        }
        return valid;
    }


    /**
     * Convenience method to append the path prefix
     */
    protected void putError(Map<LocationField, String> fieldMap, LocationField field, String errorKey, String... errorParameters) {
        GlobalVariables.getMessageMap().putError(fieldMap.get(field), errorKey, errorParameters);
    }

    protected boolean validateOnCampusLocation(Map<LocationField, String> fieldMap, AssetType assetType, String campusCode, String buildingCode, String buildingRoomNumber, String subRoomNumber) {
        boolean valid = true;
        if (assetType.isMovingIndicator()) {
            if (StringUtils.isBlank(buildingCode)) {
                putError(fieldMap, LocationField.BUILDING_CODE, CamsKeyConstants.AssetLocation.ERROR_ONCAMPUS_BUILDING_CODE_REQUIRED, assetType.getCapitalAssetTypeDescription());
                valid &= false;
            }
            if (StringUtils.isBlank(buildingRoomNumber)) {
                putError(fieldMap, LocationField.ROOM_NUMBER, CamsKeyConstants.AssetLocation.ERROR_ONCAMPUS_BUILDING_ROOM_NUMBER_REQUIRED, assetType.getCapitalAssetTypeDescription());
                valid &= false;
            }
        }
        if (assetType.isRequiredBuildingIndicator()) {
            if (StringUtils.isBlank(buildingCode)) {
                putError(fieldMap, LocationField.BUILDING_CODE, CamsKeyConstants.AssetLocation.ERROR_ONCAMPUS_BUILDING_CODE_REQUIRED, assetType.getCapitalAssetTypeDescription());
                valid &= false;
            }
            if (StringUtils.isNotBlank(buildingRoomNumber)) {
                putError(fieldMap, LocationField.ROOM_NUMBER, CamsKeyConstants.AssetLocation.ERROR_ONCAMPUS_BUILDING_ROOM_NUMBER_NOT_PERMITTED, assetType.getCapitalAssetTypeDescription());
                valid &= false;
            }
            if (StringUtils.isNotBlank(subRoomNumber)) {
                putError(fieldMap, LocationField.SUB_ROOM_NUMBER, CamsKeyConstants.AssetLocation.ERROR_ONCAMPUS_SUB_ROOM_NUMBER_NOT_PERMITTED, assetType.getCapitalAssetTypeDescription());
                valid &= false;
            }
        }
        return valid;
    }

    protected boolean validateOffCampusLocation(Map<LocationField, String> fieldMap, String contactName, String streetAddress, String cityName, String stateCode, String zipCode, String countryCode) {
        boolean valid = true;
        boolean isCountryUS = false;
        if (isBlank(fieldMap, LocationField.COUNTRY_CODE, countryCode)) {
            putError(fieldMap, LocationField.COUNTRY_CODE, CamsKeyConstants.AssetLocation.ERROR_OFFCAMPUS_COUNTRY_REQUIRED);
            valid &= false;
        } else {
            isCountryUS = countryCode.equals(KFSConstants.COUNTRY_CODE_UNITED_STATES);
        }

        if (isBlank(fieldMap, LocationField.CONTACT_NAME, contactName)) {
            putError(fieldMap, LocationField.CONTACT_NAME, CamsKeyConstants.AssetLocation.ERROR_OFFCAMPUS_CONTACT_REQUIRED);
            valid &= false;
        }

        if (isBlank(fieldMap, LocationField.STREET_ADDRESS, streetAddress)) {
            putError(fieldMap, LocationField.STREET_ADDRESS, CamsKeyConstants.AssetLocation.ERROR_OFFCAMPUS_ADDRESS_REQUIRED);
            valid &= false;
        }
        if (isBlank(fieldMap, LocationField.CITY_NAME, cityName)) {
            putError(fieldMap, LocationField.CITY_NAME, CamsKeyConstants.AssetLocation.ERROR_OFFCAMPUS_CITY_REQUIRED);
            valid &= false;
        }


        if (isCountryUS) {
            if (isBlank(fieldMap, LocationField.STATE_CODE, stateCode)) {
                putError(fieldMap, LocationField.STATE_CODE, CamsKeyConstants.AssetLocation.ERROR_OFFCAMPUS_STATE_REQUIRED);
                valid &= false;
            }
            if (isBlank(fieldMap, LocationField.ZIP_CODE, zipCode)) {
                putError(fieldMap, LocationField.ZIP_CODE, CamsKeyConstants.AssetLocation.ERROR_OFFCAMPUS_ZIP_REQUIRED);
                valid &= false;
            }
            if (!isBlank(fieldMap, LocationField.STATE_CODE, stateCode)) {
                Map assetLocationMap = new HashMap();
                assetLocationMap.put(KFSPropertyConstants.POSTAL_STATE_CODE, stateCode);
                State locationState = SpringContext.getBean(StateService.class).getState(countryCode, stateCode);
                if (ObjectUtils.isNull(locationState)) {
                    putError(fieldMap, LocationField.STATE_CODE, CamsKeyConstants.AssetLocation.ERROR_INVALID_OFF_CAMPUS_STATE, stateCode);
                    valid &= false;
                }
            }
        }

        return valid;
    }

    protected boolean isBlank(Map<LocationField, String> fieldMap, LocationField field, String countryCode) {
        return fieldMap.get(field) != null && StringUtils.isBlank(countryCode);
    }

    protected String readPropertyValue(BusinessObject currObject, Map<LocationField, String> fieldMap, LocationField field) {
        String stringValue = null;
        try {
            String propertyName = fieldMap.get(field);
            if (propertyName != null) {
                stringValue = (String) ObjectUtils.getNestedValue(currObject, propertyName);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return stringValue;
    }

    public BusinessObjectService getBusinessObjectService() {
        return businessObjectService;
    }

    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
        this.businessObjectService = businessObjectService;
    }

    public DataDictionaryService getDataDictionaryService() {
        return DataDictionaryService;
    }

    public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
        DataDictionaryService = dataDictionaryService;
    }

}
