using System;
using System.Collections.Generic;
using System.Collections;
using MiMic.CsApi;

namespace MiMic.CsApi.LPC1769
{
	public class Gpio:MiMicClass,Mcu.IBasicPeripheral
	{
		public class Option
		{
			//has not option!
		};
		public Gpio(Mcu i_mcu,Option i_opt)
		{
			this._ref_mcu=i_mcu;
			i_mcu.registerPhl(this,"GPIO");
		}
		private static UInt32[] _FIO_DIR=new UInt32[]{0x2009C000,0x2009C020,0x2009C040,0x2009C060,0x2009C080};
		private static UInt32[] _FIO_PIN=new UInt32[]{0x2009C014,0x2009C034,0x2009C054,0x2009C074,0x2009C094};
		private static UInt32[] _FIO_SET=new UInt32[]{0x2009C018,0x2009C038,0x2009C058,0x2009C078,0x2009C098};
		private static UInt32[] _FIO_CLR=new UInt32[]{0x2009C01C,0x2009C03C,0x2009C05C,0x2009C07C,0x2009C09C};
		private static UInt32[] _FIO_MASK=new UInt32[]{0x2009C010,0x2009C030,0x2009C050,0x2009C070,0x2009C090};
		internal Mcu _ref_mcu;
		public string BCF_getValues(int i_ch,UInt32 i_mask,List<UInt32> i_db)
		{
			/*GPIOレジスタからとってくるBC
			SGET #0;//GPIOの値レジスタアドレス
			SGET #1;//GPIOのMASKレジスタアドレス
			SGET #2;//MASKレジスタの値
			MSET #2,#1;//MASK設定
			MGET #3,#0;//値取得
			SPUT #3;//SPUT
			 */
			i_db.Add(_FIO_PIN[i_ch]);
			i_db.Add(_FIO_MASK[i_ch]);
			i_db.Add(~i_mask);
			return "EA00EA01EA02DF0201DB0300EE03";
		}
		/**
		 FIOPINとFIOMASKに値をセットする。
		 SGET #0;//GPIOの値レジスタアドレス
		 SGET #1;//GPIOのMASKレジスタアドレス
		 SGET #2;//MASKレジスタの値
		 SGET #3;//GPIO値
		 MPUT #2,#1;//MASK設定
		 MPUT #3,#0;//値設定
		* @private
		*/
		internal string BCF_setValues(int i_ch,UInt32 i_mask,UInt32 i_value,List<UInt32> i_db)
		{
			i_db.Add(_FIO_PIN[i_ch]);
			i_db.Add(_FIO_MASK[i_ch]);
			i_db.Add(~i_mask);
			i_db.Add(i_value);
			return "EA00EA01EA02EA03DF0201DF0300";
		}
		/**
		 * 
		 @param i_dir 1 or 0
		 @private
		 */
		internal string BCF_setDirs(int i_ch,UInt32 i_mask,UInt32 i_dir,List<UInt32> i_db)
		{
			return LPC1769._BCF.setBit(_FIO_DIR[i_ch],i_mask,i_mask*i_dir,0,i_db);
		}
		public object getPin(PinId i_pin,GpioPin.Option i_opt)
		{
			return new GpioPin(this,i_pin,i_opt);
		}
		public object getPort(PinId[] i_pins,GpioPin.Option i_opt)
		{
			return new GpioPort(this,i_pins,i_opt);
		}
		//for interrigent connection
		public object getPinObject(PinId i_id,object i_opt)
		{
			return getPin(i_id,(GpioPin.Option)i_opt);
		}
		public object getPortObject(PinId[] i_id,object i_opt)
		{
			return getPort(i_id,(GpioPin.Option)i_opt);
		}		
	}
	
	public class GpioPort:MiMicClass
	{
		private class GpioPinInfo{
			public int port;
			public int bit;
			public UInt32 pin_sel;
			public GpioPinInfo(int i_port,int i_bit,UInt32 i_pin_sel)
			{
				this.port=i_port;
				this.bit=i_bit;
				this.pin_sel=i_pin_sel;
			}
		};
		private GpioPinInfo pin2GpioPinInfo(PinId i_pin)
		{
			//pinの完全な機能名を得る。(得られれば機能がある。)
			string fn=LPC1769.completePinFunctionName(i_pin,"GPIO");
			//pin名からポートとビットを得る。
			string[] a=fn.Substring(4).Split('.');
			//pin情報を構成。
			return new GpioPinInfo(Int32.Parse(a[0]),Int32.Parse(a[1]),LPC1769.getPinSelByFunctionName(i_pin,fn));
		}
		private Port _port;
		private Gpio _gpio;
		private GpioPinInfo[] _pins;
		private UInt32 _mask;
		private int _port_no;
		private static UInt32 _PINSEL_AUTO_DETECT=0x0fffffff;
		public GpioPort(Gpio i_gpio,PinId[] i_pins,GpioPin.Option i_opt)
		{
			this._gpio=i_gpio;
			//ピンセットを取得
			this._pins=new GpioPinInfo[i_pins.Length];
			//pinに変換する。
			for(int i=0;i<i_pins.Length;i++){
				this._pins[i]=pin2GpioPinInfo(i_pins[i]);
			}
			//pinが全て同じポートに所属しているか確認
			int p=this._pins[0].port;
			for(int i=1;i<this._pins.Length;i++){
				if(p!=this._pins[i].port){
					throw new MiMicException("Invalid pin combination.");
				}
			}
			//ポートの生成
			this._port=new Port(i_gpio._ref_mcu,i_pins,null);
			this._port_no=p;
			//GPIO用のマスクを生成。
			this._mask=0;
			for(int i=0;i<this._pins.Length;i++){
				this._mask|=((UInt32)0x1<<this._pins[i].bit);
			}
			//ピンオプションの生成
			GpioPin.Option opt=new GpioPin.Option(i_opt);
			if(opt.pin==null){
				opt.pin=new Pin.Option(null,null,null);
			}
			//設定が無ければ、ピンセレクタを自動に設定
			if(opt.pin.sel==null){opt.pin.sel=_PINSEL_AUTO_DETECT;}
			//ピンオプションの設定
			this.setOpt(opt);
		}
		public void setOpt(GpioPin.Option i_opt)
		{
			List<UInt32> db=new List<UInt32>();
			//BCの生成
			string bc="";
			//dir設定
			if(i_opt.dir!=null){
				bc+=this._gpio.BCF_setDirs(this._port_no,this._mask,(UInt32)i_opt.dir,db);
			}
			//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==_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);
				}
				bc+=this._port.BCF_setOpts(optset,db);
			}
			//portの設定
			this._gpio._ref_mcu.callMiMicWithCheck(bc+LPC1769._BCF.END,db);
			return;
		}
		//
		//setValue
		//
		
		private string BCF_setValue(UInt32 i_val,List<UInt32> i_db)
		{
			UInt32 v=0;
			int l=this._pins.Length;
			for(int i=0;i<l;i++){
				//セットするbit値を得る。
				UInt32 sv=((i_val>>i)&0x01);
				//値のセット
				v|=(sv<<this._pins[i].bit);
			}
			//値をセット
			return this._gpio.BCF_setValues(this._port_no,this._mask,v,i_db);
		}
		public void setValue(UInt32 i_val)
		{
			List<UInt32> db=new List<UInt32>();
			string bc=this.BCF_setValue(i_val,db);
			this._gpio._ref_mcu.callMiMicWithCheck(bc+LPC1769._BCF.END,db);
		}
		public NBResult setValueNB(UInt32 i_val)
		{			
			List<UInt32> db=new List<UInt32>();
			string bc=this.BCF_setValue(i_val,db);
			return new NBResult(this._gpio._ref_mcu.callMiMicNB(bc+LPCXpresso1769._BCF.END,db));
		}
		
		//
		//getValue
		//
		private class NBgetValueResult:NBUIntResult
		{
			private GpioPort _parent;
			public NBgetValueResult(GpioPort i_parent,MiMicRemoteMcuInterface.NBMiMicResult i_result):base(i_result)
			{
				this._parent=i_parent;
				return;
			}
			public override UInt32 getValue()
			{
				if(!this.isDone){
					throw new MiMicException();
				}
				return this._parent.PARSE_getValue(this.result);
			}
		}
		
		protected string BCF_getValue(List<UInt32> i_db)
		{
			return this._gpio.BCF_getValues(this._port_no,this._mask,i_db);
		}
		private UInt32 PARSE_getValue(MiMicRemoteMcuInterface.MiMicResult i_result)
		{
			//値の再構成
			UInt32 retval=i_result.stream[0];
			UInt32 v=0;
			for(int i=this._pins.Length-1;i>=0;i--){
				//セットするbit値を得る。
				UInt32 sv=((retval>>this._pins[i].bit)&0x1);
				//値のセット(pinArrayの並びとビット並びが同じになるようにする)
				v=(v<<1)|sv;
			}
			return v;
		}
		
		public UInt32 getValue()
		{
			List<UInt32> db=new List<UInt32>();
			string bc=this.BCF_getValue(db);
			return this.PARSE_getValue(this._gpio._ref_mcu.callMiMicWithCheck(bc+LPC1769._BCF.END,db));
		}
		public NBUIntResult getValueNB()
		{			
			List<UInt32> db=new List<UInt32>();
			string bc=this.BCF_getValue(db);
			return new NBgetValueResult(this,this._gpio._ref_mcu.callMiMicNB(bc+LPC1769._BCF.END,db));
		}
		
		//
		//outPatt
		//
		protected string BCF_outPatt(UInt32[] i_val_array,List<UInt32> i_db)
		{
			string bc="";
			for(int i2=0;i2<i_val_array.Length;i2++){
				UInt32 v=0;
				int l=this._pins.Length;
				for(int i=0;i<l;i++){
					//セットするbit値を得る。
					UInt32 sv=((i_val_array[i2]>>i)&0x01);
					//値のセット(pinArrayの並びと最下位ビットの位置が同じになるように反転)
					v|=(sv<<this._pins[i].bit);
				}
				//値をセット
				bc+=this._gpio.BCF_setValues(this._port_no,this._mask,v,i_db);
			}
			return bc;
		}		
		public void outPatt(UInt32[] i_val_array)
		{
			List<UInt32> db=new List<UInt32>();
			string bc=this.BCF_outPatt(i_val_array,db);
			this._gpio._ref_mcu.callMiMicWithCheck(bc+LPC1769._BCF.END,db);
		}
		public NBResult outPattNB(UInt32[] i_val_array)
		{
			List<UInt32> db=new List<UInt32>();
			string bc=this.BCF_outPatt(i_val_array,db);
			return new NBResult(this._gpio._ref_mcu.callMiMicNB(bc+LPC1769._BCF.END,db));
		}
	}
	


	public class GpioPin
	{
		public class Option
		{
			public UInt32? dir;
			public Pin.Option pin=null;
			public Option(Option s)
			{
				if(s!=null){
					this.dir=s.dir;
					this.pin=new Pin.Option(s.pin);
				}
			}
			public Option(UInt32? i_dir,Pin.Option i_pin)
			{
				this.dir=i_dir;
				this.pin=i_pin;
			}
		}
		internal GpioPort _gport;
		public GpioPin(Gpio i_gpio,PinId i_pin,Option i_opt)
		{
			//1ピンのポート
			this._gport=new GpioPort(i_gpio,new PinId[]{i_pin},i_opt);
		}
		public void setOpt(Option i_opt)
		{
			this._gport.setOpt(i_opt);
		}
		public void setValue(UInt32 i_val)
		{
			this._gport.setValue(i_val);
		}
		public NBResult setValueNB(UInt32 i_val)
		{
			return this._gport.setValueNB(i_val);
		}
		
		public UInt32 getValue()
		{
			return this._gport.getValue();
		}
		public NBUIntResult getValueNB()
		{
			return this._gport.getValueNB();
		}
		
		public void outPatt(UInt32[] i_val_array)
		{
			this._gport.outPatt(i_val_array);
		}
		public NBResult outPattNB(UInt32[] i_val_array)
		{
			return this._gport.outPattNB(i_val_array);
		}
	}
}

