/*
 * @(#)ZDebug.java
 *
 * Copyright 2000 by Intelligent Technology Inc. All rights reserved.
 */
package jp.co.iti.fagot.util;

import java.text.DateFormat;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.FileWriter;
import java.io.IOException;

import java.util.Date;
import java.util.Vector;
import java.util.Enumeration;


/**
 * デバッグサポートライブラリを提供します。
 * すべてstaticメソッドのユーティリティです
 *
 * @author  福島康治
 * @version 1.01 2000/12/01
 */
public class ZDebug {
	/**
	 * トレース出力の有無を設定します。
	 * @param bTrace トレースの有無
	 */
	static public void setTrace(boolean bTrace) {
	mTrace = bTrace;
	}

	/**
	 * トレースファイルを設定します。
	 * @param szFile トレースファイルの設定
	 */
	static public void setTraceFile(String szFile) {
	mFile = szFile;
	}

	/**
	 * スタックを標準エラー出力に出力します
	 *
	 【出力例】
	ZDebug.printStack()
		at jp.co.iti.fagot.ui.ibox.UTextArea.setString(UTextArea.java:43)
		at jp.co.iti.fagot.ui.UInputPanel.loadProperty(UInputPanel.java:233)
	 */
	static public void printStack() {
    try {
		String szTest = null;
		szTest.toString();
	} catch(NullPointerException np) {
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		PrintStream ps = new PrintStream(stream,true);
		np.printStackTrace(ps);
		String szStack = stream.toString();
		// 1つめの")"まで(このソースのスタック)をとりのぞく
		int nIndex = szStack.indexOf(")");
		String szPrint = "ZDebug.printStack()"+szStack.substring(nIndex+1);
		printErr(szPrint);
    }
	}

	/**
	 * スタック文字列を取得します
	 *
	 */
	static public String getStack() {
	String szText = null;
    try {
		String szTest = null;
		szTest.toString();
	} catch(NullPointerException np) {
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		PrintStream ps = new PrintStream(stream,true);
		np.printStackTrace(ps);
		String szStack = stream.toString();
		// 1つめの")"まで(このソースのスタック)をとりのぞく
		int nIndex = szStack.indexOf(")");
		szText = szStack.substring(nIndex+1);
    }
    return szText;
	}

	/**
	 * トレースを標準出力に出力します
	 * @param szStr 出力する文字列
	 */
	static public void trace(Throwable szTh) {
	if ( !mTrace ) return;
	String szCaller = getCaller();
	println(szCaller+szTh.toString());
	StackTraceElement[] ste = szTh.getStackTrace();
	
	if(null == ste) return;
	for(int i = ste.length - 1; 0 <= i; i--) println(ste[i].toString());
	}

	/**
	 * トレースを標準出力に出力します
	 * @param szStr 出力する文字列
	 */
	static public void trace(String szStr) {
	if ( !mTrace ) {
		return;
	}
	String szCaller = getCaller();
	println(szCaller+szStr);
	}

	/**
	 * マーカーを標準出力に出力します
	 */
	static public void trace() {
	if ( !mTrace ) {
		return;
	}
	String szCaller = getCaller();
	println(szCaller+"### trace ###");
	}


	/**
	 * エラーを標準エラー出力に出力します
	 * @param szStr 出力する文字列
	 */
	static public void err(String szStr) {
	String szCaller = getCaller();
	printErr(szCaller+szStr);
	}

	/*
	 * バイト列のダンプ
	 *
	 * バイト列を次の形式で標準出力に出力します
	 *
	 【出力例】
    *** Dump *** : 000000 : 44 3a 5c 77 6f 72 6b 5c  72 69 64 2d 78 5c 45 64  | D:\work\ rid-x\Ed
    *** Dump *** : 000010 : 69 74 6f 72 5c 73 72 63  5c 64 65 6d 6f 5c 55 6e  | itor\src \demo\Un
    *** Dump *** : 000020 : 69 63 6f 72 6f 2e 6a 61  76 61                    | icoro.ja va      
	 *
	 * @param src バイト列
	 */
	static public void dump( byte[] src ) {
	if ( !mTrace ) {
		return;
	}
	int nLen = src.length ;
	if ( nLen > 64 ) nLen = 64 ;

	int nDmp = 0 ;
	int nAt  ;
	int nCol ;
	while ( nDmp < nLen )
	{
		String szLine = "    *** Dump *** : " ;
		szLine += getHex( 6, nDmp ) ;
		szLine += " : ";
		nCol = (nLen-nDmp>16)?16:nLen-nDmp ;
		for ( nAt=0 ; nAt<16 ; nAt++ ) {
			if ( nAt < nCol ) { szLine += getHex( 2,(int)src[nDmp+nAt] ) ;
								szLine += " " ; }
			else			  {	szLine += "   " ; }
			if ( nAt == 7 )	  {	szLine += " "  ; }
		}
		szLine += " | " ;
		for ( nAt = 0 ; nAt<16 ; nAt++ ) {
			if ( nAt < nCol ) { szLine += (char)(src[nDmp+nAt]) ; }
			else			  { szLine += " " ; }
			if ( nAt == 7 )	  {	szLine += " " ; }
		}
		println( szLine ) ;
		nDmp += nCol ;
	}
	}

	/**
	 * 文字列の整形：16進ダンプ
	 * ★★★いつか使ったときにサンプルをもってこよう★★★
	 */
	static public String getHex( int nSize, int val ) {
	String szRet = "" ;
	int nKeta = 0 ;
	for (  ; nKeta < nSize && val != 0 ; nKeta++ ) {
		int nx = val % 16 ;
		val    = val / 16 ;
		szRet = new Character(Character.forDigit(nx,16)).toString() + szRet;
	}
	for ( ; nKeta < nSize ; nKeta++ ) {
		szRet = "0" + szRet ;
	}
	return ( szRet ) ;
	}

	/**
	 * 性能測定の開始
	 * @param szText
	 */
	static public void lapStart(String szText) {
	mLaps = new Vector();
	Date date = new Date();
	mLaps.add(date);
	String szCaller = getCaller();
	mLaps.add(szCaller+szText);
	}

	/**
	 * ラップポイント
	 * @param szText
	 */
	static public void lap(String szText) {
	if ( mLaps == null ) {
		return;
	}
	Date date = new Date();
	mLaps.add(date);
	String szCaller = getCaller();
	mLaps.add(szCaller+szText);
	}

	/**
	 * 性能測定の終了
	 * @param szText コメント文字列
	 * @param isCSV  csv形式で出力
	 */
	static public void lapEnd(String szText, boolean isCSV) {
	if ( mLaps == null ) {
		return;
	}
	Date date = new Date();
	mLaps.add(date);
	String szCaller = getCaller();
	mLaps.add(szCaller+szText);

	// 合計時間の計算
	Date start = (Date)mLaps.firstElement();
	long total = date.getTime() - start.getTime();
	
	// ラップタイムの表示
	DateFormat fmt = DateFormat.getTimeInstance();
	Enumeration elm = mLaps.elements();
	Date   dt = (Date)elm.nextElement();
	String st = (String)elm.nextElement();
	if ( isCSV ) {
		// lap timeをcsv形式で出力
		String szOut = "*** lap ***,";
		long prev = dt.getTime();
		while ( elm.hasMoreElements() ) {
			dt = (Date)elm.nextElement();
			st = (String)elm.nextElement();
			long cur = dt.getTime();
			long interval = cur-prev;
//			szOut += st + "," + interval + ",";
			szOut += interval + ",";
			prev = cur;
		}
		println(szOut);

	} else {
		// lap timeとコメントを出力
		println("*** lap ***");
		println(fmt.format(dt)+":total["+total+"]"+st);
		long prev = dt.getTime();
		while ( elm.hasMoreElements() ) {
			dt = (Date)elm.nextElement();
			st = (String)elm.nextElement();
			long cur = dt.getTime();
			long interval = cur-prev;
			long ms  = cur % 1000;
			float per = interval * 100 / total;
			println(fmt.format(dt)+":"+ms+"#"+
							   interval+"["+
							   per+"%]"+st);
			prev = cur;
		}
	}
	mLaps = null;
	}

	//## 内部関数 #####################################################
	/**
	 * 呼び出し元の取得
	 */
	static protected String getCaller() {
	String szPrint = null;
    try {
		String szTest = null;
		szTest.toString();
	} catch(NullPointerException np) {
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		PrintStream ps = new PrintStream(stream,true);
		np.printStackTrace(ps);
		String szStack = stream.toString();
		// 2つめの"("まで(呼び出し元のファイル名)をとりのぞく
		int nIndex = szStack.indexOf("(");
		String szTmp = szStack.substring(nIndex+1);
		nIndex = szTmp.indexOf("(");
		szTmp = szTmp.substring(nIndex+1);
		// 3つめは呼び出し元のメソッド名が残っているので、メソッド名を抽出
		nIndex = szTmp.indexOf("(");
		String szPackage = szTmp.substring(0,nIndex);
		String szClass[] = ZString.tokenize(szPackage,".");
		String szFunction= szClass[szClass.length-1];
		// 3つめの"("までをとりのぞく
		szTmp = szTmp.substring(nIndex);
		// 次の")"まで(呼び出し元のファイル名+行番号)を抽出する
		nIndex = szTmp.indexOf(")");
		szPrint = szTmp.substring(0,nIndex+1) + szFunction + ":";
    }
    return szPrint;
	}

	/**
	 * トレース出力
	 * @param szStr 出力する文字列
	 */
	static protected void println(String szStr) {
	if ( mFile == null ) {
		System.out.println(szStr);
	} else {
		try {
			FileWriter fw = new FileWriter( mFile, true );
			fw.write( szStr + "\n" );
			fw.close();
		} catch(IOException io) {
			// NOP
		}
	}
	}

	/**
	 * エラー出力
	 * @param szStr 出力する文字列
	 */
	static protected void printErr(String szStr) {
	if ( mFile == null ) {
		System.err.println(szStr);
	} else {
		try {
			FileWriter fw = new FileWriter( mFile, true );
			fw.write( "*ERR*:" + szStr + "\n");
			fw.close();
		} catch(IOException io) {
			// NOP
		}
	}
	}

	//## アトリビュート ###############################################
	/**
	 * トレースの有無
	 */
	static protected boolean mTrace = true;

	/**
	 * トレースファイル
	 */
	static protected String mFile = null;

	/**
	 * 性能測定用ラップコンテナ
	 */
	static protected Vector mLaps = null;
}
