/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

package grails.plugin.formfields

import groovy.transform.Canonical
import groovy.transform.CompileStatic
import groovy.transform.ToString

import org.springframework.validation.FieldError

import grails.util.GrailsNameUtils
import grails.validation.Validateable
import org.grails.datastore.gorm.GormValidateable
import org.grails.datastore.mapping.model.PersistentEntity
import org.grails.datastore.mapping.model.PersistentProperty
import org.grails.scaffolding.model.property.Constrained
import org.grails.scaffolding.model.property.DomainProperty

@CompileStatic
@Canonical
@ToString(includes = ['beanType', 'propertyName', 'propertyType'])
class DelegatingBeanPropertyAccessorImpl implements BeanPropertyAccessor {

    private DomainProperty domainProperty
    private Object rootBean
    private Object value
    private String pathFromRoot
    final Class beanType
    final String propertyName
    final Class propertyType
    final boolean addPathFromRoot

    DelegatingBeanPropertyAccessorImpl(Object rootBean, Object value, Class propertyType, String pathFromRoot, DomainProperty domainProperty, boolean addPathFromRoot) {
        this.rootBean = rootBean
        this.value = value
        this.pathFromRoot = pathFromRoot
        this.domainProperty = domainProperty
        this.propertyType = propertyType
        this.propertyName = domainProperty.name
        this.beanType = domainProperty.beanType
        this.addPathFromRoot = addPathFromRoot
    }

    @Override
    Object getRootBean() {
        rootBean
    }

    @Override
    Class getRootBeanType() {
        rootBean.getClass()
    }

    @Override
    String getPathFromRoot() {
        pathFromRoot
    }

    @Override
    PersistentEntity getEntity() {
        domainProperty.domainClass
    }

    @Override
    List<Class> getBeanSuperclasses() {
        BeanPropertyAccessorImpl.getSuperclassesAndInterfaces(beanType)
    }

    @Override
    List<Class> getPropertyTypeSuperclasses() {
        BeanPropertyAccessorImpl.getSuperclassesAndInterfaces(propertyType)
    }

    @Override
    Object getValue() {
        value
    }

    @Override
    PersistentProperty getDomainProperty() {
        domainProperty.persistentProperty
    }

    @Override
    Constrained getConstraints() {
        domainProperty.constrained
    }

    @Override
    List<String> getLabelKeys() {
        List labelKeys = []
        if (rootBean) {
            labelKeys.add("${GrailsNameUtils.getPropertyName(rootBeanType.simpleName)}.${pathFromRoot}.label".replaceAll(/\[(.+)\]/, ''))
            if (addPathFromRoot) {
                labelKeys.add("${pathFromRoot}.label".replaceAll(/\[(.+)\]/, ''))
            }
        }
        labelKeys.addAll(domainProperty.labelKeys)
        labelKeys.unique() as List<String>
    }

    @Override
    String getDefaultLabel() {
        domainProperty.defaultLabel
    }

    @Override
    List<FieldError> getErrors() {
        if (rootBean instanceof Validateable) {
            return (rootBean as Validateable).errors.getFieldErrors(pathFromRoot)
        } else if (rootBean instanceof GormValidateable) {
            return (rootBean as GormValidateable).errors.getFieldErrors(pathFromRoot)
        }
        return []
    }

    @Override
    boolean isRequired() {
        domainProperty.required
    }

    @Override
    boolean isInvalid() {
        !errors.isEmpty()
    }

    @Override
    int hashCode() {
        return Objects.hash(beanType, propertyName, propertyType)
    }

    @Override
    boolean equals(Object obj) {
        this.hashCode() == obj?.hashCode()
    }
}
