/*
 * Thrifty
 *
 * Copyright (c) Microsoft Corporation
 *
 * All rights reserved.
 *
 * 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
 *
 * THIS CODE IS PROVIDED ON AN  *AS IS* BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
 * WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
 * FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
 *
 * See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
 */
package com.microsoft.thrifty.schema;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.microsoft.thrifty.schema.parser.AnnotationElement;
import com.microsoft.thrifty.schema.parser.EnumElement;
import com.microsoft.thrifty.schema.parser.EnumMemberElement;
import com.microsoft.thrifty.schema.parser.FieldElement;
import com.microsoft.thrifty.schema.parser.FunctionElement;
import com.microsoft.thrifty.schema.parser.ServiceElement;
import com.microsoft.thrifty.schema.parser.StructElement;
import com.microsoft.thrifty.schema.parser.TypedefElement;

import javax.annotation.Nullable;
import java.util.Locale;
import java.util.Map;

/**
 * A mixin encapsulating a common implementation of {@link UserElement},
 * which does not conveniently fit in a single base class.
 */
class UserElementMixin implements UserElement {
    private final String name;
    private final Location location;
    private final String documentation;
    private final ImmutableMap<String, String> annotations;

    UserElementMixin(StructElement struct) {
        this(struct.name(), struct.location(), struct.documentation(), struct.annotations());
    }

    UserElementMixin(FieldElement field) {
        this(field.name(), field.location(), field.documentation(), field.annotations());
    }

    UserElementMixin(EnumElement enumElement) {
        this(enumElement.name(), enumElement.location(), enumElement.documentation(), enumElement.annotations());
    }

    UserElementMixin(EnumMemberElement member) {
        this(member.name(), member.location(), member.documentation(), member.annotations());
    }

    UserElementMixin(TypedefElement element) {
        this(element.newName(), element.location(), element.documentation(), element.annotations());
    }

    UserElementMixin(ServiceElement element) {
        this(element.name(), element.location(), element.documentation(), element.annotations());
    }

    UserElementMixin(FunctionElement element) {
        this(element.name(), element.location(), element.documentation(), element.annotations());
    }

    UserElementMixin(
            String name,
            Location location,
            String documentation,
            @Nullable AnnotationElement annotationElement) {
        this.name = name;
        this.location = location;
        this.documentation = documentation;

        ImmutableMap.Builder<String, String> annotations = ImmutableMap.builder();
        if (annotationElement != null) {
            annotations.putAll(annotationElement.values());
        }
        this.annotations = annotations.build();
    }

    private UserElementMixin(Builder builder) {
        this.name = builder.name;
        this.location = builder.location;
        this.documentation = builder.documentation;
        this.annotations = builder.annotations.build();
    }

    @Override
    public Location location() {
        return location;
    }

    @Override
    public String name() {
        return name;
    }

    @Override
    public String documentation() {
        return documentation;
    }

    @Override
    public ImmutableMap<String, String> annotations() {
        return annotations;
    }

    @Override
    public boolean hasJavadoc() {
        return JavadocUtil.isNonEmptyJavadoc(documentation());
    }

    @Override
    public boolean isDeprecated() {
        return hasThriftOrJavadocAnnotation("deprecated");
    }

    /**
     * Checks for the presence of the given annotation name, in several possible
     * varieties.  Returns true if:
     *
     * <ul>
     *     <li>A Thrift annotation matching the exact name is present</li>
     *     <li>A Thrift annotation equal to the string "thrifty." plus the name is present</li>
     *     <li>The Javadoc contains "@" plus the annotation name</li>
     * </ul>
     *
     * The latter two conditions are officially undocumented, but are present for
     * legacy use.  This behavior is subject to change without notice!
     */
    boolean hasThriftOrJavadocAnnotation(String name) {
        return annotations().containsKey(name)
                || annotations().containsKey("thrifty." + name)
                || (hasJavadoc() && documentation().toLowerCase(Locale.US).contains("@" + name));
    }

    @Override
    public String toString() {
        return "UserElementMixin{"
                + "name='" + name + '\''
                + ", location=" + location
                + ", documentation='" + documentation + '\''
                + ", annotations=" + annotations
                + '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        UserElementMixin that = (UserElementMixin) o;

        if (!name.equals(that.name)) return false;
        if (!location.equals(that.location)) return false;
        if (!documentation.equals(that.documentation)) return false;
        return annotations.equals(that.annotations);

    }

    @Override
    public int hashCode() {
        int result = name.hashCode();
        result = 31 * result + location.hashCode();
        result = 31 * result + documentation.hashCode();
        result = 31 * result + annotations.hashCode();
        return result;
    }

    Builder toBuilder() {
        return new Builder(this);
    }

    static class Builder {
        private String name;
        private Location location;
        private String documentation;
        private ImmutableMap.Builder<String, String> annotations;

        private Builder(UserElement userElement) {
            this.name = userElement.name();
            this.location = userElement.location();
            this.documentation = userElement.documentation();
            this.annotations = ImmutableMap.builder();

            this.annotations.putAll(userElement.annotations());
        }

        Builder name(String name) {
            this.name = Preconditions.checkNotNull(name, "name");
            return this;
        }

        Builder location(Location location) {
            this.location = Preconditions.checkNotNull(location, "name");
            return this;
        }

        Builder documentation(String documentation) {
            if (JavadocUtil.isNonEmptyJavadoc(documentation)) {
                this.documentation = documentation;
            } else {
                this.documentation = "";
            }
            return this;
        }

        Builder annotations(Map<String, String> annotations) {
            this.annotations.putAll(annotations);
            return this;
        }

        UserElementMixin build() {
            return new UserElementMixin(this);
        }
    }
}

@@ -1330,42 +1330,8 @@
 java
-x.annotation.Nullable;%0Aimport java
 .uti
@@ -1360,16 +1360,51 @@
 util.Map
+;%0A%0Aimport javax.annotation.Nullable
 ;%0A%0A/**%0A 
@@ -3565,32 +3565,24 @@
 .annotations
-.build()
 ;%0A    %7D%0A%0A   
@@ -6239,32 +6239,24 @@
 ImmutableMap
-.Builder
 %3CString, Str
@@ -6518,69 +6518,8 @@
 s = 
-ImmutableMap.builder();%0A%0A            this.annotations.putAll(
 user
@@ -6535,25 +6535,24 @@
 nnotations()
-)
 ;%0A        %7D%0A
@@ -7211,37 +7211,79 @@
 this.annotations
-.putA
+ = ImmutableMap.copyOf(Preconditions.checkNotNu
 ll(annotations);
@@ -7281,16 +7281,17 @@
 tations)
+)
 ;%0A      

Replace annotations field in UserElementMixin directly rather than building an additive map (#101)