/*
 * @(#)ZGraphicUtil.java
 *
 * Copyright 2001 by Intelligent Technology Inc. All rights reserved.
 */

package jp.co.iti.fagot.util;

import java.awt.Color;
import java.awt.Shape;
import java.awt.Font;

import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;

import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;

import java.text.AttributedString;
import java.text.AttributedCharacterIterator;

import jp.co.iti.fagot.ZApp;

import jp.co.iti.fagot.geom.ZPath;
import jp.co.iti.fagot.geom.ZDimension;
import jp.co.iti.fagot.geom.ZPoint;
import jp.co.iti.fagot.geom.ZRectangle;

import jp.co.iti.fagot.gob.PTransform;

import jp.co.iti.fagot.util.ZDebug;

/**
 * グラフィックユーティリティ
 *
 * 図形描画上の基本関数を定義する。
 * すべて静的メソッド
 *
 * @author  福島康治
 * @version 1.01 2001/01/07
 */
public class ZGraphicUtil {
	/**
	 * 線と点が近いか判定します
	 * @param pt1  線の開始点
	 * @param pt2  線の開始点
	 * @param pt   点
	 * @param margin 余裕
	 */
	static public boolean isNearLine(
		ZPoint pt1,ZPoint pt2,ZPoint pt,double margin ) {

	// pt1 と pt2 の間に pt があるか？
	ZRectangle rect =
		new ZRectangle(
			Math.min(pt1.getX(),pt2.getX())-margin  ,
			Math.min(pt1.getY(),pt2.getY())-margin  ,
			Math.abs(pt1.getX()-pt2.getX())+margin*2,
			Math.abs(pt1.getY()-pt2.getY())+margin*2
		);
	if ( !rect.contains(pt) ) {
		 return false;
	}

	double dist = getDistance(pt1,pt2,pt);

	// 判定
	if ( dist < margin ) {
		return true ;
	}
	return false;
	}

	/**
	 * 線と点の距離を計算します
	 * @param pt1  線の開始点
	 * @param pt2  線の開始点
	 * @param pt   点
	 */
	static public double getDistance(ZPoint pt1,ZPoint pt2,ZPoint pt ) {
	// 基準ﾍﾞｸﾄﾙの抽出
	ZPoint ptA = pt.minus(pt1);
	ZPoint ptB = pt2.minus(pt1);

	// 係数の計算	Ax * Bx + Ay * By
	//              ----------------
	//				Bx * Bx + By * By
	double zx = ptA.getX()*ptB.getX() + ptA.getY()*ptB.getY() ;
	double zy = ptB.getX()*ptB.getX() + ptB.getY()*ptB.getY() ;
	double zz = zx / zy ;

	// 直線との交点
	ZPoint ptC = new ZPoint(pt1.getX()+ptB.getX()*zz,
							pt1.getY()+ptB.getY()*zz);

	// 交点と点との距離の２乗
	double dist = ( pt.getX()-ptC.getX() ) * ( pt.getX()-ptC.getX()) +
				  ( pt.getY()-ptC.getY() ) * ( pt.getY()-ptC.getY()) ;

	return ( Math.sqrt(dist) ) ;
	}

	/**
	 * 点と点の距離を計算します
	 * @param pt1  点1
	 * @param pt2  点2
	 */
	static public double getDistance(ZPoint pt1,ZPoint pt2) {
	double x = pt1.getX()-pt2.getX();
	double y = pt1.getY()-pt2.getY();
	return ( Math.sqrt(x*x + y*y) );
	}

	//## 作成 #########################################################
	/**
	 * 矢印のShapeを計算します
	 * @param pt1  線の開始点
	 * @param pt2  線の終了点
	 * @param pt   点
	 */
	static public Shape createArrowShape(ZPoint pt1,ZPoint pt2,double length,double width) {
	// 基準三角形の作成（右から左への矢印）
	ZPath p1 = new ZPath();
	p1.moveTo(      0,      0 );
	p1.lineTo( length,  width );
	p1.lineTo( length, -width );
	p1.closePath();

	// p2からp1への単位ベクトルを求める
	ZPoint ptA = pt1.minus(pt2);
	double distance = getDistance(pt1,pt2);
	ZPoint ptUnit = new ZPoint(ptA.getX()/distance,ptA.getY()/distance);

	// 変換行列の作成
    //		[   cos(theta)    -sin(theta)    pt2.x   ]
    //		[   sin(theta)     cos(theta)    pt2.y   ]
    // ただし、thetaはp1からp2への単位ベクトル
	AffineTransform at = new AffineTransform(
			ptUnit.getX(),		ptUnit.getY(),
		   -ptUnit.getY(),		ptUnit.getX(),
			pt2.getX(),			pt2.getY() ) ;

	Shape s1 = at.createTransformedShape( p1 );
	return s1 ;
	}

	//## 座標変換 #####################################################
	/**
	 * 親矩形に子矩形が収まるような座標変換を計算します
	 * (シャーリング変換はないことを前提)
	 *
	 * 旧形式なので削除予定
	 *
	 * @param pa 親矩形
	 * @param ch 子矩形
	 */
	static public PTransform getNormalizeTransform(ZRectangle pa,ZRectangle ch) {
	// 子矩形の左上が親矩形の左上に
	// 子矩形の右下が親矩形の右下になるような座標変換を計算します。

	// 左上の変換式
	// chTL.x * d00 + chTL.y * d01 + d02 = paTL.x	①
	// chTL.y * d10 + chTL.x * d11 + d12 = paTL.y	②
	// 右上の変換式
	// chBR.x * d00 + chBR.y * d01 + d02 = paBR.x	③
	// chBR.y * d10 + chBR.x * d11 + d12 = paBR.y	④

	// ③-①(ただしd01=0)
	//     chTL.x * d00 - chBR.x * d00 = paTL.x - paBR.x
	// →  d00 = ( paBR.x - paTL.x ) / ( chBR.x - chTL.x )
	// →      =   pa.getWidth()     /   ch.getWidth()
	double d00 = pa.getWidth()/(ch.getWidth());

	// 同様に ④-②(ただしd10=0)
	double d11 = pa.getHeight()/(ch.getHeight());

	// d00,d11の小さい値が正規化された倍率
	double dz = Math.min( d00, d11 );
	// 大きくはしない
	if ( dz > 1 ) {
		dz = 1;
	}

	// 中央の座標変換から、d01,d12を求める
	ZPoint paCenter = pa.getCenter();
	ZPoint chCenter = ch.getCenter();
	double d02 = paCenter.getX()-chCenter.getX()*dz;
	double d12 = paCenter.getY()-chCenter.getY()*dz;

	return new PTransform(dz,0,0,dz,d02,d12) ;
	}

	//## COLOR ########################################################
	/**
	 * 中間色を返します
	 * @param col1 色1
	 * @param col2 色2
	 */
	static public Color harfColor(Color col1, Color col2) {
	return new Color(
		(col1.getRed()  +col2.getRed())/2,
		(col1.getGreen()+col2.getGreen())/2,
		(col1.getBlue() +col2.getBlue())/2);
	}

	/**
	 * 色の値を取得できるか否かを検査します。
	 *
	 * @param szStr 色指定文字列
	 */
	static public boolean isValidColor(String szStr) {
	Color col = stringToColor(szStr);
	return ( col != null );
	}

	/**
	 * キーに対応する色を取得します。
	 * 値が取得できない場合はnullを返します
	 *
	 * @param szStr 色指定文字列
	 */
	static public Color stringToColor(String szStr) {
	Color  cCol  = null;
	if ( szStr == null ) {
		//
	} else if ( szStr.startsWith("#") &&
	     (szStr.length()==7 || szStr.length()==4) ) {
		int nRed   = 0;
		int nGreen = 0;
		int nBlue  = 0;
		int nXXX   = 1;	// 1桁で指定している場合は16倍する
		try {
			szStr = szStr.substring(1);
			int nSize = szStr.length() / 3;
			nRed  = Integer.parseInt(szStr.substring(0,nSize),16);
			szStr = szStr.substring(nSize);
			nGreen= Integer.parseInt(szStr.substring(0,nSize),16);
			szStr = szStr.substring(nSize);
			nBlue = Integer.parseInt(szStr.substring(0,nSize),16);
			if ( nSize == 1 ) {
				nXXX = 16;
			}
//			ZDebug.trace("szStr="+szStr+" R="+nRed+" G="+nGreen+" B="+nBlue);
			cCol = new Color(nRed*nXXX,nGreen*nXXX,nBlue*nXXX);
		} catch ( NumberFormatException nf ) {
			ZDebug.trace("stringToColor:parse error");
		}
	}
	return cCol;
	}

	/**
	 * 黒系ならtureを返します。
	 * @param col 判定する色
	 */
	static public boolean isBlackFamily(String szCol) {
	return isBlackFamily( stringToColor(szCol) );
	}

	/**
	 * 黒系ならtureを返します。
	 * Color が null の場合は、falseを返します
	 * @param col 判定する色
	 */
	static public boolean isBlackFamily(Color col) {
	int nAve = 255*3;
	if ( col != null ) {
		nAve = col.getRed()+col.getGreen()+col.getBlue();
	} 
	return (nAve<(128*3));
	}

	/**
	 * 反対色を返します。
	 * @param col 判定する色
	 */
	static public Color inverseColor(Color col) {
	if ( col != null ) {
		return new Color(255-col.getRed(),255-col.getGreen(),255-col.getBlue());
	}
	return null;
	}

	/**
	 * カラーを文字列に変換します
	 *
	 * Color.decode(),Color.getColor()はあてにならないので修正
	 *
	 * @param szStr 色指定文字列
	 */
	static public String colorToString(Color c) {
	String szColor = null;
	if ( c != null ) {
		szColor = "#";

		String szRed   = Integer.toHexString(c.getRed());
		if ( szRed.length() == 1 ) {
			szColor += "0" + szRed.toUpperCase();
		} else {
			szColor += szRed.toUpperCase();
		}

		String szGreen = Integer.toHexString(c.getGreen());
		if ( szGreen.length() == 1 ) {
			szColor += "0" + szGreen.toUpperCase();
		} else {
			szColor += szGreen.toUpperCase();
		}

		String szBlue = Integer.toHexString(c.getBlue());
		if ( szBlue.length() == 1 ) {
			szColor += "0" + szBlue.toUpperCase();
		} else {
			szColor += szBlue.toUpperCase();
		}
	}
	return szColor;
	}

	/**
	 * カラーをシステムカラーに変換します
	 *
	 * @param szStr 色指定文字列
	 */
	static public String stringToSystemColor(String szColor) {
	return szColor;
	}

	//## 文字列(旧バージョン)使い方をすっかり忘れた... ################
	/**
	 * LineBreakMeasurerの作成
	 * @param szText     表示文字列
	 * @param font       フォント
	 * @param bgColor    背景色
	 * @param fgColor    文字色
	 * @param at         座標変換
	 */
	static public LineBreakMeasurer createLineBreakMeasurer(
		String szText, Font font, Color bgColor, Color fgColor, AffineTransform at) {
	//
	int nLen = szText.length();

	// 属性付きテキスト
	AttributedString as = new AttributedString(szText);

	// 属性付きテキスト：フォントの設定
	if ( font != null ) {
		as.addAttribute(TextAttribute.FONT,font,0,nLen);
	}

	// 属性付きテキスト：背景色の設定
	if ( bgColor!=null ) {
		as.addAttribute(TextAttribute.BACKGROUND, bgColor, 0, nLen);
	}

	// 属性付きテキスト：文字色の設定
	if ( fgColor!=null ) {
		as.addAttribute(TextAttribute.FOREGROUND, fgColor, 0, nLen);
	} else {
		as.addAttribute(TextAttribute.FOREGROUND, Color.black, 0, nLen);
	}
	
	// 属性付きテキスト：イテレータの取得
	AttributedCharacterIterator aci = as.getIterator();

	// ★★★このあたりで大きくメモリリークしているような感じ...
//	FontRenderContext frc = new FontRenderContext(at,true,true);
//	FontRenderContext frc = new FontRenderContext(at,false,true);
	FontRenderContext frc = new FontRenderContext(at,false,false);

	LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc);
	return lbm;
	}

	//## 文字列(旧バージョン)使い方をすっかり忘れた... ################
	/**
	 * LineBreakMeasurerの作成
	 * @param szText     表示文字列
	 * @param font       フォント
	 * @param bgColor    背景色
	 * @param fgColor    文字色
	 * @param at         座標変換
	 */
	static public LineBreakMeasurer createLineBreakMeasurer(
		String szText, String family, Float size, Color bgColor, Color fgColor, FontRenderContext frc) {
	// 属性付きテキスト
	AttributedString as = new AttributedString(szText);

	// 属性付きテキスト：フォントの設定
	if ( family != null )	as.addAttribute(TextAttribute.FAMILY, family);
	if ( size != null )		as.addAttribute(TextAttribute.SIZE, size);
	
	// 属性付きテキスト：背景色の設定
	if ( bgColor!=null ) {
		as.addAttribute(TextAttribute.BACKGROUND, bgColor);
	}

	// 属性付きテキスト：文字色の設定
	if ( fgColor!=null ) {
		as.addAttribute(TextAttribute.FOREGROUND, fgColor);
	} else {
		as.addAttribute(TextAttribute.FOREGROUND, Color.black);
	}
	
	return new LineBreakMeasurer(as.getIterator(), frc);
	}
	
	/**
	 * 適切なサイズの取得
	 * @param szText     LineBreakMeasurer
	 * @param at         座標変換
	 * @param nLen       文字列の長さ
	 * @param dWidth     最大幅（1行の場合は0）
	 */
	static public ZDimension getTextPreferredSize(
		LineBreakMeasurer lbm, AffineTransform at, int nLen, double dWidth) {

	boolean bSingle = false;
	if ( dWidth == 0 ) {
		bSingle = true;
		dWidth = 10000;
	}

	// 位置の保存
	int pos = lbm.getPosition();

	// 描画開始位置を取得するが、とりあえず左上から
	float fx = 0;
	float fy = 0;
	while ( lbm.getPosition() < nLen ) {
		TextLayout tl = lbm.nextLayout((float)dWidth);
		float plus = tl.getAscent() + tl.getDescent() + tl.getLeading();
		if ( at == null ) {
			fy += plus;
		} else {
			ZPoint pttrans = new ZPoint(0,(double)plus);
			Point2D pt2D = at.transform(pttrans,null);
			fy += pt2D.getY() ;
		}
		if ( fx < tl.getVisibleAdvance() ) {
			fx = tl.getVisibleAdvance();
		}

		if ( bSingle ) {
			break;
		}
	}

	lbm.setPosition( pos ) ;

//System.out.println(fx+","+fy);
	return new ZDimension( (double)fx, (double)fy );
	}

}
