/**
 * @fileOverview LPC1769のPwmペリフェラル、ピンを制御するクラスを定義する。
 */
(function(){
var DEV=LPC1769;
var BCF=DEV._BCF;
var EE=DEV._EE;
var isUndef=MiMicLib.isUndef;
var isArray=MiMicLib.isArray;
var cloneAssoc=MiMicLib.cloneAssoc;



/**
 * LPCXPresso1769.Pwm (Pwm)クラスのコンストラクタ。
 * MCUに関連付けしたPwmペリフェラルを生成する。
 * Pwmクラスは、PWM1ペリフェラルを管理して、PWMペリフェラル全体の設定を管理する。
 * Pwmの分解能は、無指定の場合25MHz(1/4クロック)設定である。関数は、PWMのマッチレジスタ以外に、PCR,CTCR,TCRを同時に初期化する。
 * @name LPC1769.Pwm
 * @constructor
 * @param {object as LPC1769.Mcu} i_mcu
 * インスタンスを結びつけるMcuオブジェクト。
 * @param {associative array} i_opt
 * インスタンス生成と同時にsetOpt関数でセットする値を指定する。省略時は、{freq:100,phl:{power:1,clock:0}}である。
 * 詳細は、setOpt関数を参照。 
 * @example
 * //create PWM  pheripheral
 * var mcu=new LPC1769.Mcu("192.168.0.39");
 * var pwm=new LPC1769.Pwm(mcu,"");
 */
DEV.Pwm=function Pwm(i_mcu,i_opt)
{
	try{
		this._mcu=i_mcu;
		//PHL生成。
		this._phl=new DEV.Peripheral(i_mcu,DEV.PHL.PWM1);
		//設定値のロード
		var opt=isUndef(i_opt)?{phl:{}}:
		{//i_optある。
			phl:isUndef(i_opt.phl)?{}:cloneAssoc(i_opt.phl),
			freq:i_opt.freq
		};
		//デフォルト値設定
		if(isUndef(opt.phl.power)){opt.phl.power=1;};
		if(isUndef(opt.phl.clock)){opt.phl.clock=0;};
		if(isUndef(opt.freq)){opt.freq=100;}
		
		var bc="";
		var db=new Array();
		//optionの設定
		bc+=this.BCF_setOpt(opt,db);
		bc+=BCF.setMem(this._PC,0x0,db);
		//PCR
		bc+=BCF.setMem(this._PCR,0x0,db);
		//CTCR
		bc+=BCF.setMem(this._CTCR,0x0,db);
		//TCRに値設定(カウンタとPWMモードを無効化)
		bc+=BCF.setMem(this._TCR,0x0,db);
		bc+=BCF.setMem(this._TC,0x0,db);
		//TCRに値設定(カウンタとPWMモードを有効化)
		bc+=BCF.setMem(this._TCR,0x9,db);
		//初期化
		this._mcu.callMiMicWithCheck(bc+BCF.END,db);
		//ペリフェラルをMCUに登録
		this._mcu.registerPhl(this,"PWM");
	}catch(e){
		throw new MiMicException(e);
	}

}
DEV.Pwm.prototype=
{
	_cache:{
		mr0:0
	},
	_TCR:0x40018004,
	_TC:0x40018008,
	_PR:0x4001800C,
	_PC:0x40018010,
	_MCR:0x40018018,
	_PCR:0x4001804C,
	_LER:0x40018050,
	_CTCR:0x40018070,
	_MRn:[0x40018018,0x4001801C,0x40018020,0x40018024,0x40018040,0x40018044,0x40018048],
	_phl:null,
	_mcu:null,

	/**
	 * @private
	 */
	BCF_setOpt:function BCF_setOpt(i_opt,i_db)
	{
		try{
			var bc="";
			var mr0;
			//ペリフェラルの設定
			if(!isUndef(i_opt.phl)){
				bc+=this._phl.BCF_setOpt(i_opt.phl,i_db);
			}
			if(!isUndef(i_opt.freq)){
				//サイクル(Hz)とペリフェラルのクロックから、MR0のサイクルを決定
				mr0=Math.round(this._phl.getPCLK()/i_opt.freq);
				if(mr0<=0){
					//ペリフェラルクロック小さすぎﾜﾛﾀ
					throw new MiMicException("The peripheral clock too small.");
				}
				//MR0に値設定
				bc+=this.BCF_setMRn(0,mr0,i_db);
				//LERにビットセット
				bc+=this.BCF_setLER(0,i_db);
				this._cache.mr0=mr0;
			}
			return bc;
		}catch(e){
			throw new MiMicException(e);
		}
	},
	BCF_setLER:function BCF_setLER(i_ch,i_db)
	{
		try{
			//LERにビットをセット
			return BCF.setBit(this._LER,0x01,0x01,i_ch,i_db);
		}catch(e){
			throw new MiMicException(e);
		}
	},
	BCF_setLERs:function BCF_setLERs(i_mask,i_value,i_db)
	{
		try{
			//LERにビットをセット
			return BCF.setBit(this._LER,i_mask,i_value,0,i_db);
		}catch(e){
			throw new MiMicException(e);
		}
	},
	
	/**
	 * MRnに値を設定するBC
	 @private
	 */
	BCF_setMRn:function BCF_setMRn(i_ch,i_val,i_db)
	{
		try{
			return BCF.setMem(this._MRn[i_ch],i_val,i_db);
		}catch(e){
			throw new MiMicException(e);
		}
	},
	BCF_setMRnByDuty:function BCF_setMRnByDuty(i_ch,i_duty,i_db)
	{
		try{
			return this.BCF_setMRn(i_ch,Math.round(i_duty*this._cache.mr0),i_db);
		}catch(e){
			throw new MiMicException(e);
		}
	},
	/**
	 * PCRに値を設定するBC
	 @private
	 @param i_mask
	 @param i_edge
	 エッジモード(1/0)
	 @param i_en
	有効/無効の選択
	 */
	BCF_setPCRbits:function BCF_setPCRbits(i_mask,i_value,i_db)
	{
		try{
			//ビットの設定
			return BCF.setBit(this._PCR,i_mask,i_value,0,i_db);
		}catch(e){
			throw new MiMicException(e);
		}
	},
	
	/**
	 * Pwmペリフェラルに値を設定する。
	 * 設定する値は、全てのPWMチャンネル（PIN）に共通な項目である。
	 * @name LPC1769.Pwm#setOpt
	 * @function
	 * @param {associative array} i_opt
	 * GPIOピンのコンフィグレーションパラメタである。必要な値を格納した連想配列で指定する。
	 * 全ての値を省略することは出来ない。連想配列のメンバは以下の通り。
	 * <pre>{freq:int,phl:associative array}</pre>
	 * <ul>
	 * <li>freq - PWMの基本周波数(Hz)である。この値は、全てのPWMチャンネルで共通である。</li>
	 * <li>phl - ペリフェラルの共通パラメタである。LPC1769.Peripheral.setOpt関数のi_optに渡すパラメタである。詳細はLPC1769.Peripheral#setOptを参照。</li>
	 * </ul>
	 * @example
	 * //create PWM (logical)peripheral
	 * var mcu=new LPC1769.Mcu(“192.168.0.39”);
	 * var pwm=new LPC1769.Pwm(mcu);
	 * pwm.setOpt({freq:1000});
	 */
	setOpt:function setOpt(i_opt)
	{
		try{
			var db=new Array();
			var bc=this.BCF_setOpt(i_opt,db);
			this._mcu.callMiMicWithCheck(bc+BCF.END,db);
		}catch(e){
			throw new MiMicException(e);
		}
	},	
	/**
	 * PWM機能を持つピンを取得する。
	 * ピン識別子で指定されるピンをPWMペリフェラルと結合して、PwmPinを生成する。
	 * 関数は、PwmPinオブジェクトのコンストラクタをコールして、PwmPinを生成する。失敗すると、例外をスローする。
	 * 生成ルールについての詳細は、PwmPinを参照。
	 * @name LPC1769.Pwm#getPin
	 * @function
	 * @param {object as ピン識別子} i_pin
	 * PWM機能を割り当てるPINの識別子である。
	 * @param {associative array} i_opt
	 * PwmPinのコンストラクタに渡すオプション値。省略時はundefinedである。詳細はLPC1769.PwmPinを参照。
	 * @return {object as LPC1769.PwmPin}
	 * LPC1769.PwmPinクラスのオブジェクトである。
	 * @example
	 * //create 100hz d=0.5 pwm
	 * var mcu=new LPC1769.Mcu("192.168.0.39");
	 * var pwm=new LPC1769.Pwm(mcu);
	 * var pwmpin=pwm.getPin(LPC1769.P2[0],{duty:0.5});
	 */
	getPin:function getPin(i_pin,i_opt)
	{
		try{
			return new DEV.PwmPin(this,i_pin,i_opt);
		}catch(e){
			throw new MiMicException(e);
		}
	},
	/**
	 * PWM機能を持つポート(Pin集合)を取得する。
	 * ピン識別子で指定されるピンのセットをPWMペリフェラルと結合して、PwmPortを生成する。
	 * 関数は、PwmPortのコンストラクタをコールする。
	 * 生成ルールについては、PwmPort関数を参照すること。
	 * @name LPC1769.Pwm#getPort
	 * @function
	 * @param {array[ピン識別子]} i_pin
	 * Pwm機能を割り当てるPINの識別子の配列である。値は、LPC1769.Pn[m]のメンバ変数である。
	 * @param {associative array} i_opt
	 * PwmPortのコンストラクタに渡すオプション値。省略時はundefinedである。詳細はLPC1769.PwmPortを参照。
	 * @return {object as LPC1769.PwmPort}
	 * LPC1769.PwmPortクラスのオブジェクトである。
	 * @example
	 * //create 2AdcPort that has 2 pins.
	 * var mcu=new LPC1769.Mcu("192.168.0.39");
	 * var pwm=new LPC1769.Pwm(mcu);
	 * var port=pwm.getPort([LPC1769.P0[23],LPC1769.P0[24]]);
	 */
	getPort:function getPort(i_pins,i_opt)
	{
		try{
			return new DEV.PwmPort(this,i_pins,i_opt);
		}catch(e){
			throw new MiMicException(e);
		}
	}	
}

/**
 * Pwm pinからPwmInfoを取得
 * @private
 */
function makePwmPinInfoArray(i_pins)
{
	try{
		var ret=new Array();
		for(var i=0;i<i_pins.length;i++){
			//pinの完全な機能名を得る。(得られれば機能がある。)
			var fn=DEV.completePinFunctionName(i_pins[i],"PWM");
			//portとbitを得る
			var a=fn.substring(2).split(".");
			if(isNaN(a[1])){
				throw new MiMicException(EE.INVALID_CFG);
			}
			ret.push({port:0,ch:parseInt(a[1]),pin_sel:DEV.getPinSelByFunctionName(i_pins[i],fn)});
		}
		return ret;
	}catch(e){
		throw new MiMicException(e);
	}
}
/**
 * Pwmペリフェラルと複数のPin識別子から、シングルエッジPwmポートを構成する。
 * Pwmペリフェラルオブジェクトにピン識別子の配列で指定されたピンを関連付けて、PWM機能を持つポートを生成する。
 * 関数は、ピン識別子を元に、それぞれのピンがPWM機能に接続できるかを調べる。
 * ピンにPWM機能を割り当てられない場合、例外が発生する。どのピンにPWM機能が割り当てられるかは、MCUのスペックシートを参照すること。
 * PWMポートは、複数の同一周期のPWMピンのデューティ比を、同期して操作するときに便利である。
 * @constructor
 * @name LPC1769.PwmPort
 * @param {object as LPC1769.Pwm} i_pwm
 * インスタンスを結びつけるPwmオブジェクト。
 * @param {array[pin識別子]} i_pins
 * ピン識別子の配列。指定できるのは、LPC1769.P?[?]である。
 * @param {associative array} i_opt
 * setOpt関数のi_optに渡すパラメタである。省略可能。省略時は{duty:100,enable:1,pin{sel:auto}}を設定する。
 * autoは、関数が自動的に決定するPINSELの値である。詳細はsetOpt関数を参照。 
 * dutyをピン毎に設定する場合は、setDuty関数を使うこと。
 * @example
 * //create PWM1.0,PWM1.1
 * var mcu=new LPC1769.Mcu(“192.168.0.39”);
 * var pwm=new LPC1769.Pwm(mcu,{freq:100});
 * var port=new LPC1769.PwmPort(pwm,[LPC1769.P2[0],LPC1769.P2[1]]); 
 */
DEV.PwmPort=function PwmPort(i_pwm,i_pins,i_opt)
{
	try{
		this._pwm=i_pwm;
		//ピンセットを取得
		var pininfo=makePwmPinInfoArray(i_pins);

		//pinが全て同じポートに所属しているか確認
		var p=pininfo[0].port;
		for(var i=1;i<pininfo.length;i++){
			if(p!=pininfo[i].port){
				throw new MiMicException("Invalid pin combination.");
			}
		}
		//ポートの生成
		this._port=new DEV.Port(i_pwm._mcu,i_pins);
		this._port_no=p;
		

		//ピンオプションの生成
		var opt={};
		if(isUndef(i_opt)){
			opt.pin=this._port.clonePinOptAssoc({});
			opt.duty=0;
			opt.enable=1;
		}else{
			opt.pin=this._port.clonePinOptAssoc(isUndef(i_opt.pin)?{}:i_opt.pin);
			opt.duty=isUndef(i_opt.duty)?0:i_opt.duty;
			opt.enable=isUndef(i_opt.enable)?0:i_opt.enable;
		};
		//pinselの自動設定
		for(var i=0;i<pininfo.length;i++){
			if(isUndef(opt.pin[i].sel)){
				opt.pin[i].sel=pininfo[i].pin_sel;
			}
		}
		this._pininfo=pininfo;
		this.setOpt(opt);
	}catch(e){
		throw new MiMicException(e);
	}	
}
DEV.PwmPort.prototype=
{
	_pwm:null,
	_pininfo:null,
	_port_no:0,
	_port:null,
	/**
	 * PWMポートにオプション値を設定する。
	 * 設定可能な値は、LPC1769.PwmPin#setOptと同じである。	 
	 * @name LPC1769.PwmPort#setOpt
	 * @function
	 * @param {object as associative array} i_opt
	 * PWMピンのコンフィグレーションパラメタである。必要な値を格納した連想配列、またはその配列を指定する。
	 * 全てのピンに同じパラメータを指定する場合は、連装配列で指定する。個別に設定する場合は、連装配列を配列にして指定する。
	 * <pre>{i_opt:associative array}</pre>
	 * <ul>
	 * <li>pin(option) - array[{PIN_OPTION}] | {PIN_OPTION}</li>
	 * <li>enable(option) - array[{1bit int}] | {1bit int}</li>
	 * <li>duty(option) - array[{1bit int}] | {1bit int}</li>
	 * </ul>
	 * @example
	 * var mcu=new LPC1769.Mcu("192.168.128.39",true);
	 * var pwm=mcu.getPeripheral("PWM",{freq:100});
	 * var port=pwm.getPort([LPC1769.P2[0]]);
	 * port.setOpt({enable:0});
	 */
	setOpt:function setOpt(i_opt)
	{
		try{
			var pininfo=this._pininfo;
			var db=new Array();
			//BCの生成
			var bc="";
			//pinの設定
			if(!isUndef(i_opt.pin)){
				bc+=this._port.BCF_setOpts(i_opt.pin,db);
			}
			//enable設定
			if(!isUndef(i_opt.enable)){
				var pcr_mask=0x0;
				var pcr_bits=0x0;
				if(isArray(i_opt.enable)){
					if(this._port.pins.length!=i_opt.enable.length){
						throw new MiMicException(DEV._EE.INVALID_ARG);
					}					
					for(var i=0;i<pininfo.length;i++){
						if(isUndef(i_opt.enable[i])){
							continue;
						}
						pcr_bits|=((0x100*i_opt.enable[i])<<pininfo[i].ch);
						pcr_mask|=(0x101<<pininfo[i].ch);
						//single edge
					}
				}else{
					for(var i=0;i<pininfo.length;i++){
						pcr_mask|=(0x101<<pininfo[i].ch);
						pcr_bits|=((0x100*i_opt.enable)<<pininfo[i].ch);
					}
				}
				//edgeは固定でゼロ、enableはビット配列。
				bc+=this._pwm.BCF_setPCRbits(pcr_mask,pcr_bits,db);
			}
			//dutyの設定
			if(!isUndef(i_opt.duty)){
				var ler_value=0;
				if(isArray(i_opt.duty)){
					if(this._port.pins.length!=i_opt.duty.length){
						throw new MiMicException(DEV._EE.INVALID_ARG);
					}					
					for(var i=0;i<pininfo.length;i++){
						if(isUndef(i_opt.duty[i])){
							continue;
						}
						bc+=this._pwm.BCF_setMRnByDuty(pininfo[i].ch,i_opt.duty[i],db);
						ler_value|=(0x1<<pininfo[i].ch);
					}
				}else{
					for(var i=0;i<pininfo.length;i++){
						bc+=this._pwm.BCF_setMRnByDuty(pininfo[i].ch,i_opt.duty,db);
						ler_value|=(0x1<<pininfo[i].ch);
					}
				}
				bc+=this._pwm.BCF_setLERs(ler_value,ler_value,db);
			}
			this._pwm._mcu.callMiMicWithCheck(bc+BCF.END,db);
			return;
		}catch(e){
			throw new MiMicException(e);
		}
	},
	/**
	 * PWMポートのデューティ比を変更する。
	 * この関数は、LPC1769.PwmPort#setOpt関数へのショートカットである。
	 * @name LPC1769.PwmPort#setDutys
	 * @function
	 * @param {array[double]} i_duty_array
	 * デューティ比の配列である。0.0<=n<=1.0のdouble値を指定する。
	 * ポートを構成するピンの数だけ指定する。変更しないピンには、undefinedを指定できる。
	 * @example
	 * varmcu=new LPC1769.Mcu("192.168.128.39",true);
	 * var pwm=mcu.getPeripheral("PWM",{freq:100});
	 * var port=pwm.getPort([LPC1769.P2[0],LPC1769.P2[1]]);
	 * pwm.setDutys([0.5,0.0]);//PWM0=0.5,PWM1=0.0
	 * pwm.setDutys([undefined,0.0]);//PWM1=0.0
	 */	
	setDutys:function setDutys(i_duty_array)
	{
		try{			
			this.setOpt({duty:i_duty_array});
		}catch(e){
			throw new MiMicException(e);
		}
	}
}
/**
 * PwmペリフェラルとPin識別子から、シングルエッジPwmピンを構成する。
 * Pwmペリフェラルオブジェクトにピン識別子で指定されたピンを関連付けて、PWM機能を持つピンを生成する。
 * 関数は、ピン識別子を元に、そのピンがPWM機能に接続できるかを調べる。ピンにPWM機能を割り当てられない場合、例外が発生する。どのピンにPWM機能が割り当てられるかは、MCUのスペックシートを参照すること。
 * ピンがPWM機能を持たない場合、例外が発生する。
 * @constructor
 * @name LPC1769.PwmPin
 * @param {object as LPC1769.Pwm} i_pwm
 * インスタンスを結びつけるAdcオブジェクト。
 * @param {object as pin識別子} i_pin
 * ピン識別子。指定できるのは、LPC1769.P?[?]である。
 * @param {associative array} i_opt
 * setOpt関数のi_optに渡すパラメタである。省略可能。省略時は{duty:100,enable:1,pin{sel:undefined}}を設定する。
 * 詳細はsetOpt関数を参照。 pin.selを省略(undefined)した場合、関数はpin.selの値をPin情報から自動的に決定する。
 * @example
 * //create PWM1.0
 * var mcu=new LPC1769.Mcu(“192.168.0.39”);
 * var pwm=new LPC1769.Pwm(mcu,{freq:100});
 * var pwmpin=new LPC1769.PwmPin(pwm,P2[0]); 
 */
DEV.PwmPin=function PwmPin(i_pwm,i_pin,i_opt)
{
	try{
		//1pinのポートとして実装
		this._pport=new DEV.PwmPort(i_pwm,[i_pin],i_opt);
	}catch(e){
		throw new MiMicException(e);
	}
}

DEV.PwmPin.prototype=
{
	_pport:null,

	/**
	 * PWMピンにオプション値を設定する。
	 * @name LPC1769.PwmPin#setOpt
	 * @function
	 * @param {object as associative array} i_opt
	 * PWMピンのコンフィグレーションパラメタである。必要な値を格納した連想配列で指定する。
	 * 全ての値を省略することは出来ない。連想配列のメンバは以下の通り。
	 * <pre>{pin:{associative array},enable:int,duty:double}</pre>
	 * <ul>
	 * <li>pin - LPC1769.Pin#setOpt関数のi_optに渡すパラメタである。</li>
	 * <li>enable - PWMの有効/無効を切り替える。1で有効。</li>
	 * <li>duty - デューティ比である。0.0<=n<=1.0のdouble値を指定する。</li>
	 * </ul>
	 * @example
	 * var mcu=new LPC1769.Mcu("192.168.128.39",true);
	 * var pwm=mcu.getPeripheral("PWM",{freq:100});
	 * var pin=pwm.getPin(LPC1769.P2[0]);
	 * pin.setOpt({duty:0});
	 */
	setOpt:function setOpt(i_opt)
	{
		try{
			this._pport.setOpt(i_opt);
		}catch(e){
			throw new MiMicException(e);
		}
	},
	/**
	 * PWMのデューティ比を変更する。
	 * @name LPC1769.PwmPin#setDuty
	 * @function
	 * @param {double} i_duty
	 * デューティ比である。0.0<=n<=1.0のdouble値を指定する。
	 * @example
	 * varmcu=new LPC1769.Mcu("192.168.128.39",true);
	 * var pwm=mcu.getPeripheral("PWM",{freq:100});
	 * pwmpin=pwm.getPin(LPC1769.P2[0]);
	 * pwmpin.setDuty(0.5);
	 */
	setDuty:function setDuty(i_duty)
	{
		try{
			this._pport.setDutys([i_duty]);
		}catch(e){
			throw new MiMicException(e);
		}
	}
}
	


}());
