package com.atlassian.bitbucket.rest.project;

import com.atlassian.bitbucket.rest.avatar.AvatarAware;
import com.atlassian.bitbucket.project.PersonalProject;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.project.ProjectType;
import com.atlassian.bitbucket.project.ProjectVisitor;
import com.atlassian.bitbucket.rest.annotation.JsonSurrogate;
import com.atlassian.bitbucket.rest.RestLinkedMapEntity;
import com.atlassian.bitbucket.rest.util.RestNamedLink;
import com.atlassian.bitbucket.rest.util.RestPage;
import com.atlassian.bitbucket.rest.util.RestRelatedLinks;
import com.atlassian.bitbucket.rest.util.RestDocUtils;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.map.annotate.JsonSerialize;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.function.Function;

import static com.atlassian.bitbucket.rest.util.RestDocUtils.*;

@JsonSerialize
@JsonSurrogate(Project.class)
public class RestProject extends RestLinkedMapEntity implements AvatarAware, Project {

    private static final String AVATAR = "avatar";
    private static final String DESCRIPTION = "description";
    private static final String ID = "id";
    private static final String KEY = "key";
    private static final String NAME = "name";
    private static final String NAMESPACE = "namespace";
    private static final String PUBLIC = "public";
    private static final String TYPE = "type";

    public static final Function<Project, RestProject> REST_TRANSFORM = RestProject::fromProject;

    public static final ProjectVisitor<RestProject> TRANSFORM_VISITOR = new ProjectVisitor<RestProject>() {

        @Override
        public RestProject visit(@Nonnull Project project) {
            return new RestProject(project);
        }

        @Override
        public RestProject visit(@Nonnull PersonalProject project) {
            return new RestPersonalProject(project);
        }
    };

    @SuppressWarnings("unused") //Required by Jersey
    public RestProject() {
    }

    public RestProject(Project project) {
        this(project.getId(), project.getNamespace(), project.getKey(), project.getName(), project.getDescription(), project.getType(), project.isPublic());
    }

    /**
     * Common properties between model and documentation construction
     */
    RestProject(int id, String namespace, String key, String name, String description, ProjectType type, Boolean publiclyAccessible) {
        putIfNotNull(NAMESPACE, namespace);
        putIfNotNull(KEY, key);
        if (id > 0) {
            put(ID, id);
        }
        putIfNotNull(NAME, name);
        putIfNotNull(DESCRIPTION, description);
        putIfNotNull(PUBLIC, publiclyAccessible);
        if (type != null) {
            put(TYPE, type.name());
        }
    }

    // Required for REST documentation
    private RestProject(int id, String namespace, String key, String name, String description, ProjectType type,
                        Boolean publiclyAccessible, RestNamedLink selfLink) {
        this(id, namespace, key, name, description, type, publiclyAccessible);

        setLinks(new RestRelatedLinks.Builder()
                .addRelatedLink(RestRelatedLinks.RELATIONSHIP_SELF, selfLink)
                .build());
    }

    private RestProject(Map<String, Object> values) {
        super(values);
    }

    @Override
    public <T> T accept(@Nonnull ProjectVisitor<T> visitor) {
        return visitor.visit(this);
    }

    public String getAvatar() {
        return StringUtils.trimToNull(getStringProperty(AVATAR));
    }

    @Override
    public String getDescription() {
        return getStringProperty(DESCRIPTION);
    }

    @Override
    public int getId() {
        return getIntProperty(ID);
    }

    @Nonnull
    @Override
    public String getKey() {
        return getStringProperty(KEY);
    }

    @Nonnull
    @Override
    public String getName() {
        return getStringProperty(NAME);
    }

    @Override
    public String getNamespace() {
        return getStringProperty(NAMESPACE);
    }

    @Nonnull
    @Override
    public ProjectType getType() {
        return getEnumProperty(TYPE, ProjectType.class);
    }

    public boolean hasAvatar() {
        return StringUtils.isNotBlank(getStringProperty(AVATAR));
    }

    public boolean hasPublic() {
        return get(PUBLIC) != null;
    }

    @Override
    public boolean isPublic() {
        return getBoolProperty(PUBLIC);
    }

    @Override
    public void setAvatarUrl(String url) {
        put(AVATAR_URL, url);
    }

    public static RestProject fromProject(Project project) {
        if (project == null) {
            return null;
        }

        //It's not safe to use instanceof or normal casts; this may receive a Hibernate proxy of InternalProject, which
        //_represents_ a PersonalProject but doesn't actually implement that interface. Going through the visitor forces
        //Hibernate to resolve down to a PersonalProject and makes everything work correctly
        return project.accept(TRANSFORM_VISITOR);
    }

    @Nullable
    @SuppressWarnings("unchecked")
    public static RestProject valueOf(@Nullable Object value) {
        if (value instanceof RestProject) {
            return (RestProject) value;
        } else if (value instanceof Map) {
            return new RestProject((Map) value);
        }
        return null;
    }

    // REST Documentation Examples
    private static final ImmutableMap<String, String> EXAMPLE_AVATAR_PARAM = ImmutableMap.of("avatar",
            "data:image/png;base64,<base64-encoded-image-data>");

    public static final RestProject REQUEST_EXAMPLE = RestDocUtils.decorate(new RestProject(0, null, PROJECT_KEY,
            PROJECT_NAME, PROJECT_DESCRIPTION, null, null), EXAMPLE_AVATAR_PARAM);

    public static final RestProject REQUEST_EXAMPLE_KEY_ONLY = new RestProject(0, null, PROJECT_KEY,
            null, null, null, null);

    public static final RestProject RESPONSE_EXAMPLE_PERSONAL = new RestProject(2, null, "~JDOE", "John Doe", null,
            ProjectType.PERSONAL, null, selfLink(Project.class));

    public static final RestProject RESPONSE_EXAMPLE = new RestProject(PROJECT_ID, null, PROJECT_KEY, PROJECT_NAME,
            PROJECT_DESCRIPTION, ProjectType.NORMAL, true, selfLink(Project.class));

    public static final RestPage<RestProject> PAGE_EXAMPLE = pageOf(RESPONSE_EXAMPLE);
}
