/*******************************************************************************
 * Copyright (c) 2009 David Green and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     David Green - initial API and implementation
 *******************************************************************************/

package org.eclipse.mylyn.internal.wikitext.core.util.css;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author David Green
 */
public class SparseCharSequence implements CharSequence {

	private final CharSequence data;

	private final Segment[] segments;

	private final int length;

	public SparseCharSequence(CharSequence data, Pattern excludePattern) {
		this.data = data;
		List<Segment> segments = new ArrayList<Segment>(5);
		Segment segment = new Segment(0, 0);
		Matcher matcher = excludePattern.matcher(data);
		while (matcher.find()) {
			segment.length = matcher.start() - segment.offset;
			segments.add(segment);
			int end = matcher.end();
			if (end == data.length()) {
				segment = null;
				break;
			} else {
				segment = new Segment(end, segment.zeroBase + segment.length);
			}
		}
		if (segment != null) {
			segment.length = data.length() - segment.offset;
			segments.add(segment);
		}
		// remove 0-length segments
		if (segments.size() > 1) {
			Iterator<Segment> it = segments.iterator();
			while (it.hasNext() && segments.size() > 1) {
				segment = it.next();
				if (segment.length == 0) {
					it.remove();
				}
			}
		}
		this.segments = segments.toArray(new Segment[segments.size()]);
		Segment lastSegment = this.segments[this.segments.length - 1];
		length = lastSegment.zeroBase + lastSegment.length;
	}

	public int originalOffsetOf(int index) {
		if (index < 0 || index >= length()) {
			throw new IndexOutOfBoundsException(String.format("%s is not within [0,%s)", index, length())); //$NON-NLS-1$
		}
		Segment segment = segmentOf(index);
		return segment.offset + (index - segment.zeroBase);
	}

	public char charAt(int index) {
		if (index < 0 || index >= length()) {
			throw new IndexOutOfBoundsException(String.format("%s is not within [0,%s)", index, length())); //$NON-NLS-1$
		}
		Segment segment = segmentOf(index);
		return data.charAt(segment.offset + (index - segment.zeroBase));
	}

	private Segment segmentOf(int index) {
		if (index == 0) {
			return segments[0];
		}
		Segment candidate = segments[0];
		for (int x = 1; x < segments.length; ++x) {
			if (segments[x].zeroBase > index) {
				break;
			}
			candidate = segments[x];
		}
		return candidate;
	}

	public int length() {
		return length;
	}

	public CharSequence subSequence(int start, int end) {
		if (start < 0 || start >= length || end > length) {
			throw new IndexOutOfBoundsException(String.format("[%s,%s) is not within range [0,%s)", start, end, length)); //$NON-NLS-1$
		}
		int rangeLength = end - start;
		if (rangeLength == 0) {
			return ""; //$NON-NLS-1$
		}
		int remainingLength = rangeLength;
		String sequence = null;
		for (Segment segment = segmentOf(start); remainingLength > 0; segment = segmentOf(end - remainingLength)) {
			int segmentOffset = start - segment.zeroBase;
			int segmentEnd = Math.min(end - segment.zeroBase, segment.length);
			CharSequence part = data.subSequence(segment.offset + segmentOffset, segment.offset + segmentEnd);
			if (sequence == null) {
				sequence = part.toString();
			} else {
				sequence += part;
			}
			remainingLength -= part.length();
			start += part.length();
		}
		return sequence;
	}

	private static class Segment {
		public Segment(int offset, int zeroBase) {
			this.offset = offset;
			this.zeroBase = zeroBase;
		}

		int offset, length;

		int zeroBase;
	}

	@Override
	public String toString() {
		return subSequence(0, length).toString();
	}
}
