package debugger;

import java.awt.Color;
import java.awt.Container;
import java.util.ArrayList;

import javax.swing.*;
import javax.swing.JScrollPane.*;



public class debugVar {

	static final int VAR_HEIGHT = 20;
	static final int ARR_HOSEI = 10;

	//定数
	public static final int TYPE_INT = 0;
	public static final int TYPE_DOUBLE = 1;
	public static final int TYPE_FLOAT = 2;
	public static final int TYPE_STRING = 3;
	public static final int TYPE_CHAR = 4;
	public static final int TYPE_BYTE = 5;
	public static final int TYPE_OBJECT = 6;
	public static final int TYPE_BOOLEAN = 7;
	public static final int TYPE_ARRAY = 0x10;


	//子供と次のノードを所持する
	public debugVar next_var;
	public debugVar child_var;


	//パラメーター
	private String name;
	private int type;
	private Object value;
	private int array;//配列の次元。配列でないなら0



	//表示系コンポーネント
	private ArrayList labels = new ArrayList();
	private ArrayList textfields = new ArrayList();
	private Container comp;
	private JToggleButton arr_open = null;
	private boolean arr_open_flag = false;

	/** コンストラクタ
	 *
	 * @param _name 変数名
	 * @param _type 変数の型
	 * @param _val 変数の値
	 */
	public debugVar(String _name,int _type,Object _val){
		this.name = _name;
		this.type = _type;
		this.value = _val;
		this.array = 0;
	}

	/** コンストラクタ
	 *
	 * @param _name 変数名
	 * @param _type 変数の型
	 * @param _val 変数の値
	 * @param _arr 配列の次元
	 */
	public debugVar(String _name,int _type,Object _val,int _arr){
		this.name = _name;
		this.type = _type;
		this.value = _val;
		this.array = _arr;
	}
	/** 変数名の取得
	 *
	 * @return 変数名を返す
	 */
	public String getName() {
		return name;
	}

	/** 変数の型を取得する
	 *
	 * @return 変数の型を返す
	 */
	public int getType() {
		return type;
	}

	/** 変数の値を取得する
	 *
	 * @return 変数の値を返す。
	 */
	public Object getValue() {
		return value;
	}

	/** 配列の次元を取得する
	 *
	 * @return 配列の次元を返す。配列でない場合は0。
	 */
	public int getArr(){
		return this.array;
	}

	/** 値をセットする
	 *
	 * @param obj セットする値
	 */
	public void setValue(Object obj){
		this.value = obj;
	}


	/** 初期化を行う
	 */
	public void init(Container _comp){
		JLabel labl;
		JTextField tex;

		this.comp = _comp;

		labl = new JLabel(this.name);
		comp.add(labl);
		this.labels.add(labl);

		tex = new JTextField();
		comp.add(tex);
		this.textfields.add(tex);

		if(this.array>=1){
			this.type |= TYPE_ARRAY;
			this.arr_open = new JToggleButton("－");
			this.comp.add(this.arr_open);
		}
	}


	/** 配列や子要素を出す
	 */
	public void initChild(){
		this._init(this.value,this.array,this.name);
		this.setRunningFlag( debugWindow.getInstance().getRunningFlag() );
	}

	/**初期化のサブ関数
	 * @param obj オブジェクト
	 * @param arr 配列の階層
	 * @param arr_name 配列の添え字部分の文字列
	 */
	private void _init(Object obj,int arr,String arr_name){
		if(arr==1){
			int length=0;
			switch( (this.type&(~TYPE_ARRAY) ) ){
			case TYPE_INT:
				length = ((int[])obj).length;
				break;
			case TYPE_STRING:
				length = ((String[])obj).length;
				break;
			case TYPE_BYTE:
				length = ((byte[])obj).length;
				break;
			case TYPE_CHAR:
				length = ((char[])obj).length;
				break;
			case TYPE_OBJECT:
				length = ((Object[])obj).length;
				break;
			case TYPE_DOUBLE:
				length = ((double[])obj).length;
				break;
			case TYPE_FLOAT:
				length = ((float[])obj).length;
				break;
			case TYPE_BOOLEAN:
				length = ((boolean[])obj).length;
				break;
			}
			for(int i=0;i<length;i++){
				JLabel labl;
				JTextField tex;

				labl = new JLabel(name+"["+i+"]");
				comp.add(labl);
				this.labels.add(labl);

				tex = new JTextField();
				comp.add(tex);
				this.textfields.add(tex);
				// テキストフィールドの値を更新
				tex.setText( makeTextFieldText(i+1) );
			}
		}else if(arr>1){
			Object[] tmp = (Object[])obj;
			debugVar tmpvar = null;

			for(int i=0;i<tmp.length;i++){
				if(this.child_var==null){
					tmpvar = this.child_var = new debugVar(name+"["+i+"]",this.type,tmp[i],arr-1);
				}else{
					tmpvar.next_var = new debugVar(name+"["+i+"]",this.type,tmp[i],arr-1);
					tmpvar = tmpvar.next_var;
				}
				tmpvar.init(this.comp);
			}
		}
	}

	/** 子供や、配列要素を消す
	 */
	public void killChilds(){
		this._kill(1);
	}

	/** killする場合
	 * _initの逆
	 * @param start 全体を消すなら 0、一つだけ残すなら1
	 */
	private void _kill(int start){
		JLabel labl;
		JTextField tex;

		if(start==0 && arr_open!=null){
			this.comp.remove( this.arr_open );
		}

		for(int i=start;i<this.labels.size();){
			labl = (JLabel)this.labels.get(i);
			tex = (JTextField)this.textfields.get(i);

			this.comp.remove(labl);
			this.comp.remove(tex);

			this.labels.remove(i);
			this.textfields.remove(i);
		}
		//子供に同じ作業をさせる
		for(debugVar tmp = this.child_var;tmp != null;tmp = tmp.next_var){
			tmp._kill(0);
		}
		this.child_var = null;
	}


	/** 描画します
	 * @param y Yの補正位置
	 * @param update_var 変数のアップデートを行うかどうかのフラグをセットします。(falseならフォームの値が書き換えられない)
	 * @return
	 */
	public int draw(int y,boolean update_var){
		int num = this._draw(0,0,y,this.value,this.array,update_var);
		if(this.child_var!=null)
			num = this._childDraw(num,ARR_HOSEI,y,this.value,this.array,update_var);
		return num;
	}

	/** 描画します
	 * @param num 何個目の変数か？
	 * @param y 補正x座標
	 * @param y 補正Ｙ座標
	 * @param object 描画すべきオブジェクト
	 * @param arr 配列の階層
	 * @param update_var 変数のアップデートを行うかどうかのフラグをセットします。(falseならフォームの値が書き換えられない)
	 * @return
	 */
	private int _draw(int num,int x,int y,Object object,int arr,boolean update_var){
		String drawVal="";
		int length;
		int tmp;

		//配列オープン・クローズ切り替え
		if(this.arr_open != null){
			boolean tmpflag = arr_open.isSelected();
			arr_open.setBounds(2+x,y+num*VAR_HEIGHT,20,VAR_HEIGHT);
			if(this.arr_open_flag != tmpflag){
				if(tmpflag){
					this.initChild();
				}else{
					this.killChilds();
				}
			}
			this.arr_open_flag = tmpflag;
		}
		length  = this.labels.size();

		//実際の表示部
		for(int i=0;i<length;i++){
			JLabel lbl = (JLabel)this.labels.get(i);
			JTextField fld = (JTextField)this.textfields.get(i);
			if(lbl==null || fld==null)
				return num;
			if(i==1)
				x += ARR_HOSEI;
			lbl.setBounds(25+x,y+num*VAR_HEIGHT,90,VAR_HEIGHT);
			fld.setBounds(130,y+num*VAR_HEIGHT,90,VAR_HEIGHT);

			//実行中(変数の変更あり)
			if(update_var){
				//目に見えそうなのだけ更新
				if(  - VAR_HEIGHT < y+num*VAR_HEIGHT && y+num*VAR_HEIGHT<this.comp.getHeight()+VAR_HEIGHT ){
					drawVal = makeTextFieldText(i);
					fld.setText(drawVal);
				}
			}else{//実行中でないとき
				updateValue( fld , i );
			}
			num++;
		}
		return num;
	}


	/** テキストボックス内の変数の値を更新します
	 * @param arr_index 配列のインデックス+1(普通の変数は0)
	 */
	private void updateValue(JTextField fld,int arr_index){
		String fld_txt = fld.getText();
		int tmp = this.type&(~TYPE_ARRAY);

		if((this.type&TYPE_ARRAY)!=TYPE_ARRAY){
			fld_txt = this.encodeValueString(fld_txt,this.type,fld);
			switch(tmp){
			case TYPE_BYTE:
				this.value = new Byte(fld_txt);
				break;
			case TYPE_CHAR:
				this.value = new Character(fld_txt.charAt(0));
				break;
			case TYPE_DOUBLE:
				this.value = new Double(fld_txt);
				break;
			case TYPE_FLOAT:
				this.value = new Float(fld_txt);
				break;
			case TYPE_INT:
				this.value = new Integer(fld_txt);
				break;
			case TYPE_BOOLEAN:
				this.value = new Boolean(fld_txt);
				break;
			case TYPE_OBJECT:
				break;
			case TYPE_STRING:
				this.value = fld_txt;
				break;
			}
		}
		else if(arr_index>0){ //配列だった場合は、値を直接いじる
			fld_txt = this.encodeValueString(fld_txt,this.type,fld);
			switch(tmp){
			case TYPE_BYTE:
				((byte[])this.value)[arr_index-1] = Byte.parseByte( fld_txt );
				break;
			case TYPE_CHAR:
				((char[])this.value)[arr_index-1] = fld_txt.charAt(0);
				break;
			case TYPE_DOUBLE:
				((double[])this.value)[arr_index-1] = Double.parseDouble( fld_txt );
				break;
			case TYPE_FLOAT:
				((double[])this.value)[arr_index-1] = Float.parseFloat( fld_txt );
				break;
			case TYPE_INT:
				((int[])this.value)[arr_index-1] = Integer.parseInt( fld_txt );
				break;
			case TYPE_BOOLEAN:
				((boolean[])this.value)[arr_index-1] = fld_txt.equals("true");
				break;
			case TYPE_OBJECT:
				break;
			case TYPE_STRING:
				((String[])this.value)[arr_index-1] = fld_txt;
				break;
			}
		}
	}


	/** テキストフィールドに描画するものを作成します
	 * @param arr_index 配列のインデックス+1(普通の変数は0)
	 */
	private String makeTextFieldText(int arr_index){
		String drawVal="";
		int tmp;
		tmp = this.type&(~TYPE_ARRAY);
		if((this.type&TYPE_ARRAY)!=TYPE_ARRAY){
			switch(tmp){
			case TYPE_BYTE:
				drawVal = ( ((Byte)this.value).toString() );
				break;
			case TYPE_CHAR:
				drawVal = ((Character)this.value).toString();
				break;
			case TYPE_DOUBLE:
				drawVal = ((Double)this.value).toString();
				break;
			case TYPE_FLOAT:
				drawVal = ((Float)this.value).toString();
				break;
			case TYPE_INT:
				drawVal = ((Integer)this.value).toString();
				break;
			case TYPE_BOOLEAN:
				drawVal = ((Boolean)this.value).toString();
				break;
			case TYPE_OBJECT:
				drawVal = this.value.toString();
				break;
			case TYPE_STRING:
				drawVal = this.value.toString();
				break;
			}
		}else if(arr_index > 0){
			switch(tmp){
			case TYPE_BYTE:
				drawVal = "" + ((byte[])this.value)[arr_index-1];
				break;
			case TYPE_CHAR:
				drawVal = "" + ((char[])this.value)[arr_index-1];
				break;
			case TYPE_DOUBLE:
				drawVal = "" + ((double[])this.value)[arr_index-1];
				break;
			case TYPE_FLOAT:
				drawVal = "" + ((float[])this.value)[arr_index-1];
				break;
			case TYPE_INT:
				drawVal = "" + ((int[])this.value)[arr_index-1];
				break;
			case TYPE_BOOLEAN:
				drawVal = "" + ((boolean[])this.value)[arr_index-1];
				break;
			case TYPE_OBJECT:
				drawVal = ( (String[])this.value)[arr_index-1].toString();;
				break;
			case TYPE_STRING:
				drawVal = ((Object[])this.value)[arr_index-1].toString();
				break;
			}
		}
		return drawVal;
	}


	/** 描画します
	 * @param num 何個目の変数か？
	 * @param x 補正X座標
	 * @param y 補正Ｙ座標
	 * @param object 描画すべきオブジェクト
	 * @param arr 配列の階層
	 * @param update_var 変数のアップデートを行うかどうかのフラグをセットします。(falseならフォームの値が書き換えられない)
	 * @return 描画後の描画済み変数個数
	 */
	private int _childDraw(int num,int x,int y,Object obj,int arr,boolean update_var){
		if(this.child_var!=null){
			Object[] tmp = (Object[])obj;
			debugVar tmpvar = this.child_var;
			for(int i=0;i<tmp.length;i++){
				num = tmpvar._draw(num,x,y,obj,arr,update_var);
				num = tmpvar._childDraw(num,x+ARR_HOSEI,y,tmp[i],arr-1,update_var);
				tmpvar = tmpvar.next_var;
			}
		}
		return num;
	}



	/** 実行中かどうかの変更
	 * @param flag 実行中ならtrue,停止ならfalse
	 */
	public void setRunningFlag(boolean flag){
		int length = this.textfields.size();
		for(int i=0;i<length;i++){
			JTextField fld = (JTextField)this.textfields.get(i);
			if( fld == null )
				return;
			if( flag ){
				fld.setBackground(Color.LIGHT_GRAY);
				fld.setDisabledTextColor(Color.BLACK);
				fld.setEnabled(false);
			}else{
				fld.setBackground(Color.WHITE);
				fld.setEnabled(true);
				updateFormValue();
			}
		}
		if( this.child_var != null ){
			for(debugVar tmp=this.child_var;tmp!=null;tmp=tmp.next_var){
				tmp.setRunningFlag(flag);
			}
		}
	}

	/** フォームの内容を全更新。
	 */
	private void updateFormValue(){
		int length  = this.labels.size();
		String drawVal="";
		//実際の表示部
		for(int i=0;i<length;i++){
			JLabel lbl = (JLabel)this.labels.get(i);
			JTextField fld = (JTextField)this.textfields.get(i);
			if(lbl==null || fld==null)
				return;
			//更新処理
			drawVal = makeTextFieldText(i);
			fld.setText(drawVal);
		}
	}

	/** 型に応じて、その型でありえる文字列に変換し、テキストフィールドの中身を書き換える
	 * @param str 変換前の文字列
	 * @param type 型のタイプ
	 * @param fld テキストフィールド
	 * @return 変換後の文字列を返す
	 */
	private String encodeValueString(String str,int type,JTextField fld){
		int tmp = this.type&(~TYPE_ARRAY);
		int ok_str_len=0;
		char tmpchr;
		int str_len = str.length();
		String ok_text="";

		switch(tmp){
		case TYPE_BYTE:
			for(;ok_str_len<str_len;ok_str_len++){
				tmpchr = str.charAt(ok_str_len);
				if( !( ('0' <= tmpchr && tmpchr <= '9') || tmpchr=='-') ){
					break;
				}

				if( ok_str_len==0  || str_len==0){
					ok_str_len = -1;
					ok_text = "0";
				}else if( str_len != ok_str_len ){
					ok_text = str.substring(0,ok_str_len);
				}
			}
			break;
		case TYPE_CHAR:
			ok_str_len = 1;
			if(str_len == 0){
				ok_text = "a";
			}else if( str_len != 1 ){
				ok_text = str.substring(0,1);
			}
			break;
		case TYPE_FLOAT:
		case TYPE_DOUBLE:
			for(;ok_str_len<str_len;ok_str_len++){
				tmpchr = str.charAt(ok_str_len);
				if( ! ( ('0' <= tmpchr && tmpchr <= '9') || tmpchr=='.')  || tmpchr=='-' ){
					break;
				}
			}

			if( ok_str_len==0 || str_len==0){
				ok_str_len = -1;
				ok_text = "0";
			}else if( str_len != ok_str_len ){
				ok_text = str.substring(0,ok_str_len);
			}
			break;
		case TYPE_INT:
			for(;ok_str_len<str_len;ok_str_len++){
				tmpchr = str.charAt(ok_str_len);
				if( !(('0' <= tmpchr && tmpchr <= '9')  || tmpchr=='-') ){
					break;
				}
			}
			if( ok_str_len==0  || str_len==0){
				ok_str_len = -1;
				ok_text = "0";
			}else if( str_len != ok_str_len ){
				ok_text = str.substring(0,ok_str_len);
			}
			break;
		case TYPE_OBJECT:
			ok_str_len = str_len;
			break;
		case TYPE_BOOLEAN:
			ok_str_len = str_len;
			break;
		case TYPE_STRING:
			ok_str_len = str_len;
			break;
		}
		if( str_len != ok_str_len ){
			fld.setText( ok_text );
			return ok_text;
		}else{
			return str;
		}
	}

}
