package com.t11e.lucene;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.lucene.index.AtomicReader;
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.DocsEnum;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.FieldInfos;
import org.apache.lucene.index.Fields;
import org.apache.lucene.index.IndexCommit;
import org.apache.lucene.index.IndexReaderContext;
import org.apache.lucene.index.Terms;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Attribute;
import org.apache.lucene.util.AttributeSource;
import org.apache.lucene.util.Bits;
import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;

public class Inspector {
	public static void main(final String[] args) {
		int retVal;
		try {
			retVal = mainImpl(args);
		} catch (final Throwable t) {
			retVal = 1;
			System.err.println("Uncaught exception: " + t);
			t.printStackTrace();
		}
		if (retVal != 0) {
			System.exit(retVal);
		}
	}

	public static enum Inspect {
		CLASS, COMMIT, CONTEXT, FIELDS, TERMS, DOCS, ATTRIBUTES,
	}

	public static int mainImpl(final String[] args) throws IOException {
		final String indexPath;
		if (args.length < 1) {
			final StringBuilder builder = new StringBuilder();
			builder.append("Usage: ").append(Inspector.class.getSimpleName())
					.append(" indexPath [{");
			int count = 0;
			for (final Inspect value : Inspect.values()) {
				if (count++ > 0) {
					builder.append("|");
				}
				builder.append(value.name().toLowerCase());
			}
			builder.append("}...]");
			System.err.println(builder.toString());
			return 2;
		}
		indexPath = args[0];

		final File indexFile = new File(indexPath);
		final Directory directory = FSDirectory.open(indexFile);
		try {
			if (!DirectoryReader.indexExists(directory)) {
				System.err.println("No index found in "
						+ indexFile.getCanonicalPath());
				return 3;
			}
			final DirectoryReader reader = DirectoryReader.open(directory);
			try {
				final JsonFactory factory = new JsonFactory();
				final JsonGenerator json = factory.createJsonGenerator(
						System.out, JsonEncoding.UTF8)
						.useDefaultPrettyPrinter();
				try {
					final Inspector inspector = new Inspector(json);
					for (int i = 1; i < args.length; i++) {
						final Inspect inspect = Inspect.valueOf(args[i]
								.toUpperCase());
						switch (inspect) {
						case CLASS:
							inspector.setInspectClass(true);
							break;
						case ATTRIBUTES:
							inspector.setInspectAttributes(true);
							break;

						case DOCS:
							inspector.setInspectDocs(true);
							//$FALL-THROUGH$
						case TERMS:
							inspector.setInspectTerms(true);
							//$FALL-THROUGH$
						case FIELDS:
							inspector.setInspectFields(true);
							//$FALL-THROUGH$
						case CONTEXT:
							inspector.setInspectContext(true);
							break;

						case COMMIT:
							inspector.setInspectCommit(true);
							break;
						}
					}
					inspector.directoryReader(reader);
				} finally {
					json.close();
				}
			} finally {
				reader.close();
			}
		} finally {
			directory.close();
		}
		return 0;
	}

	private final JsonGenerator json;
	private boolean inspectClass;
	private boolean inspectCommit;
	private boolean inspectContext;
	private boolean inspectFields;
	private boolean inspectTerms;
	private boolean inspectDocs;
	private boolean inspectAttributes;

	public Inspector(final JsonGenerator json) {
		this.json = json;
	}

	private Inspector setInspectClass(final boolean inspectClass) {
		this.inspectClass = inspectClass;
		return this;
	}

	private Inspector setInspectCommit(final boolean inspectCommit) {
		this.inspectCommit = inspectCommit;
		return this;
	}

	private Inspector setInspectContext(final boolean inspectContext) {
		this.inspectContext = inspectContext;
		return this;
	}

	private Inspector setInspectFields(final boolean inspectFields) {
		this.inspectFields = inspectFields;
		return this;
	}

	private Inspector setInspectTerms(final boolean inspectTerms) {
		this.inspectTerms = inspectTerms;
		return this;
	}

	private Inspector setInspectAttributes(final boolean inspectAttributes) {
		this.inspectAttributes = inspectAttributes;
		return this;
	}

	private Inspector setInspectDocs(final boolean inspectDocs) {
		this.inspectDocs = inspectDocs;
		return this;
	}

	public void directoryReader(final DirectoryReader reader)
			throws IOException {
		json.writeStartObject();
		if (inspectClass) {
			json.writeStringField("class", reader.getClass().getSimpleName());
		}
		json.writeNumberField("version", reader.getVersion());
		json.writeBooleanField("hasDeletions", reader.hasDeletions());
		json.writeBooleanField("isCurrent", reader.isCurrent());
		json.writeNumberField("numDocs", reader.numDocs());
		json.writeNumberField("numDeletedDocs", reader.numDeletedDocs());
		json.writeNumberField("maxDoc", reader.maxDoc());
		if (inspectCommit) {
			json.writeFieldName("indexCommit");
			indexCommit(reader.getIndexCommit());
		}
		if (inspectContext) {
			json.writeFieldName("context");
			indexReaderContext(reader.getContext());
		}
		json.writeEndObject();
	}

	public void indexCommit(final IndexCommit indexCommit) throws IOException {
		json.writeStartObject();
		if (inspectClass) {
			json.writeStringField("class", indexCommit.getClass()
					.getSimpleName());
		}
		json.writeStringField("directory", indexCommit.getDirectory()
				.toString());
		json.writeNumberField("generation", indexCommit.getGeneration());
		json.writeNumberField("segmentCount", indexCommit.getSegmentCount());
		json.writeStringField("segmentsFileName",
				indexCommit.getSegmentsFileName());
		json.writeArrayFieldStart("fileNames");
		for (final String fileName : indexCommit.getFileNames()) {
			json.writeString(fileName);
		}
		json.writeEndArray();
		json.writeFieldName("userData");
		userData(indexCommit.getUserData());
		json.writeEndObject();
	}

	public void userData(final Map<String, String> userData) throws IOException {
		json.writeStartObject();
		for (final Entry<String, String> entry : userData.entrySet()) {
			json.writeStringField(entry.getKey(), entry.getValue());
		}
		json.writeEndObject();
	}

	public void indexReaderContext(final IndexReaderContext context)
			throws IOException {
		json.writeStartObject();
		if (inspectClass) {
			json.writeStringField("class", context.getClass().getSimpleName());
		}
		json.writeBooleanField("isTopLevel", context.isTopLevel);
		if (context.isTopLevel) {
			json.writeNumberField("numLeaves", context.leaves().size());
		}
		if (context instanceof AtomicReaderContext) {
			final AtomicReaderContext atomicContext = (AtomicReaderContext) context;
			json.writeNumberField("ord", atomicContext.ord);
			json.writeNumberField("docBase", atomicContext.docBase);
		}
		json.writeNumberField("ordInParent", context.ordInParent);
		json.writeNumberField("docBaseInParent", context.docBaseInParent);
		final List<IndexReaderContext> children = context.children();
		if (children != null) {
			json.writeFieldName("children");
			json.writeStartArray();
			for (final IndexReaderContext child : children) {
				indexReaderContext(child);
			}
			json.writeEndArray();
		}
		if (context instanceof AtomicReaderContext) {
			final AtomicReaderContext atomicContext = (AtomicReaderContext) context;
			json.writeFieldName("reader");
			atomicReader(atomicContext.reader());
		}
		json.writeEndObject();
	}

	public void atomicReader(final AtomicReader reader) throws IOException {
		json.writeStartObject();
		if (inspectClass) {
			json.writeStringField("class", reader.getClass().getSimpleName());
		}
		json.writeBooleanField("hasDeletions", reader.hasDeletions());
		json.writeNumberField("numDocs", reader.numDocs());
		json.writeNumberField("numDeletedDocs", reader.numDeletedDocs());
		json.writeNumberField("maxDoc", reader.maxDoc());
		json.writeFieldName("liveDocs");
		bits(reader.getLiveDocs());
		if (inspectFields) {
			final Fields fields = reader.fields();
			if (fields != null) {
				final FieldInfos fieldInfos = reader.getFieldInfos();
				json.writeFieldName("fields");
				json.writeStartArray();
				for (final String field : fields) {
					json.writeStartObject();
					json.writeFieldName("info");
					fieldInfo(fieldInfos.fieldInfo(field));
					if (inspectTerms) {
						final Terms terms = reader.terms(field);
						if (terms != null) {
							json.writeFieldName("terms");
							terms(terms);
						}
					}
					json.writeEndObject();
				}
				json.writeEndArray();
			}
		}
		json.writeEndObject();
	}

	public void terms(final Terms terms) throws IOException {
		json.writeStartObject();
		if (inspectClass) {
			json.writeStringField("class", terms.getClass().getSimpleName());
		}
		json.writeNumberField("size", terms.size());
		json.writeNumberField("docCount", terms.getDocCount());
		json.writeNumberField("sumDocFreq", terms.getSumDocFreq());
		json.writeNumberField("sumTotalTermFreq", terms.getSumTotalTermFreq());
		json.writeBooleanField("hasOffsets", terms.hasOffsets());
		json.writeBooleanField("hasPayloads", terms.hasPayloads());
		json.writeBooleanField("hasPositions", terms.hasPositions());
		json.writeFieldName("termsEnum");
		json.writeStartArray();
		{
			final TermsEnum termsEnum = terms.iterator(null);
			while (termsEnum.next() != null) {
				termsEnum(termsEnum);
			}
		}
		json.writeEndArray();
		json.writeEndObject();
	}

	public void fieldInfo(final FieldInfo fieldInfo) throws IOException {
		json.writeStartObject();
		if (inspectClass) {
			json.writeStringField("class", fieldInfo.getClass().getSimpleName());
		}
		json.writeStringField("name", fieldInfo.name);
		json.writeNumberField("number", fieldInfo.number);

		json.writeBooleanField("isIndexed", fieldInfo.isIndexed());
		if (fieldInfo.isIndexed()) {
			json.writeStringField("indexOptions", fieldInfo.getIndexOptions()
					.name());
		}

		json.writeBooleanField("hasVectors", fieldInfo.hasVectors());
		json.writeBooleanField("hasPayloads", fieldInfo.hasPayloads());

		json.writeBooleanField("hasDocValues", fieldInfo.hasDocValues());
		if (fieldInfo.hasDocValues()) {
			json.writeStringField("docValuesType", fieldInfo.getDocValuesType()
					.name());
		}

		json.writeBooleanField("hasNorms", fieldInfo.hasNorms());
		if (fieldInfo.hasNorms()) {
			json.writeStringField("normType", fieldInfo.getNormType().name());
		}
		json.writeBooleanField("omitsNorms", fieldInfo.omitsNorms());

		json.writeFieldName("attributes");
		userData(fieldInfo.attributes());
		json.writeEndObject();
	}

	public void bits(final Bits bits) throws IOException {
		if (bits == null) {
			json.writeNull();
		} else {
			json.writeStartArray();
			final int N = bits.length();
			for (int i = 0; i < N; i++) {
				if (bits.get(i)) {
					json.writeNumber(i);
				}
			}
			json.writeEndArray();
		}
	}

	public void termsEnum(final TermsEnum termsEnum) throws IOException {
		json.writeStartObject();
		try {
			json.writeNumberField("ord", termsEnum.ord());
		} catch (final UnsupportedOperationException e) {
			// Swallow, some codecs don't support ord
		}
		if (inspectClass) {
			json.writeStringField("class", termsEnum.getClass().getSimpleName());
		}
		json.writeStringField("text", termsEnum.term().utf8ToString());
		json.writeStringField("bytes", termsEnum.term().toString());
		json.writeNumberField("docFreq", termsEnum.docFreq());
		json.writeNumberField("totalTermFreq", termsEnum.totalTermFreq());
		if (inspectAttributes) {
			json.writeFieldName("attributes");
			attributes(termsEnum.attributes());
		}
		if (inspectDocs) {
			json.writeFieldName("docs");
			json.writeStartArray();
			{
				final DocsEnum docsEnum = termsEnum.docs(null, null);
				while (docsEnum.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) {
					docsEnum(docsEnum);
				}
			}
			json.writeEndArray();
		}
		json.writeEndObject();
	}

	public void docsEnum(final DocsEnum docsEnum) throws IOException {
		json.writeStartObject();
		if (inspectClass) {
			json.writeStringField("class", docsEnum.getClass().getSimpleName());
		}
		json.writeNumberField("doc", docsEnum.docID());
		json.writeNumberField("freq", docsEnum.freq());
		if (inspectAttributes) {
			json.writeFieldName("attributes");
			attributes(docsEnum.attributes());
		}
		json.writeEndObject();
	}

	public void attributes(final AttributeSource attributes) throws IOException {
		json.writeStartObject();
		if (inspectClass) {
			json.writeStringField("class", attributes.getClass()
					.getSimpleName());
		}
		json.writeBooleanField("hasAttributes", attributes.hasAttributes());
		if (attributes.hasAttributes()) {
			json.writeFieldName("attributes");
			json.writeStartArray();
			final Iterator<Class<? extends Attribute>> iter = attributes
					.getAttributeClassesIterator();
			while (iter.hasNext()) {
				final Class<? extends Attribute> klass = iter.next();
				final Attribute attribute = attributes.getAttribute(klass);
				json.writeStartObject();
				json.writeStringField("class", klass.getSimpleName());
				json.writeStringField("class2", attribute.getClass()
						.getSimpleName());
				json.writeStringField("toString", attribute.toString());
				json.writeEndObject();
			}
			json.writeEndArray();
		}
		json.writeEndObject();
	}
}
