/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * JBoss, Home of Professional Open Source
 * Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the @authors tag. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */
package org.hibernate.search.indexes.serialization.avro.impl;

import java.util.HashMap;
import java.util.Map;

import org.apache.avro.Protocol;

import org.hibernate.search.indexes.serialization.spi.Deserializer;
import org.hibernate.search.indexes.serialization.spi.SerializationProvider;
import org.hibernate.search.indexes.serialization.spi.Serializer;
import org.hibernate.search.util.impl.FileHelper;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;

/**
 * parsing code inspired by http://www.infoq.com/articles/ApacheAvro
 * from Boris Lublinsky
 *
 * @author Emmanuel Bernard <emmanuel@hibernate.org>
 */
public class AvroSerializationProvider implements SerializationProvider {

	private static final Log log = LoggerFactory.make();
	private static String V1_PATH = "org/hibernate/search/remote/codex/avro/v1_0/";
	private static final String AVRO_SCHEMA_FILE_SUFFIX = ".avro";
	private static final String AVRO_PROTOCOL_FILE_SUFFIX = ".avpr";

	private Map<String, String> schemas = new HashMap<String, String>();
	private Protocol protocol;

	public static byte MAJOR_VERSION = (byte) ( -128 + 1 );
	public static byte MINOR_VERSION = (byte) ( -128 + 0 );

	public static int getMajorVersion() {
		return MAJOR_VERSION + 128; //rebase to 0
	}

	public static int getMinorVersion() {
		return MINOR_VERSION + 128; //rebase to 0
	}

	@Override
	public Serializer getSerializer() {
		return new AvroSerializer( protocol );
	}

	@Override
	public Deserializer getDeserializer() {
		return new AvroDeserializer( protocol );
	}

	public AvroSerializationProvider() {
		log.serializationProtocol( getMajorVersion(), getMinorVersion() );
		parseSchema( "attribute/TokenTrackingAttribute" );
		parseSchema( "attribute/CharTermAttribute" );
		parseSchema( "attribute/PayloadAttribute" );
		parseSchema( "attribute/KeywordAttribute" );
		parseSchema( "attribute/PositionIncrementAttribute" );
		parseSchema( "attribute/FlagsAttribute" );
		parseSchema( "attribute/TypeAttribute" );
		parseSchema( "attribute/OffsetAttribute" );
		parseSchema( "field/TermVector" );
		parseSchema( "field/Index" );
		parseSchema( "field/Store" );
		parseSchema( "field/TokenStreamField" );
		parseSchema( "field/ReaderField" );
		parseSchema( "field/StringField" );
		parseSchema( "field/BinaryField" );
		parseSchema( "field/NumericIntField" );
		parseSchema( "field/NumericLongField" );
		parseSchema( "field/NumericFloatField" );
		parseSchema( "field/NumericDoubleField" );
		parseSchema( "field/CustomFieldable" );
		parseSchema( "Document" );
		parseSchema( "operation/Id" );
		parseSchema( "operation/OptimizeAll" );
		parseSchema( "operation/PurgeAll" );
		parseSchema( "operation/Delete" );
		parseSchema( "operation/Add" );
		parseSchema( "operation/Update" );
		parseSchema( "Message" );

		this.protocol = parseProtocol( "Works" );
	}

	private void parseSchema(String filename) {
		String fullFileName = V1_PATH + filename + AVRO_SCHEMA_FILE_SUFFIX;
		String messageSchemaAsString = FileHelper.readResourceAsString( fullFileName, AvroSerializationProvider.class.getClassLoader() );
		schemas.put( filename, messageSchemaAsString );
	}

	public Protocol parseProtocol(String name) {
		String filename = V1_PATH + name + AVRO_PROTOCOL_FILE_SUFFIX;
		String protocolSkeleton = FileHelper.readResourceAsString( filename, AvroSerializationProvider.class.getClassLoader() );
		String protocolString = inlineSchemas( protocolSkeleton );
		return Protocol.parse( protocolString );
	}

	public String inlineSchemas(String protocolSkeleton) {
		String result = protocolSkeleton;
		for ( Map.Entry<String, String> entry : schemas.entrySet() ) {
			result = replace(
					result, "`" + entry.getKey() + "`",
					entry.getValue()
			);
		}
		return result;
	}

	static String replace(String str, String pattern, String replace) {
		int s = 0;
		int e;
		StringBuilder result = new StringBuilder();
		while ( ( e = str.indexOf( pattern, s ) ) >= 0 ) {
			result.append( str.substring( s, e ) );
			result.append( replace );
			s = e + pattern.length();

		}
		result.append( str.substring( s ) );
		return result.toString();
	}

	@Override
	public String toString() {
		return "Avro SerializationProvider v" + getMajorVersion() + "." + getMinorVersion();
	}
}
