/*
 * Copyright (c) 2009 The openGion Project.
 *
 * 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 org.opengion.fukurou.util;

import java.util.List;
import java.util.ArrayList;
import java.util.Locale;
import java.text.Normalizer;

/**
 * HybsContains.java は、指定の AND OR 形式の文字列が含まれるかどうかを判定するｸﾗｽです。
 *
 * AND OR 形式の文字列 とは、「AAA BBB」は、AAA とBBB のAND形式、「CCC OR DDD」は、
 * CCC と DDD のOR形式になります。
 * 優先順位を付ける"(" などは使えません。常に、OR で分解後、ｽﾍﾟｰｽ分解で、AND因子を求めます。
 *
 * 例)
 *   AAA BBB OR CCC ⇒「AAA BBB」OR 「CCC」
 *   AAA OR BBB CCC ⇒「AAA」OR 「BBB CCC」
 *
 * 判定方法は、ﾉｰﾏﾙ、大文字小文字を区別しない、全角半角を正規化するを指定します。
 *
 * @version	8.5
 * @author	Kazuhiko Hasegawa
 * @since	JDK17.0,
 */
public final class HybsContains {
	// List成分が、OR因子、文字列配列が、AND因子
	private final List<String[]> andOrList = new ArrayList<>();
	private final boolean isIgnoreCase ;		// true で大文字小文字を区別しない
	private final boolean isNormalize ;			// true でNormalize変換を使用する

	/**
	 * ｺﾝｽﾄﾗｸﾀｰ
	 *
	 * AND OR 形式の文字列 を指定します。｢OR｣は、大文字固定で前後に半角ｽﾍﾟｰｽを入れます。
	 * AND の連結は、ｽﾍﾟｰｽで区切ります。OR の分割には、String#split を使いますが、ANDの
	 * 分割は、CSVTokenizer を使用するため、"xxx yyy"などで一連の文字列として処理できます。
	 * ｽﾍﾟｰｽで分割するため、ﾀﾞﾌﾞﾙｸｵｰﾄで括っても 前後のｽﾍﾟｰｽは削除されます。
	 *
	 * @og.rev 8.5.0.0 (2023/04/21)
	 *
	 * @param	andOrStr	AND OR 形式の文字列
	 */
	public HybsContains( final String andOrStr ) {
		this( andOrStr,false,false );
	}

	/**
	 * ｺﾝｽﾄﾗｸﾀｰ
	 *
	 * @og.rev 8.5.0.0 (2023/04/21)
	 *
	 * @param	andOrStr		AND OR 形式の文字列
	 * @param	isIgnoreCase	true で大文字小文字を区別しない
	 * @param	isNormalize		true でNormalize変換を使用する
	 */
	public HybsContains( final String andOrStr, final boolean isIgnoreCase, final boolean isNormalize ) {
		this.isIgnoreCase = isIgnoreCase;
		this.isNormalize  = isNormalize;

		String base = andOrStr ;
		if( isIgnoreCase ) { base = base.toUpperCase(Locale.JAPAN); }
		if( isNormalize  ) { base = Normalizer.normalize( base,Normalizer.Form.NFKC ); }

		for( final String andStr : base.split( " OR " ) ) {				// OR で分割
			andOrList.add( StringUtil.csv2ArrayOnly( andStr,' ' ) );	// ｽﾍﾟｰｽで分割(文字列配列)をListに追加
		}
	}

	/**
	 * 指定の文字列に、ｺﾝｽﾄﾗｸﾀで指定したAND OR文字列が含まれるか判定します。
	 * 注意点としては、通常の String#contains() とは、引数が逆です。
	 * つまり、このﾒｿｯﾄﾞの引数がﾍﾞｰｽとなって判定します。
	 * (通常の String#contains() は、引数が判定される部分文字列です)
	 *
	 * @og.rev 8.5.0.0 (2023/04/21)
	 *
	 * @param	value	判定のﾍﾞｰｽとなる文字列
	 * @return	AND OR文字列が含まれる場合は、true
	 */
	public boolean contains( final String value ) {
		String base = value ;
		if( isIgnoreCase ) { base = base.toUpperCase(Locale.JAPAN); }
		if( isNormalize  ) { base = Normalizer.normalize( base,Normalizer.Form.NFKC ); }

		for( final String[] orAry : andOrList ) {			// [A B C] [D]
			boolean andFlag = true;
			for( final String andStr : orAry ) {			// [A B C] の配列を順番に処理
				if( !base.contains( andStr ) ) {
					andFlag = false;
					break;									// and 判定なので、含まないと即終了
				}
			}
			if( andFlag ) { return true; }					// or 判定なので、成立すれば、即完了
		}
		return false;
	}

	/**
	 * 指定の文字列に、ｺﾝｽﾄﾗｸﾀで指定したAND OR文字列が含まれる場合、tag1 とtag2 で囲んだ。
	 * 文字列で置換した結果を返します。文字列が含まれない場合は、null を返します。
	 * このﾒｿｯﾄﾞでは、各種置換後(大文字化や正規化)の文字列を返しますので、
	 * ｵﾘｼﾞﾅﾙの文字列と異なるのでご注意ください。
	 *
	 * @og.rev 8.5.0.0 (2023/04/21)
	 *
	 * @param	value	判定のﾍﾞｰｽとなる文字列
	 * @param	tag1	置換する場合の前文字列
	 * @param	tag2	置換する場合の後文字列
	 * @return	value	置換後の文字列(含まれない場合は、null)
	 */
	public String changeValue( final String value, final String tag1, final String tag2 ) {
		String base = value ;
		if( isIgnoreCase ) { base = base.toUpperCase(Locale.JAPAN); }
		if( isNormalize  ) { base = Normalizer.normalize( base,Normalizer.Form.NFKC ); }

		final List<Integer[]> adrsList = new ArrayList<>();
		boolean orFlag = false;											// ORﾌﾗｸﾞは、初期値 false
		for( final String[] orAry : andOrList ) {
			boolean andFlag = true;										// ANDﾌﾗｸﾞは、初期値 true (一つでもfalseになれば false)
			for( final String andStr : orAry ) {
				boolean cngFlag = false;								// 変更ﾌﾗｸﾞは、初期値 false
				int idx = base.lastIndexOf( andStr , base.length() );	// 後ろからbaseを検索
				while( idx >= 0 ) {
					cngFlag = true;										// 変更ﾌﾗｸﾞは、一つでも置換があれば、true
					adrsList.add( new Integer[] { idx,idx + andStr.length() } );
					idx = base.lastIndexOf( andStr , idx-1 );			// 後ろからbaseを検索
				}
				andFlag = andFlag && cngFlag;							// ANDﾌﾗｸﾞは、すべてが true でないと、true にならない。
			}
			orFlag = orFlag || andFlag ;								// ORﾌﾗｸﾞは、一度でも true になれば、true
		}

		if( orFlag ) {
			final boolean useOriginal = base.length() == value.length() ;	// Normalize 変換時に、文字数が変わらなければ、ｵﾘｼﾞﾅﾙを置き換える。
			final StringBuilder buf = new StringBuilder( useOriginal ? value : base );

			adrsList.sort( (s1, s2) -> s2[0] - s1[0] );					// ｱﾄﾞﾚｽを降順に並べ替える
			for( final Integer[] adrs : adrsList ) {
				buf.insert( adrs[1] , tag2 );							// 後ろから追加している。
				buf.insert( adrs[0] , tag1 );
			}

			return buf.toString();
		}
		return null;
	}
}
