/*
 * Copyright 2012-2015 the original author or authors.
 *
 * 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 org.springframework.data.rest.webmvc;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.core.EmbeddedWrapper;
import org.springframework.util.Assert;

import com.fasterxml.jackson.annotation.JsonIgnore;

/**
 * A Spring HATEOAS {@link Resource} subclass that holds a reference to the entity's {@link PersistentEntity} metadata.
 * 
 * @author Jon Brisbin
 * @author Oliver Gierke
 */
public class PersistentEntityResource extends Resource<Object> {

	private static final Iterable<EmbeddedWrapper> NO_EMBEDDEDS = new NoLinksResources<EmbeddedWrapper>(
			Collections.<EmbeddedWrapper> emptyList());

	private final PersistentEntity<?, ?> entity;
	private final Iterable<EmbeddedWrapper> embeddeds;
	private final boolean isNew;

	/**
	 * Creates a new {@link PersistentEntityResource} for the given {@link PersistentEntity}, content, embedded
	 * {@link Resources}, links and flag whether to render all associations.
	 * 
	 * @param entity must not be {@literal null}.
	 * @param content must not be {@literal null}.
	 * @param links must not be {@literal null}.
	 * @param embeddeds can be {@literal null}.
	 */
	private PersistentEntityResource(PersistentEntity<?, ?> entity, Object content, Iterable<Link> links,
			Iterable<EmbeddedWrapper> embeddeds, boolean isNew) {

		super(content, links);

		Assert.notNull(entity, "PersistentEntity must not be null!");

		this.entity = entity;
		this.embeddeds = embeddeds == null ? NO_EMBEDDEDS : embeddeds;
		this.isNew = isNew;
	}

	/**
	 * Returns the {@link PersistentEntity} for the underlying instance.
	 * 
	 * @return
	 */
	public PersistentEntity<?, ? extends PersistentProperty<?>> getPersistentEntity() {
		return entity;
	}

	/**
	 * Returns the {@link PersistentPropertyAccessor} for the underlying content bean.
	 * 
	 * @return
	 */
	public PersistentPropertyAccessor getPropertyAccessor() {
		return entity.getPropertyAccessor(getContent());
	}

	/**
	 * Returns the resources that are supposed to be rendered in the {@code _embedded} clause.
	 * 
	 * @return the embeddeds
	 */
	public Iterable<EmbeddedWrapper> getEmbeddeds() {
		return embeddeds;
	}

	/**
	 * Returns whether the content of the resource is a new entity about to be created. Used to distinguish between
	 * creation and updates for incoming requests.
	 * 
	 * @return
	 */
	public boolean isNew() {
		return isNew;
	}

	/**
	 * Creates a new {@link Builder} to create {@link PersistentEntityResource}s eventually.
	 * 
	 * @param content must not be {@literal null}.
	 * @param entity must not be {@literal null}.
	 * @return
	 */
	public static Builder build(Object content, PersistentEntity<?, ?> entity) {
		return new Builder(content, entity);
	}

	/**
	 * Builder to create {@link PersistentEntityResource} instances.
	 *
	 * @author Oliver Gierke
	 */
	public static class Builder {

		private final Object content;
		private final PersistentEntity<?, ?> entity;
		private final List<Link> links = new ArrayList<Link>();

		private Iterable<EmbeddedWrapper> embeddeds;

		/**
		 * Creates a new {@link Builder} instance for the given content and {@link PersistentEntity}.
		 * 
		 * @param content must not be {@literal null}.
		 * @param entity must not be {@literal null}.
		 */
		private Builder(Object content, PersistentEntity<?, ?> entity) {

			Assert.notNull(content, "Content must not be null!");
			Assert.notNull(entity, "PersistentEntity must not be null!");

			this.content = content;
			this.entity = entity;
		}

		/**
		 * Configures the builder to embed the given {@link EmbeddedWrapper} instances. Creates a {@link Resources} instance
		 * to make sure the {@link EmbeddedWrapper} handling gets applied to the serialization output ignoring the links.
		 * 
		 * @param resources can be {@literal null}.
		 * @return the builder
		 */
		public Builder withEmbedded(Iterable<EmbeddedWrapper> resources) {

			this.embeddeds = resources == null ? null : new NoLinksResources<EmbeddedWrapper>(resources);
			return this;
		}

		/**
		 * Adds the given {@link Link} to the {@link PersistentEntityResource}.
		 * 
		 * @param link must not be {@literal null}.
		 * @return the builder
		 */
		public Builder withLink(Link link) {

			Assert.notNull(link, "Link must not be null!");

			this.links.add(link);
			return this;
		}

		/**
		 * Finally creates the {@link PersistentEntityResource} instance.
		 * 
		 * @return
		 */
		public PersistentEntityResource build() {
			return new PersistentEntityResource(entity, content, links, embeddeds, false);
		}

		/**
		 * Finally creates the {@link PersistentEntityResource} instance to symbolize the contained entity is about to be
		 * created.
		 * 
		 * @return
		 */
		public PersistentEntityResource forCreation() {
			return new PersistentEntityResource(entity, content, links, embeddeds, true);
		}
	}

	private static class NoLinksResources<T> extends Resources<T> {

		public NoLinksResources(Iterable<T> content) {
			super(content);
		}

		/* 
		 * (non-Javadoc)
		 * @see org.springframework.hateoas.ResourceSupport#getLinks()
		 */
		@Override
		@JsonIgnore
		public List<Link> getLinks() {
			return super.getLinks();
		}
	}
}
