/*
 * Copyright 2013 Yuichiro Moriguchi
 *
 * 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 net.morilib.sed;

import net.morilib.sed.line.SedLastLineMatcher;
import net.morilib.sed.line.SedNumberLineMatcher;
import net.morilib.sed.line.SedPatternLineMatcher;
import net.morilib.sed.line.SedStepLineMatcher;

/**
 * 行のマッチ状況を表します。
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/03
 */
public class SedLineOption {

	//
	private enum St { NO_MATCH, MATCHING, MATCHED }

	//
	SedLineMatcher begin, end;
	SedPatternLineMatcher pattern;
	boolean negate;

	//
	St matchstat = St.NO_MATCH;
	int rellineno = -1;

	//
	SedLineOption() {
		this(null, null, null, false);
	}

	//
	SedLineOption(SedLineMatcher matcher) {
		this(matcher, null, null, false);
	}

	//
	SedLineOption(String pattern, boolean casein, boolean allm) {
		this(null, null,
				new SedPatternLineMatcher(pattern, casein, allm),
				false);
	}

	//
	SedLineOption(String pattern, boolean neg) {
		this(null, null,
				new SedPatternLineMatcher(pattern),
				neg);
	}

	//
	SedLineOption(String begin, String end) {
		this(new SedPatternLineMatcher(begin),
				new SedPatternLineMatcher(end), null, false);
	}

	//
	SedLineOption(String begin, String end, boolean neg) {
		this(new SedPatternLineMatcher(begin),
				new SedPatternLineMatcher(end), null, neg);
	}

	//
	SedLineOption(long begin, String end) {
		this(new SedNumberLineMatcher(begin),
				new SedPatternLineMatcher(end), null, false);
	}

	//
	SedLineOption(String begin, long end) {
		this(new SedPatternLineMatcher(begin),
				new SedNumberLineMatcher(end), null, false);
	}

	//
	SedLineOption(long begin, boolean dummy, boolean dummy2) {
		this(new SedNumberLineMatcher(begin),
				SedLastLineMatcher.INSTANCE, null, false);
	}

	//
	SedLineOption(String begin, boolean dummy, int dummy2) {
		this(new SedPatternLineMatcher(begin),
				SedLastLineMatcher.INSTANCE, null, false);
	}

	//
	SedLineOption(SedLineMatcher begin, SedLineMatcher end) {
		this(begin, end, null, false);
	}

	//
	SedLineOption(long lineno) {
		this(new SedNumberLineMatcher(lineno), null, null, false);
	}

	//
	SedLineOption(long begin, long end) {
		this(new SedNumberLineMatcher(begin),
				new SedNumberLineMatcher(end), null, false);
	}

	//
	SedLineOption(long begin, long end, boolean neg) {
		this(new SedNumberLineMatcher(begin),
				new SedNumberLineMatcher(end), null, neg);
	}

	//
	private SedLineOption(SedLineMatcher b, SedLineMatcher e,
			SedPatternLineMatcher p, boolean n) {
		begin   = b;
		end     = (b == null || b.equals(e)) ? null : e;
		pattern = p;
		negate  = n;
	}

	/**
	 * 行のマッチを得ます。<br />
	 * 複数行マッチのときは最初のマッチを得ます。
	 * マッチが指定されなかったときはnullを得ます。
	 * 
	 * @return 最初の行のマッチ
	 */
	public SedLineMatcher getBegin() {
		return begin;
	}

	/**
	 * 複数行マッチのとき、最後の行のマッチを得ます。
	 * 複数行マッチでないときはnullを得ます。
	 * 
	 * @return 最初の行のマッチ
	 */
	public SedLineMatcher getEnd() {
		return end;
	}

	/**
	 * gコマンドをつけられた正規表現マッチを得ます。<br />
	 * gコマンドでないときはnullを得ます。
	 * 
	 * @return
	 */
	public String getPattern() {
		return ((SedPatternLineMatcher)pattern).getPattern();
	}

	/**
	 * マッチが指定されなかったときは0、
	 * 1行マッチのときは1、2行マッチのときは2を得ます。
	 * 
	 * @return マッチに対応した数
	 */
	public int options() {
		if(begin == null) {
			return 0;
		} else if(end == null) {
			return 1;
		} else {
			return 2;
		}
	}

	/**
	 * 入力行に対してマッチさせます。<br />
	 * このメソッドの呼出により状態が変わることがあります。
	 * 
	 * @param e 正規表現エンジン
	 * @param s 入力行
	 * @param l 行番号
	 * @param lastno 最後の行のときtrue
	 * @return マッチしているときtrue
	 */
	public boolean matches(SedPatternEngine e, String s, long l,
			boolean lastno) {
		if(pattern != null) {
			return negate != pattern.matches(e, s, l, -1, lastno);
		} else if(begin == null) {
			return true;
		} else if(begin instanceof SedStepLineMatcher) {
			return negate != begin.matches(e, s, l, -1, lastno);
		} else if(matchstat.equals(St.NO_MATCH)) {
			if(!begin.matches(e, s, l, -1, lastno)) {
				return negate;
			} else if(end == null) {
				matchstat = St.MATCHED;
				return !negate;
			} else {
				matchstat = St.MATCHING;
				rellineno = 1;
				return !negate;
			}
		} else if(matchstat.equals(St.MATCHING)) {
			if(end.matches(e, s, l, rellineno++, lastno)) {
				matchstat = St.MATCHED;
				return !negate;
			} else {
				return !negate;
			}
		} else {
			return negate;
		}
	}

	/**
	 * マッチが完了したかを調べます。
	 * 
	 * @return マッチが完了したときtrue
	 */
	public boolean hasMatched() {
		return !negate && matchstat.equals(St.MATCHED);
	}

	/**
	 * 今マッチしているかを調べます。
	 * 
	 * @return 今マッチしているときtrue
	 */
	public boolean isMatching() {
		return negate != matchstat.equals(St.MATCHING);
	}

	/**
	 * まだマッチしていないかを調べます。
	 * 
	 * @return まだマッチしていないときtrue
	 */
	public boolean hasNotMatched() {
		return !negate && matchstat.equals(St.NO_MATCH);
	}

	/**
	 * マッチが否定であるときtrueを得ます。
	 * 
	 * @return マッチが否定であるときtrue
	 */
	public boolean isNegate() {
		return negate;
	}

	private static boolean eq(Object a, Object b) {
		return a == null ? b == null : a.equals(b);
	}

	/**
	 * 与えられたオブジェクトがこのマッチと同じものにマッチする
	 * オブジェクトであるかを調べます。
	 * 
	 * @param p マッチが等価のときtrue
	 * @return
	 */
	public boolean isEquivalent(SedLineOption p) {
		return (eq(begin, p.begin) && negate == p.negate &&
				eq(end, p.end) && eq(pattern, p.pattern));
	}

	public String toString() {
		StringBuffer b = new StringBuffer();

		if(pattern != null) {
			b.append("g/").append(pattern).append("/");
		} else if(begin != null) {
			b.append(begin).append(",").append(end);
		}
		if(negate)  b.append('!');
		return b.toString();
	}

}
