using System;
using System.Collections.Generic;
using System.Collections;
using MiMic.CsApi;
namespace MiMic.CsApi.LPC1769
{

	public class Pwm:MiMicClass,Mcu.IBasicPeripheral
	{
		public class Option
		{
			public UInt32? freq;
			public Peripheral.Option phl;
			public Option(Option s)
			{
				if(s!=null){
					this.freq=s.freq;
					this.phl=new Peripheral.Option(s.phl);
				}
			}
			public Option(UInt32 i_freq,Peripheral.Option i_phl)
			{
				this.freq=i_freq;
				this.phl=i_phl;
			}
		}
		private const UInt32 _TCR=0x40018004;
		private const UInt32 _TC=0x40018008;
		private const UInt32 _PR=0x4001800C;
		private const UInt32 _PC=0x40018010;
		private const UInt32 _MCR=0x40018018;
		private const UInt32 _PCR=0x4001804C;
		private const UInt32 _LER=0x40018050;
		private const UInt32 _CTCR=0x40018070;
		private static UInt32[] _MRn=new UInt32[]{0x40018018,0x4001801C,0x40018020,0x40018024,0x40018040,0x40018044,0x40018048};
		internal Mcu _mcu;
		private Peripheral _phl;
		private UInt32 _cache_mr0;
		
		public Pwm(Mcu i_mcu,Option i_opt)
		{
			this._mcu=i_mcu;
			//PHL生成。
			this._phl=new Peripheral(i_mcu,LPCXpresso1769.PHL.PWM1,null);
			//設定値のロード
			Option opt=new Option(i_opt);
			if(opt.phl==null){
				opt.phl=new Peripheral.Option(null);
			}
			//デフォルト値設定
			if(opt.phl.power==null){opt.phl.power=1;};
			if(opt.phl.clock==null){opt.phl.clock=0;};
			if(opt.freq==null){opt.freq=100;}
			
			string bc="";
			List<UInt32> db=new List<UInt32>();
			//optionの設定
			bc+=this.BCF_setOpt(opt,db);
			bc+=LPCXpresso1769._BCF.setMem(_PC,0x0,db);
			//PCR
			bc+=LPCXpresso1769._BCF.setMem(_PCR,0x0,db);
			//CTCR
			bc+=LPCXpresso1769._BCF.setMem(_CTCR,0x0,db);
			//TCRに値設定(カウンタとPWMモードを無効化)
			bc+=LPCXpresso1769._BCF.setMem(_TCR,0x0,db);
			bc+=LPCXpresso1769._BCF.setMem(_TC,0x0,db);
			//TCRに値設定(カウンタとPWMモードを有効化)
			bc+=LPCXpresso1769._BCF.setMem(_TCR,0x9,db);
			//初期化
			this._mcu.callMiMicWithCheck(bc+LPCXpresso1769._BCF.END,db);
			//ペリフェラルをMCUに登録
			this._mcu.registerPhl(this,"PWM");
		}
		internal string BCF_setOpt(Option i_opt,List<UInt32> i_db)
		{
			string bc="";
			UInt32 mr0;
			//ペリフェラルの設定
			if(i_opt.phl!=null)
			{
				bc+=this._phl.BCF_setOpt(i_opt.phl,i_db);
			}
			if(i_opt.freq!=null){
				//サイクル(Hz)とペリフェラルのクロックから、MR0のサイクルを決定
				mr0=(UInt32)(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;
		}
		internal string BCF_setLER(int i_ch,List<UInt32> i_db)
		{
			//LERにビットをセット
			return LPCXpresso1769._BCF.setBit(_LER,0x01,0x01,i_ch,i_db);
		}
		internal string BCF_setLERs(UInt32 i_mask,UInt32 i_value,List<UInt32> i_db)
		{
			//LERにビットをセット
			return LPCXpresso1769._BCF.setBit(_LER,i_mask,i_value*i_mask,0,i_db);
		}
		internal string BCF_setMRn(int i_ch,UInt32 i_val,List<UInt32> i_db)
		{
			return LPCXpresso1769._BCF.setMem(_MRn[i_ch],i_val,i_db);
		}
		internal string BCF_setMRnByDuty(int i_ch,float i_duty,List<UInt32> i_db)
		{
			return this.BCF_setMRn(i_ch,(UInt32)(i_duty*this._cache_mr0),i_db);
		}
		internal string BCF_setPCRbits(UInt32 i_mask,UInt32? i_edge,UInt32? i_en,List<UInt32> i_db)
		{
			UInt32 m,v;
			m=v=0;
			if(i_edge!=null){
				UInt32 t=0xff&i_mask;
				v|=(UInt32)i_edge*t;
				m|=t;
			}
			if(i_en!=null){
				UInt32 t=0xff00&i_mask;
				v|=((UInt32)i_en*t);
				m|=t;
			}
			//ビットの設定
			return LPCXpresso1769._BCF.setBit(_PCR,m,v,0,i_db);
		}
		public void setOpt(Option i_opt)
		{
			List<UInt32> db=new List<UInt32>();
			string bc=this.BCF_setOpt(i_opt,db);
			this._mcu.callMiMicWithCheck(bc+LPCXpresso1769._BCF.END,db);
		}

		public PwmPin getPin(PinId i_pin,PwmPin.Option i_opt)
		{
			return new PwmPin(this,i_pin,i_opt);
		}
		public PwmPort getPort(PinId[] i_pins,PwmPin.Option i_opt)
		{
			return new PwmPort(this,i_pins,i_opt);
		}
		//for interigent connection
		public object getPinObject(PinId i_id,object i_opt)
		{
			return getPin(i_id,(PwmPin.Option)i_opt);
		}
		public object getPortObject(PinId[] i_id,object i_opt)
		{
			return getPort(i_id,(PwmPin.Option)i_opt);
		}		
	}

	public class PwmPort:MiMicClass
	{
		private class PwmPinInfo
		{
			public UInt32 port;
			public int ch;
			public UInt32 pin_sel;
			public PwmPinInfo(UInt32 i_port,int i_ch,UInt32 i_pin_sel)
			{
				this.port=i_port;
				this.ch=i_ch;
				this.pin_sel=i_pin_sel;
			}
		}
		private static PwmPinInfo pin2PwmPinInfo(PinId i_pin)
		{
			//pinの完全な機能名を得る。(得られれば機能がある。)
			string fn=LPCXpresso1769.completePinFunctionName(i_pin,"PWM");
			//portとbitを得る(AD0だけしか管理しないよ)
			string[] a=fn.Substring(2).Split('.');
			return new PwmPinInfo(0,int.Parse(a[1]),LPCXpresso1769.getPinSelByFunctionName(i_pin,fn));
		}
		protected Pwm _pwm;
		private PwmPinInfo[] _pins;
		private const UInt32 _PINSEL_AUTO_DETECT=0x0fffffff;
		private UInt32 _port_no;
		private Port _port;
		private UInt32 _pcr_mask;
		private UInt32 _ler_mask;		
		public PwmPort(Pwm i_pwm,PinId[] i_pins,PwmPin.Option i_opt)
		{
			this._pwm=i_pwm;
			//ピンセットを取得
			this._pins=new PwmPinInfo[i_pins.Length];
			//pinに変換する。
			for(int i=0;i<i_pins.Length;i++){
				this._pins[i]=pin2PwmPinInfo(i_pins[i]);
			}
			//pinが全て同じポートに所属しているか確認
			UInt32 p=this._pins[0].port;
			for(int i=1;i<this._pins.Length;i++){
				if(p!=this._pins[1].port){
					throw new MiMicException("Invalid pin combination.");
				}
			}
			//ポートの生成
			this._port=new Port(i_pwm._mcu,i_pins,null);
			this._port_no=p;
			
			this._ler_mask=0;
			this._pcr_mask=0;
			for(int i=0;i<this._pins.Length;i++){
				this._ler_mask|=((UInt32)0x1<<this._pins[i].ch);
				this._pcr_mask|=((UInt32)0x101<<this._pins[i].ch);
			}		
			PwmPin.Option opt=new PwmPin.Option(i_opt);
			if(opt.pin==null){
				opt.pin=new Pin.Option(null);
			}
			//デフォルト値のロード
			if(opt.pin.sel==null){opt.pin.sel=PwmPort._PINSEL_AUTO_DETECT;}
			if(opt.duty==null){opt.duty=0;}
			if(opt.enable==null){opt.enable=1;}
			this.setOpt(opt);
		}
		public void setOpt(PwmPin.Option i_opt)
		{
			List<UInt32> db=new List<UInt32>();
			//BCの生成
			string bc="";
			//i_optの展開
			if(i_opt.pin!=null)
			{
				Pin.Option[] optset=new Pin.Option[this._pins.Length];
				for(int i=0;i<this._pins.Length;i++){
					//pinselが_PINSEL_AUTO_DETECTならばauto。そうでなければundefinedも含めて設定
					UInt32? s=(i_opt.pin.sel==PwmPort._PINSEL_AUTO_DETECT)?this._pins[i].pin_sel:i_opt.pin.sel;
					optset[i]=new Pin.Option(s,i_opt.pin.mode,i_opt.pin.od);
				}
				//portの設定
				bc+=this._port.BCF_setOpts(optset,db);
			}
			//PCRに値設定
			if(i_opt.enable!=null){
				bc+=this._pwm.BCF_setPCRbits(this._pcr_mask,0,i_opt.enable,db);
			}
			//デューティ比を
			if(i_opt.duty!=null){
				//デューティ比を設定
				for(int i=0;i<this._pins.Length;i++){
					bc+=this._pwm.BCF_setMRnByDuty(this._pins[i].ch,(float)i_opt.duty,db);
				}
				//LERにセット
				bc+=this._pwm.BCF_setLERs(this._ler_mask,1,db);
			}
			this._pwm._mcu.callMiMicWithCheck(bc+LPCXpresso1769._BCF.END,db);
			return;
		}
		//
		//setDutys
		//
		private string BCF_setDutys(float?[] i_duty_array,List<UInt32> i_db)
		{
			if(i_duty_array.Length!=this._pins.Length){
				throw new MiMicException();
			}
			string bc="";
			//デューティ比をまとめてセット
			for(int i=0;i<this._pins.Length;i++){
				if(i_duty_array[i]!=null){
					bc+=this._pwm.BCF_setMRnByDuty(this._pins[i].ch,(float)i_duty_array[i],i_db);
				}
			}
			//LERにセット
			bc+=this._pwm.BCF_setLERs(this._ler_mask,1,i_db);
			return bc;
		}
		
		public void setDutys(float?[] i_duty_array)
		{
			List<UInt32> db=new List<UInt32>();
			string bc=this.BCF_setDutys(i_duty_array,db);
			this._pwm._mcu.callMiMicWithCheck(bc+LPCXpresso1769._BCF.END,db);
		}
		public NBResult setDutysNB(float?[] i_duty_array)
		{
			List<UInt32> db=new List<UInt32>();
			string bc=this.BCF_setDutys(i_duty_array,db);
			return new NBResult(this._pwm._mcu.callMiMicNB(bc+LPCXpresso1769._BCF.END,db));
		}
	}
	public class PwmPin:MiMicClass
	{
		public class Option
		{
			public float? duty;
			public UInt32? enable;
			public Pin.Option pin;
			public Option(float? i_duty,UInt32? i_enable,Pin.Option i_pin)
			{
				this.duty=i_duty;
				this.enable=i_enable;
				this.pin=i_pin;
			}
			public Option(Option s)
			{
				if(s!=null){
					this.duty=s.duty;
					this.enable=s.enable;
					this.pin=s.pin;
				}
			}
		}
		private PwmPort _pport;
		public PwmPin(Pwm i_pwm,PinId i_pin,Option i_opt)
		{
			//1pinのポートとして実装
			this._pport=new PwmPort(i_pwm,new PinId[]{i_pin},i_opt);
		}
		public void setOpt(Option i_opt)
		{
			this._pport.setOpt(i_opt);
		}
		public void setDuty(float i_duty)
		{
			this._pport.setDutys(new float?[]{i_duty});
		}
		public NBResult setDutyNB(float i_duty)
		{
			return this._pport.setDutysNB(new float?[]{i_duty});
		}
	}
}