/*
 * Copyright 2017 requery.io
 *
 * Licensed 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
 *
 *     http://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 io.requery.jackson;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.ObjectIdInfo;
import io.requery.Column;
import io.requery.ForeignKey;
import io.requery.JunctionTable;
import io.requery.ManyToMany;
import io.requery.ManyToOne;
import io.requery.OneToMany;
import io.requery.OneToOne;
import io.requery.Table;
import io.requery.Transient;
import io.requery.View;
import io.requery.meta.Attribute;
import io.requery.meta.EntityModel;
import io.requery.meta.Type;
import io.requery.proxy.EntityProxy;
import io.requery.proxy.PropertyState;

import java.lang.reflect.Field;

/**
 * Implementation of {@link AnnotationIntrospector} that creates Object id type and reference
 * information for entity types annotated with requery annotations.
 *
 * @author Nikhil Purushe
 */
class EntityAnnotationIntrospector extends AnnotationIntrospector {

    private final EntityModel model;
    private final Version version;
    private boolean useTableNames;

    EntityAnnotationIntrospector(EntityModel model, Version version) {
        this.model = model;
        this.version = version;
        this.useTableNames = false;
    }

    @Override
    public Version version() {
        return version;
    }

    @Override
    public ObjectIdInfo findObjectIdInfo(Annotated annotated) {
        Class<?> rawClass = annotated.getType().getRawClass();
        for (Type<?> type : model.getTypes()) {
            if (type.getClassType() == rawClass && type.getSingleKeyAttribute() != null) {
                Attribute<?, ?> attribute = type.getSingleKeyAttribute();
                String name = attribute.getPropertyName();
                if (useTableNames) {
                    name = attribute.getName();
                }

                // if the name is overridden use that
                Class<?> superClass = rawClass.getSuperclass();
                while (superClass != Object.class && superClass != null) {
                    try {
                        Field field = superClass.getDeclaredField(attribute.getPropertyName());
                        JsonProperty jsonProperty = field.getAnnotation(JsonProperty.class);
                        if (jsonProperty != null) {
                            name = jsonProperty.value();
                            break;
                        }
                    } catch (NoSuchFieldException ignored) {
                    }
                    superClass = superClass.getSuperclass();
                }

                return new ObjectIdInfo(new PropertyName(name), rawClass,
                        ObjectIdGenerators.PropertyGenerator.class,
                        EntityStoreResolver.class);
            }
        }
        return super.findObjectIdInfo(annotated);
    }

    @Override
    public ReferenceProperty findReferenceType(AnnotatedMember member) {
        if (member.hasAnnotation(OneToOne.class)) {
            if (member.hasAnnotation(ForeignKey.class)) {
                return ReferenceProperty.managed(null);
            } else {
                return ReferenceProperty.back(null);
            }
        } else if (member.hasAnnotation(OneToMany.class)) {
            return ReferenceProperty.back(null);
        } else if (member.hasAnnotation(ManyToOne.class)) {
            return ReferenceProperty.managed(null);
        } else if (member.hasAnnotation(ManyToMany.class)) {
            if (member.hasAnnotation(JunctionTable.class)) {
                return ReferenceProperty.managed(null);
            } else {
                return ReferenceProperty.back(null);
            }
        }
        return super.findReferenceType(member);
    }

    @Override
    public PropertyName findNameForDeserialization(Annotated annotated) {
        if (useTableNames) {
            return getMappedName(annotated);
        } else {
            return super.findNameForDeserialization(annotated);
        }
    }

    @Override
    public PropertyName findNameForSerialization(Annotated annotated) {
        if (useTableNames) {
            return getMappedName(annotated);
        } else {
            return super.findNameForSerialization(annotated);
        }
    }

    private PropertyName getMappedName(Annotated annotated) {
        if (annotated.hasAnnotation(Table.class)) {
            Table table = annotated.getAnnotation(Table.class);
            return new PropertyName(table.name());
        } if (annotated.hasAnnotation(View.class)) {
            View view = annotated.getAnnotation(View.class);
            return new PropertyName(view.name());
        } else if (annotated.hasAnnotation(Column.class)) {
            Column column = annotated.getAnnotation(Column.class);
            return new PropertyName(column.name());
        } else {
            return null;
        }
    }

    @Override
    public boolean hasIgnoreMarker(AnnotatedMember member) {
        return member.hasAnnotation(Transient.class);
    }

    @Override
    public Boolean isIgnorableType(AnnotatedClass annotatedClass) {
        return PropertyState.class.isAssignableFrom(annotatedClass.getAnnotated()) ||
               EntityProxy.class.isAssignableFrom(annotatedClass.getAnnotated());
    }
}

@@ -1172,187 +1172,8 @@
 mn;%0A
-import io.requery.ForeignKey;%0Aimport io.requery.JunctionTable;%0Aimport io.requery.ManyToMany;%0Aimport io.requery.ManyToOne;%0Aimport io.requery.OneToMany;%0Aimport io.requery.OneToOne;%0A
 impo
@@ -3618,901 +3618,8 @@
  %7D%0A%0A
-    @Override%0A    public ReferenceProperty findReferenceType(AnnotatedMember member) %7B%0A        if (member.hasAnnotation(OneToOne.class)) %7B%0A            if (member.hasAnnotation(ForeignKey.class)) %7B%0A                return ReferenceProperty.managed(null);%0A            %7D else %7B%0A                return ReferenceProperty.back(null);%0A            %7D%0A        %7D else if (member.hasAnnotation(OneToMany.class)) %7B%0A            return ReferenceProperty.back(null);%0A        %7D else if (member.hasAnnotation(ManyToOne.class)) %7B%0A            return ReferenceProperty.managed(null);%0A        %7D else if (member.hasAnnotation(ManyToMany.class)) %7B%0A            if (member.hasAnnotation(JunctionTable.class)) %7B%0A                return ReferenceProperty.managed(null);%0A            %7D else %7B%0A                return ReferenceProperty.back(null);%0A            %7D%0A        %7D%0A        return super.findReferenceType(member);%0A    %7D%0A%0A
     

Resolve #445 Jackson use only ObjectIdInfo for managing references