/*
 * 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.Iterator;
import java.util.Map;
import java.util.HashMap;

import org.opengion.fukurou.system.HybsConst;

/**
 * JSONScan.java は、JSONで行うためのUtilクラスです。
 *
 * @og.rev 8.0.2.0 (2021/11/30) 新規作成
 * @og.group ユーティリティ
 *
 * @version  8.0
 * @author   LEE.
 * @since    JDK17.0,
 */
public final class JSONScan implements Iterator<String> {
	private static final String CR			= HybsConst.CR;						// システムの改行コード
	private static final int BUFFER_MIDDLE	= HybsConst.BUFFER_MIDDLE;			// StringBilderなどの初期値

	private final String		orgStr;											// 処理対象の文字列
	private final char			stCh;											// 開始文字
	private final char			edCh;											// 終了文字
	private final int			stScan;											// 検索範囲の開始位置
	private final int			edScan;											// 検索範囲の終了位置
	private int					stAdrs;											// 処理途中の切り出し開始位置
	private int					edAdrs;											// 処理途中の切り出し終了位置

	/**
	 * コンストラクタ
	 * 処理対象の文字列を解析する JSONScan のインスタンスを作成します。
	 * 検索範囲の開始位置は、ゼロ(0) で初期化されます。
	 * 検索範囲の終了位置は、文字列の長さ で初期化されます。
	 *
	 * @param	orgStr	処理対象の文字列
	 * @param	stCh	開始文字
	 * @param	edCh	終了文字
	 */
	public JSONScan( final String orgStr, final char stCh, final char edCh ) {
		this( orgStr, stCh, edCh, 0, orgStr.length() );
	}

	/**
	 * コンストラクタ
	 * 処理対象の文字列を解析する JSONScan のインスタンスを作成します。
	 *
	 * @param	orgStr	処理対象の文字列
	 * @param	stCh	開始文字
	 * @param	edCh	終了文字
	 * @param	stScan	検索範囲の開始位置
	 * @param	edScan	検索範囲の終了位置
	 */
	public JSONScan( final String orgStr, final char stCh, final char edCh , final int stScan , final int edScan ) {
		this.orgStr				= orgStr;										// 処理対象の文字列
		this.stCh				= stCh;											// 開始文字
		this.edCh				= edCh;											// 終了文字
		this.stScan				= stScan;										// 検索範囲の開始位置
		this.edScan				= edScan;										// 検索範囲の終了位置
		stAdrs					= 0;											// 処理途中の切り出し開始位置
		edAdrs					= stScan;										// 処理途中の切り出し終了位置(検索範囲の最初のアドレス)
	}

	/**
	 * 検索範囲から開始文字がまだあるかどうかを判定します。
	 * このメソッドが true を返す場合、それ以降の引数のない next() への
	 * 呼び出しは適切に文字列を返します。
	 *
	 * @return	文字列内の現在の位置の後ろに 1 つ以上の開始文字がある場合だけ true、そうでない場合は false
	 */
	@Override
	public boolean hasNext() {
		stAdrs = orgStr.indexOf( stCh, edAdrs );
		// 見つからないか、検索範囲を超えた場合
		if( stAdrs < 0 || stAdrs > edScan ) {
			return false;
		} else {
			edAdrs = orgStr.indexOf( edCh , stAdrs+1 );
			// 見つからないか、検索範囲を超えた場合
			if( edAdrs < 0 || edAdrs > edScan ) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 検索範囲から次の文字列を返します。
	 *
	 * @return	処理途中の切り出し文字列
	*/
	@Override
	public String next() {
		return orgStr.substring( stAdrs+1 , edAdrs );
	}

	/**
	 * 検索範囲から開始文字の繰り返し数を数えます。
	 *
	 * @return	開始文字の出現回数
	 */
	public int countBlock() {
		final String subStr = orgStr.substring( stScan , edScan );
		final long cnt = subStr.chars().filter(ch -> ch == stCh).count();
		return Math.toIntExact(cnt);
	}

	/**
	 * JSON形式 から Mapオブジェクト に変更します。
	 *
	 * 「カンマ(,)」と「コロン(:)」キーワードで分割し、キーとラベルのペア情報を
	 * マッピングします。
	 *
	 * @param	csvData	JSON形式の文字列 (例：{"key1":"value1","key2":"value2"})
	 * @return	Mapオブジェクト
	 */
	public static Map<String,String> json2Map( final String csvData ) {
		final Map<String,String> map = new HashMap<>();
		// 「カンマ(,)」で分割
		final String[] ary = StringUtil.csv2Array( csvData );
		for( final String str : ary ) {
			// 「コロン(:)」で分割
			final String[] kv = str.split(":", 2);
			if( kv.length == 2 ) {
				map.put( json2Trim(kv[0]), json2Trim(kv[1]) );
			}
		}
		return map;
	}

	/**
	 * Mapオブジェクト から JSON形式 に変更します。
	 *
	 * @param	prmMap	Mapオブジェクト
	 * @return	JSON形式の文字列 (例：{"key1":"value1","key2":"value2"})
	 */
	public static String map2Json( final Map<String,String> prmMap ) {
		final StringBuilder buf = new StringBuilder(BUFFER_MIDDLE);
		if( prmMap != null ) {
			int i = 0;
			buf.append( '{' )
				.append( CR );

			for( final Map.Entry<String, String> entry : prmMap.entrySet() ) {
				if( i == 0 ) { buf.append( '"' ); }
				else { buf.append( ",\"" ); }
				i++;

				buf.append( entry.getKey() )
					.append( "\":\"" )
					.append( entry.getValue() )
					.append( '"' )
					.append( CR );
			}
			buf.append( '}' );
		}
		return buf.toString();
	}

	/**
	 * 「カンマ(,)」区切り文字で連結された String を、配列に分解して、その値を返します。
	 * ヌル値(null)は 空白 に変換した値を返します。
	 *
	 * @param	csvData	元のデータ
	 * @return	文字列配列
	 */
	public static String[] json2Array( final String csvData ) {
		final String[] ary = StringUtil.csv2Array( csvData );
		for( int i=0; i<ary.length; i++ ) {
			ary[i] = "null".equals(ary[i]) ? "" : json2Trim( ary[i] ) ;
		}
		return ary;
	}

	/**
	 * 前後の二重引用符(")、中括弧({})、スペースを削除します。
	 *
	 * @param	str	文字列
	 * @return	前後の二重引用符(")、中括弧({})、スペースを削除した文字列
	 */
	private static String json2Trim( final String str ) {
		int st = 0;
		int ed = str.length();
		while( st < ed ) {
			final char ch = str.charAt(st);
			if( ch == '"' || ch == '{'  || ch == '}' || ch == ' ' ) { st++; }
			else { break; }
		}
		while( st < ed ) {
			final char ch = str.charAt(ed-1);
			if( ch == '"' || ch == '{'  || ch == '}' || ch == ' ' ) { ed--; }
			else { break; }
		}
		return st < ed ? str.substring(st, ed) : "" ;
	}
}
