/*
 * Copyright (c) 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 id.amberjack.hod.jaxrs;

import java.lang.annotation.Annotation;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.Link.Builder;
import javax.ws.rs.core.Response.Status.Family;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Variant;
import javax.ws.rs.ext.RuntimeDelegate;
import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate;

/**
 * @author indroneel
 *
 */

public class AmberResponse extends Response {

	private StatusType      statusInfo;
	private Object          entity;
	private Annotation[]    entityAnns;
	private EntityTag       entityTag;
	private MediaType       mediaType;
	private Locale          language;
	private Set<String>     allowedMethods;
	private Date            responseDate;
	private Date            lastModifiedDate;
	private URI             location;

	private MultivaluedMap<String, Object> headers;
	private Map<String, NewCookie>         cookies;

	private AmberResponse() {
		//So that you cannot directly instantiate this class outside of its builder.
		allowedMethods = new HashSet<>();
		headers = new MultivaluedHashMap<>();
		cookies = new HashMap<>();
	}

	public Annotation[] getEntityAnnotations() {
		return entityAnns;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////
	// Methods of base class Response

	@Override
	public int getStatus() {
		if(statusInfo == null) {
			return (entity != null)
				? Response.Status.OK.getStatusCode()
				: Response.Status.NO_CONTENT.getStatusCode();
		}
		return statusInfo.getStatusCode();
	}

	@Override
	public StatusType getStatusInfo() {
		return statusInfo;
	}

	@Override
	public Object getEntity() {
		return entity;
	}

/**
 * Note: This method should never be invoked on the server-side of things, as there is no underlying
 * input stream asociated with the response object. The current implementation of this method always
 * returns <tt>null</tt>.
 */

	@Override
	public <T> T readEntity(Class<T> entityType) {
		return null;
	}

/**
 * Note: This method should never be invoked on the server-side of things, as there is no underlying
 * input stream asociated with the response object. The current implementation of this method always
 * returns <tt>null</tt>.
 */

	@Override
	public <T> T readEntity(GenericType<T> entityType) {
		return null;
	}

/**
 * Note: This method should never be invoked on the server-side of things, as there is no underlying
 * input stream asociated with the response object. The current implementation of this method always
 * returns <tt>null</tt>.
 */

	@Override
	public <T> T readEntity(Class<T> entityType, Annotation[] annotations) {
		return null;
	}

/**
 * Note: This method should never be invoked on the server-side of things, as there is no underlying
 * input stream asociated with the response object. The current implementation of this method always
 * returns <tt>null</tt>.
 */

	@Override
	public <T> T readEntity(GenericType<T> entityType, Annotation[] annotations) {
		return null;
	}

	@Override
	public boolean hasEntity() {
		return (entity != null);
	}

/**
 * Note: This method should never be invoked on the server-side of things, as there is no underlying
 * input stream asociated with the response object. The current implementation of this method always
 * returns <tt>false</tt>.
 */

	@Override
	public boolean bufferEntity() {
		return false;
	}

/**
 * Note: This method should never be invoked on the server-side of things, as there is no underlying
 * input stream asociated with the response object.
 */

	@Override
	public void close() {
		// NOOP
	}

	@Override
	public MediaType getMediaType() {
		return mediaType;
	}

	@Override
	public Locale getLanguage() {
		return language;
	}

/**
 * Note: This method should never be invoked on the server-side of things, as there is no underlying
 * input stream asociated with the response object. The current implementation of this method always
 * returns <tt>-1</tt>.
 */

	@Override
	public int getLength() {
		return -1;
	}

	@Override
	public Set<String> getAllowedMethods() {
		return allowedMethods;
	}

	@Override
	public Map<String, NewCookie> getCookies() {
		return cookies;
	}

	@Override
	public EntityTag getEntityTag() {
		return entityTag;
	}

	@Override
	public Date getDate() {
		return responseDate;
	}

	@Override
	public Date getLastModified() {
		return lastModifiedDate;
	}

	@Override
	public URI getLocation() {
		return location;
	}

	/* (non-Javadoc)
	 * @see javax.ws.rs.core.Response#getLinks()
	 */
	@Override
	public Set<Link> getLinks() {
		// TODO Auto-generated method stub
		return null;
	}

	/* (non-Javadoc)
	 * @see javax.ws.rs.core.Response#hasLink(java.lang.String)
	 */
	@Override
	public boolean hasLink(String relation) {
		// TODO Auto-generated method stub
		return false;
	}

	/* (non-Javadoc)
	 * @see javax.ws.rs.core.Response#getLink(java.lang.String)
	 */
	@Override
	public Link getLink(String relation) {
		// TODO Auto-generated method stub
		return null;
	}

	/* (non-Javadoc)
	 * @see javax.ws.rs.core.Response#getLinkBuilder(java.lang.String)
	 */
	@Override
	public Builder getLinkBuilder(String relation) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public MultivaluedMap<String, Object> getMetadata() {
		return headers;
	}

	@Override
	public MultivaluedMap<String, Object> getHeaders() {
		return headers;
	}

	@Override
	public MultivaluedMap<String, String> getStringHeaders() {
		MultivaluedHashMap<String, String> strHdrs = new MultivaluedHashMap<>();
		for(String key : headers.keySet()) {
			strHdrs.addAll(key, convert(headers.get(key)));
		}
		return strHdrs;
	}

	@Override
	public String getHeaderString(String name) {
		List<Object> values = headers.get(name);
		if(values == null) {
			return null;
		}
		List<String> strValues = convert(values);
		Iterator<String> iter = strValues.iterator();
		StringBuilder sb = new StringBuilder();
		if(iter.hasNext()) {
			sb.append(iter.next());
			while(iter.hasNext()) {
				sb.append(',').append(iter.next());
			}
		}
		return sb.toString();
	}

	////////////////////////////////////////////////////////////////////////////////////////////////
	// Helper methods

	@SuppressWarnings("unchecked")
	private List<String> convert(List<Object> values) {
		if(values == null || values.isEmpty()) {
			return new ArrayList<String>();
		}
		ArrayList<String> result = new ArrayList<>();
		for(Object obj : values) {
			HeaderDelegate<Object> hd = (HeaderDelegate<Object>)
					RuntimeDelegate.getInstance().createHeaderDelegate(obj.getClass());
			if(hd != null) {
				result.add(hd.toString(obj));
			}
			else {
				result.add(obj.toString());
			}
		}
		return result;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////
	// ResponseBuilder implementation

	public static class AmberResponseBuilder extends ResponseBuilder {

		private AmberResponse response;

		public AmberResponseBuilder() {
			response = new AmberResponse();
		}

		@Override
		public Response build() {
			response.responseDate = new Date();
			return response;
		}

		/* (non-Javadoc)
		 * @see javax.ws.rs.core.Response.ResponseBuilder#clone()
		 */
		@Override
		public ResponseBuilder clone() {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public ResponseBuilder status(int status) {
			response.statusInfo = Response.Status.fromStatusCode(status);
			return this;
		}

		@Override
		public ResponseBuilder status(int status, String reasonPhrase) {
			response.statusInfo = new StatusType() {
				public int getStatusCode() {
					return status;
				}

				public String getReasonPhrase() {
					return reasonPhrase;
				}

				public Family getFamily() {
					Response.Status stat = Response.Status.fromStatusCode(status);
					if(stat != null) {
						return stat.getFamily();
					}
					return null;
				}
			};
			return this;
		}

		@Override
		public ResponseBuilder entity(Object entity) {
			response.entity = entity;
			return this;
		}

		@Override
		public ResponseBuilder entity(Object entity, Annotation[] annotations) {
			response.entity = entity;
			if(annotations != null) {
				response.entityAnns = new Annotation[annotations.length];
				for(int i=0; i<annotations.length; i++) {
					response.entityAnns[i] = annotations[i];
				}
			}
			return this;
		}

		@Override
		public ResponseBuilder allow(String... methods) {
			response.allowedMethods.clear();
			for(String mthd : methods) {
				response.allowedMethods.add(mthd.toUpperCase());
			}
			return this;
		}

		@Override
		public ResponseBuilder allow(Set<String> methods) {
			response.allowedMethods.clear();
			for(String mthd : methods) {
				response.allowedMethods.add(mthd.toUpperCase());
			}
			return this;
		}

		@Override
		public ResponseBuilder cacheControl(CacheControl cacheControl) {
			response.headers.remove("Cache-Control");
			if(cacheControl != null) {
				response.headers.add("Cache-Control", cacheControl.toString());
			}
			return this;
		}

		@Override
		public ResponseBuilder encoding(String encoding) {
			response.headers.add("Content-Encoding", encoding);
			return this;
		}

		@Override
		public ResponseBuilder header(String name, Object value) {
			if(value == null) {
				response.headers.remove(name);
			}
			response.headers.add(name, value);
			return this;
		}

		@Override
		public ResponseBuilder replaceAll(MultivaluedMap<String, Object> headers) {
			for(String key : headers.keySet()) {
				response.headers.remove(key);
				List<Object> values = headers.get(key);
				if(values != null) {
					for(Object value : values) {
						header(key, value);
					}
				}
			}
			return this;
		}

		@Override
		public ResponseBuilder language(String language) {
			response.language = Locale.forLanguageTag(language);
			response.headers.remove("Language");
			header("Language", response.language.toString());
			return this;
		}

		@Override
		public ResponseBuilder language(Locale language) {
			response.language = language;
			response.headers.remove("Language");
			header("Language", response.language.toString());
			return this;
		}

		@Override
		public ResponseBuilder type(MediaType type) {
			response.mediaType = type;
			return this;
		}

		@Override
		public ResponseBuilder type(String type) {
			response.mediaType = MediaType.valueOf(type);
			return this;
		}

		/* (non-Javadoc)
		 * @see javax.ws.rs.core.Response.ResponseBuilder#variant(javax.ws.rs.core.Variant)
		 */
		@Override
		public ResponseBuilder variant(Variant variant) {
			// TODO Auto-generated method stub
			return null;
		}

		/* (non-Javadoc)
		 * @see javax.ws.rs.core.Response.ResponseBuilder#contentLocation(java.net.URI)
		 */
		@Override
		public ResponseBuilder contentLocation(URI location) {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public ResponseBuilder cookie(NewCookie... cookies) {
			if(cookies != null) {
				for(NewCookie cookie : cookies) {
					response.cookies.put(cookie.getName(), cookie);
				}
			}
			return this;
		}

		@Override
		public ResponseBuilder expires(Date expires) {
			response.headers.remove("Expires");
			header("Expires", expires);
			return this;
		}

		@Override
		public ResponseBuilder lastModified(Date lastModified) {
			response.lastModifiedDate = lastModified;
			response.headers.remove("Last-Modified");
			header("Last-Modified", lastModified);
			return this;
		}

		@Override
		public ResponseBuilder location(URI location) {
			response.location = location;
			response.headers.remove("Location");
			header("Location", location.toString());
			return this;
		}

		@Override
		public ResponseBuilder tag(EntityTag tag) {
			response.entityTag = tag;
			//XXX: Populate Etag header
			return this;
		}

		@Override
		public ResponseBuilder tag(String tag) {
			response.entityTag = EntityTag.valueOf(tag);
			//XXX: Populate Etag header
			return this;
		}

		/* (non-Javadoc)
		 * @see javax.ws.rs.core.Response.ResponseBuilder#variants(javax.ws.rs.core.Variant[])
		 */
		@Override
		public ResponseBuilder variants(Variant... variants) {
			// TODO Auto-generated method stub
			return null;
		}

		/* (non-Javadoc)
		 * @see javax.ws.rs.core.Response.ResponseBuilder#variants(java.util.List)
		 */
		@Override
		public ResponseBuilder variants(List<Variant> variants) {
			// TODO Auto-generated method stub
			return null;
		}

		/* (non-Javadoc)
		 * @see javax.ws.rs.core.Response.ResponseBuilder#links(javax.ws.rs.core.Link[])
		 */
		@Override
		public ResponseBuilder links(Link... links) {
			// TODO Auto-generated method stub
			return null;
		}

		/* (non-Javadoc)
		 * @see javax.ws.rs.core.Response.ResponseBuilder#link(java.net.URI, java.lang.String)
		 */
		@Override
		public ResponseBuilder link(URI uri, String rel) {
			// TODO Auto-generated method stub
			return null;
		}

		/* (non-Javadoc)
		 * @see javax.ws.rs.core.Response.ResponseBuilder#link(java.lang.String, java.lang.String)
		 */
		@Override
		public ResponseBuilder link(String uri, String rel) {
			// TODO Auto-generated method stub
			return null;
		}
	}
}
