package devices.LEDMax7219;

import java.io.IOException;

import com.pi4j.io.spi.SpiChannel;
import com.pi4j.io.spi.SpiDevice;
import com.pi4j.io.spi.SpiFactory;
import com.pi4j.io.spi.SpiMode;

import anywheresoftware.b4a.BA;
import jGPIO.SPI.SPI_BitBang;

@BA.ShortName("LEDMax7219")
//@BA.DependsOn(values = { "pi4j-core" })
@BA.Events(values= {
		"log(msg as string)",
		"updateresult(success as boolean)"
})

public class LEDMax7219  {
	static {
		
	}
	private BA ba;
	private Object myobject;
	private String event;
	private boolean need_log_event = false;
	private boolean need_updateresult_event = false;
	
	private SPI_BitBang spi_bitbang;
	private SpiDevice spidev;
	
	private boolean is_open = false;
	
	public Graphic_Max7219 graphic;
	
	/**
	 * Initialize LEDMax7219
	 * @param callerobject : caller object
	 * @param Eventname : event name
	 */
	public void Initialize(BA bax, Object callerobject, String Eventname) {
		ba = bax;
		myobject = callerobject;
		event = Eventname.trim();
		if (ba!=null) {
			if (myobject!=null) {
				if (!event.isEmpty()) {
					need_log_event = ba.subExists(event+"_log");	
					need_updateresult_event =ba.subExists(event+"_updateresult");
				}
			}
		}
		
		// auto close kalau java program closed
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                Close();
            }
        });
        
       
	}
	
	/**
	 * Open SPI Device using Pi4J
	 * @param channel : 0 or 1
	 * @param blocks : how many led blocks 
	 * @return true if success
	 */
	public boolean Open_SpiDevice(int channel, int blocks) {
		if (is_open) {		// sebelumnya terbuka pake metode lain
			Close();		// tutup dulu, is_open jadi false
		}
		if (blocks<0) blocks = 1;		// default 1
		
		graphic = new Graphic_Max7219(blocks);
		SpiChannel channelspi = SpiChannel.getByNumber(channel);
		
		if (channelspi!=null) {
			int mode = 0;
			SpiMode modespi = SpiMode.getByNumber(mode);		// mode 0 = idle di low (0), active rising edge
			int speed = 500000;		// speed max di 10 Mhz
			if (modespi!=null) {
				try {
					spidev = SpiFactory.getInstance(channelspi, speed, modespi);
					is_open = true;
					raise_log("SpiDev opened at channel "+channel+", mode="+mode+", speed="+speed);
				} catch (IOException e) {
					raise_log("Unable to SPIFactory GetInstance, Msg : "+e.getMessage());
				}						
			} else raise_log("Invalid SPI Mode "+mode);
		} else raise_log("Invalid SPI Channel "+channel);
		return false;	
	}
	
	/**
	 * Open in SPI BitBang mode for compatibility
	 * @param pin_do : pin for Serial Data
	 * @param pin_cs : pin for Chip Select
	 * @param pin_clk : pin for Clock
	 * @param led_blocks : how many led blocks in cascaded
	 * @return true if success
	 */
	public boolean Open_BitBang(int pin_do, int pin_cs, int pin_clk, int blocks) {
		if (is_open) {		// sebelumnya terbuka pake metode lain
			Close();		// tutup dulu, is_open jadi false
		}
		if (blocks<0) blocks = 1;		// default 1
		
		graphic = new Graphic_Max7219(blocks);
		spi_bitbang = new SPI_BitBang(-1, pin_do, pin_cs, pin_clk);
		if (spi_bitbang.IsOpened()) {
			// initialize
			is_open = true;
			DecodeMode_None();
			ScanLimiter_All();
		}
		
		return spi_bitbang.IsOpened();
	}
	
	
	private void DecodeMode_None() {
		
		if (is_open) {
			
			short command = 0x900;	// decode mode = 0 --> no decode
			boolean result = WriteSPI(command);
			
			if (result)
				raise_log("DecodeMode_None success");
			else
				raise_log("DecodeMode_None failed");
			
		} else {
			raise_log("DecodeMode_None failed, no SPI opened");
		}
		
	}
	
	
	private void ScanLimiter_All() {
		if (is_open) {
			short command = 0xB07;
			boolean result = WriteSPI(command);
			
			if (result)
				raise_log("ScanLimiter_All success");
			else
				raise_log("ScanLimiter_All failed");
			
		} else {
			raise_log("ScanLimiter_All failed, no SPI opened");
		}
		
	}
	
	
	/**
	 * Set Display testing mode
	 * @param testing : if true, will turn on all LED. If false, normal operation
	 * @return true if success
	 */
	public boolean Test_Display(boolean testing) {
		short command = 0;
		if (testing) {
			command = 0xF01;	// all LED should ON
		} else {
			command = 0xF00;	// normal operation
		}
		
		if (is_open) {
			boolean result = WriteSPI(command);
			return result;
			
		} else raise_log("Test_Display failed, no SPI opened");
		return false;
	}
	
	/**
	 * Set Display Shutdown mode
	 * @param shutdown : if true, will shutdown display. If false, normal operation
	 * @return true if success
	 */
	public boolean Shutdown_Display(boolean shutdown) {
		short command = 0;
		if (shutdown) {
			command = 0xC00;		// shutdown display
		} else {
			command = 0xC01;		// normal operation
		}
		
		if (is_open) {
			
			boolean result = WriteSPI(command);
			
			return result;
		} else raise_log("Shutdown_Display failed, no SPI opened");
		return false;
	}
	
	/**
	 * Check if LEDMax7219 is opened or not
	 * @return true if opened
	 */
	public boolean getIsOpened() {
		return is_open;
	}
	
	private boolean WriteSPI(short value) {
		boolean result = true;
		if (spi_bitbang!=null) {
			spi_bitbang.setCS(true);
			result = spi_bitbang.Write16(value);
			spi_bitbang.setCS(false);
		} else if (spidev!=null) {
			try {
				spidev.write(value);
			} catch (IOException e) {
				result = false;
				raise_log("WriteSPI spidev value= "+value+", exception, Msg = "+e.getMessage());
			}
			
		} else {
			raise_log("Error WriteSPI value="+value);
			result =false;
		}
		return result;
	}
	
	/**
	 * Write Buffer to LED Max7219
	 * @return true if success
	 */
	public boolean Update_Display() {
		boolean result = true;
		if (is_open) {
			if (graphic!=null) {
				int blockcount = graphic.getBlocks();
				for(int row=0;row<8;row++) {
					for(int ii=0;ii<blockcount;ii++) {	
						LEDBlock ledblock = graphic.GetLEDBlock(ii, 0);
						WriteSPI(ledblock.GetRowValue(ii));
					}	
				}
				
			} else result = false;
		} else result = false;
		raise_updateresult(result);
		return result;
	}
	
	
	
	/**
	 * Close LEDMax7219
	 */
	public void Close() {
		if (is_open) {
			graphic.All_OFF();
			Update_Display();
			Shutdown_Display(true);
			
			if (spi_bitbang!=null) {
				spi_bitbang.Close();
				spi_bitbang = null;
			} else if (spidev!=null) {	
				spidev =  null;
			}
			is_open = false;
		}
		
		
		
		raise_log("LEDMax7219 closed");
	}
	
	private void raise_log(String msg) {
		if (need_log_event) {
			ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_log", false, new Object[] {msg});
		}
	}

	private void raise_updateresult(boolean result) {
		if (need_updateresult_event) {
			ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_updateresult", false, new Object[] {result});
		}
	}
}
