/*
 * 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.provider;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.util.Locale;

import javax.activation.DataSource;
import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;

/**
 * @author indroneel
 *
 */

class StdMessageBodyReaders {

	public MessageBodyReader<?>[] list() {
		return new MessageBodyReader[] {
			new ByteArrayBodyReader(),
			new StringBodyReader(),
			new InputStreamBodyReader(),
			new ReaderBodyReader(),
			new DataSourceBodyReader(),
			new GenericObjectBodyReader()
		};
	}

	/* TRACK [JSR339:4.2.4] Default reader for wildcard type into byte[]
	 */

	@Provider
	@Consumes("*/*")
	private class ByteArrayBodyReader implements MessageBodyReader<byte[]> {

		@Override
		public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations,
				MediaType mediaType) {
			return type.equals(byte[].class);
		}

		@Override
		public byte[] readFrom(Class<byte[]> type, Type genericType, Annotation[] annotations,
				MediaType mediaType, MultivaluedMap<String, String> httpHeaders,
				InputStream entityStream) throws IOException, WebApplicationException {
			return readFully(entityStream);
		}
	}

	/* TRACK [JSR339:4.2.4] Default reader for wildcard type into String
	 */

	@Provider
	@Consumes("*/*")
	private class StringBodyReader implements MessageBodyReader<String> {

		@Override
		public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations,
				MediaType mediaType) {
			return type.equals(String.class);
		}

		@Override
		public String readFrom(Class<String> type, Type genericType, Annotation[] annotations,
				MediaType mediaType, MultivaluedMap<String, String> httpHeaders,
				InputStream entityStream) throws IOException, WebApplicationException {
			return new String(readFully(entityStream), Charset.defaultCharset());
		}
	}

	/* TRACK [JSR339:4.2.4] Default reader for wildcard type into java.io.InputStream
	 */

	@Provider
	@Consumes("*/*")
	private class InputStreamBodyReader implements MessageBodyReader<InputStream> {

		@Override
		public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations,
				MediaType mediaType) {
			return type.equals(InputStream.class);
		}

		@Override
		public InputStream readFrom(Class<InputStream> type, Type genericType, Annotation[] annotations,
				MediaType mediaType, MultivaluedMap<String, String> httpHeaders,
				InputStream entityStream) throws IOException, WebApplicationException {
			return entityStream;
		}
	}

	/* TRACK [JSR339:4.2.4] Default reader for wildcard type into java.io.Reader
	 */

	@Provider
	@Consumes("*/*")
	private class ReaderBodyReader implements MessageBodyReader<Reader> {

		@Override
		public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations,
				MediaType mediaType) {
			return type.equals(Reader.class);
		}

		@Override
		public Reader readFrom(Class<Reader> type, Type genericType, Annotation[] annotations,
				MediaType mediaType, MultivaluedMap<String, String> httpHeaders,
				InputStream entityStream) throws IOException, WebApplicationException {
			return new InputStreamReader(entityStream);
		}
	}

	/* TRACK [JSR339:4.2.4] Default reader for wildcard type into javax.activation.DataSource
	 */

	@Provider
	@Consumes("*/*")
	private class DataSourceBodyReader implements MessageBodyReader<DataSource> {

		@Override
		public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations,
				MediaType mediaType) {
			return type.equals(DataSource.class);
		}

		@Override
		public DataSource readFrom(Class<DataSource> type, Type genericType, Annotation[] annotations,
				MediaType mediaType, MultivaluedMap<String, String> httpHeaders,
				InputStream entityStream) throws IOException, WebApplicationException {
			return new MessageBodyDataSource(mediaType.toString(), readFully(entityStream));
		}
	}

	@Provider
	@Consumes("*/*")
	private class GenericObjectBodyReader implements MessageBodyReader<Object> {

		@Override
		public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations,
				MediaType mediaType) {
			boolean result = false;
			for(Constructor<?> ctor : type.getConstructors()) {
				if(ctor.getParameterCount() != 1) {
					continue;
				}
				if(!String.class.equals(ctor.getParameterTypes()[0])) {
					continue;
				}
				result = true;
			}
			return result;
		}

		@Override
		public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations,
				MediaType mediaType, MultivaluedMap<String, String> httpHeaders,
				InputStream entityStream) throws IOException, WebApplicationException {

			String content = new String(readFully(entityStream));

			for(Constructor<?> ctor : type.getConstructors()) {
				if(ctor.getParameterCount() != 1) {
					continue;
				}
				if(!String.class.equals(ctor.getParameterTypes()[0])) {
					continue;
				}
				try {
					return ctor.newInstance(content);
				}
				catch (Exception exep) {
					return null;
				}
			}
			return null;
		}
	}

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

	private byte[] readFully(InputStream in) throws IOException {
		byte[] buffer = new byte[0];
		byte[] block = new byte[1024];
		int extent = 0;
		while((extent = in.read(block)) >= 0) {
			if(extent == 0) {
				continue;
			}
			byte[] tempBuf = new byte[buffer.length + extent];
			System.arraycopy(buffer, 0, tempBuf, 0, buffer.length);
			System.arraycopy(block, 0, tempBuf, buffer.length, extent);
			buffer = tempBuf;
		}
		return buffer;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////
	// Datasource implementation

	private class MessageBodyDataSource implements DataSource {
		private String      contentType;
		private InputStream input;
		private String      name;

		public MessageBodyDataSource(String type, byte[] content) {
			contentType = type;
			input = new ByteArrayInputStream(content);
			try {
				MessageDigest md = MessageDigest.getInstance("SHA");
				byte[] signature = md.digest(content);
				name = new BigInteger(signature).toString(36).toUpperCase(Locale.getDefault());
			}
			catch(Exception exep) {
				name = "???";
			}
		}

		@Override
		public String getContentType() {
			return contentType;
		}

		@Override
		public InputStream getInputStream() throws IOException {
			return input;
		}

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

		@Override
		public OutputStream getOutputStream() throws IOException {
			throw new IOException("this data source cannot have an outputstream");
		}
	}
}
