import 'package:dart_periphery/dart_periphery.dart';
import 'package:logging/logging.dart';
import 'dart:io';

extension Ads1265Int on int {
  double toMicroVolt(){
    int newThis = this;
    if ((newThis >> 23) == 1) {
      newThis = this - 16777216;
    }
    // 5000000.0 microVolt = Vref; 8388608 = 2^{23} -1
    // (5000000.0 / 8388608) = 0.5960464477539062;
    return 0.5960464477539062 * newThis;
  }
  double toMilliVolt() => toMicroVolt() / 1000.0;
  double toVolt() => toMicroVolt() / 1000000.0;
  toListBoolbit() => [(this & 0x01)==0x01?true:false, (this &0x02)==0x02?true:false, (this &0x04)==0x04?true:false, (this &0x08)==0x08?true:false];
}
extension Ads1265ListBool on List<bool> {
  toNibble() => (this[0]?0x01:0x00) | (this[1]?0x02:0x00) | (this[2]?0x04:0x00) | (this[3]?0x08:0x00);
}
class Ads1256 {
  late SPI spi; //SPI
  late GPIO drdy; //GPIO
  late GPIO? rst;
  late GPIO? cs; //GPIO
  late Logger log;
  bool isCSActiveHigh;

  Ads1256({
      required int spiBus,
      required int spiChip,
      required int gpioChip,
      required int pinDrdy,
      required this.isCSActiveHigh,
      String? tag,
      int? pinReset,
      int? pinCS }){
    spi = SPI(spiBus, spiChip, SPImode.mode1, 50000);
    drdy = GPIO(pinDrdy, GPIOdirection.gpioDirIn, gpioChip);
    cs = pinCS != null ? GPIO(pinCS, GPIOdirection.gpioDirOutHigh, gpioChip): null;
    rst = pinReset != null ? GPIO(pinReset, GPIOdirection.gpioDirOutHigh, gpioChip): null;
    log = Logger("[Ads1256][$tag]");
  }

  void skipClk() => sleep(const Duration(microseconds: 1 ));

  void writeRegister(int reg, int wdata) {
    log.info('    writeRegistere() <${reg.toRadixString(16)}> <${wdata.toRadixString(16)}>');
    waitDRDY();
    spi.transfer([CMD_WREG | reg, 0, wdata], false);
    skipClk();
  }

  int readRegister(int reg) {
    waitDRDY();
    spi.transfer([CMD_RREG | reg], true);
    skipClk();
    List<int> val= spi.transfer([CMD_WAKEUP, CMD_WAKEUP], true);
    log.info('    readRegister() <${reg.toRadixString(16)}>');
    log.info('    > ${val[1].toRadixString(16)}');
    skipClk();
    return val[1];
  }

  void sendCommand(int cmd){
    log.info('    sendCommand() <${cmd.toRadixString(16)}>');
    csOn();
    waitDRDY();
    spi.transfer([cmd], false);
    skipClk();
    csOff();
  }
  int get offsetCalibration {
    csOn();
    skipClk();
    int x =  readRegister(RADD_OFC0) | readRegister(RADD_OFC1) <<8 | readRegister(RADD_OFC2) <<16;
    csOff();
   return x;
  }
  int get fullscaleCalibration {
    csOn();
    skipClk();
    int x =  readRegister(RADD_FSC0) | readRegister(RADD_FSC1) <<8 | readRegister(RADD_FSC2) <<16;
    csOff();
    return x;
  }
  void calibrateOffsetGain() => sendCommand(CMD_SELFCAL);
  void calibrateOffset() => sendCommand(CMD_SELFOCAL);
  void calibrateGain() => sendCommand(CMD_SELFGCAL);
  void calibrateSystemOffset() => sendCommand(CMD_SYSOCAL);
  void calibrateSystemGain() => sendCommand(CMD_SYSGCAL);
  void stopContinueRead() => sendCommand(CMD_SDATAC);
  void resetCommand() => sendCommand(CMD_RESET);

  void doReset(){
    waitDRDY();
    rst?.write(false);
    skipClk();
    skipClk();
    skipClk();
    rst?.write(true);
  }

  void waitDRDY() {
    while (drdy.read()){}
  }

  void csOn(){
    cs?.write(isCSActiveHigh?true:false);
    skipClk();
    skipClk();
    skipClk();
    log.info("    > Start transfer");
  }
  void csOff(){
    log.info("    > End   transfer");
    cs?.write(isCSActiveHigh?false:true);
    skipClk();
    skipClk();
    skipClk();
  }

  void begin(int drate, int gain, bool buffen) {
    log.info("begin() : <${drate.toRadixString(16)}> <${gain.toRadixString(8)}> <$buffen>");
    doReset();
    resetCommand();
    skipClk();
    skipClk();
    csOn();
    syncIt();
    skipClk();
    skipClk();
    bufferNCS = buffen;
    autoCalibrationNCS = true;
    clockOutRateNCS = CLKOUT_OFF;
    var ac = controlRegisterNCS.toRadixString(16);
    log.info("    adcon   : $ac");

    programmableGainNCS = gain;
    var st = statusNCS.toRadixString(16);
    log.info("    status  : $st");

    dataRateNCS = drate;
    var dt = dataRateNCS.toRadixString(16);
    log.info("    drate   : $dt");

    csOff();
    skipClk();
    calibrateOffsetGain();
  }

  void syncIt(){
    spi.transfer([CMD_SYNC], false);
    skipClk();
    skipClk();
    skipClk();
    spi.transfer([CMD_WAKEUP], false);
    skipClk();
  }

  int get statusNCS => readRegister(RADD_STATUS);

  set bufferNCS(bool isEnable){
    isEnable ? writeRegister(RADD_STATUS, (statusNCS | (0x01 << 1))):
    writeRegister(RADD_STATUS, (statusNCS & ~(0x01 << 1)));
  }
  set autoCalibrationNCS(bool isEnable){
    isEnable ? writeRegister(RADD_STATUS, (statusNCS | (0x01 << 2))):
      writeRegister(RADD_STATUS, (statusNCS & ~(0x01 << 2)));
  }

  int get controlRegisterNCS => readRegister(RADD_ADCON);    
  set clockOutRateNCS(int clk) => writeRegister(RADD_ADCON, (controlRegisterNCS & ~(0x03 << 5 )) & (clk << 5 ));
  set programmableGainNCS(int gain) => writeRegister(RADD_ADCON, (controlRegisterNCS & ~(0x07)) & gain);

  
  int get dataRateNCS => readRegister(RADD_DRATE);
  set dataRateNCS(int rate) => writeRegister(RADD_DRATE, rate);

  set ioDir(List<int> dir){
    log.info("    set ioDir <$dir>");
    csOn();
    int d = (dir[0] | dir[1]<<1 | dir[2]<<2 | dir[3]<<3) <<4;
    log.info("       > <${d.toRadixString(16)}>");
    writeRegister(RADD_IO, d);
    csOff();
  }
  List<bool> get dIO {
    csOn();
    int dio = readRegister(RADD_IO);
    csOff();
    log.info("    get dIO <${dio.toRadixString(16)}>");
    return dio.toListBoolbit();
  }
  set dIO(List<bool> io){
    log.info("    set dIO <$io -- ${io.toNibble().toRadixString(16)}>");
    csOn();
    writeRegister(RADD_IO, io.toNibble() );
    csOff();
    log.info("    confirm <$dIO>");
  }

  set channelNCS(int c) => writeRegister(RADD_MUX, c);

  int analogRead(int pChannel, int nChannel){
    var nc = pChannel | nChannel;
    log.info("    analogRead() <${pChannel.toRadixString(16)}> <${nChannel.toRadixString(16)}> = <${nc.toRadixString(16)}>");
    csOn();
    channelNCS =  nc;
    waitDRDY();
    spi.transfer([CMD_RDATA], false);
    skipClk();
    var d = spi.transfer([0, 0, 0], true);
    csOff();
    int v =  d[0] << 16 | d[1] << 8 | d[2];
    log.info("    > $v");
    return v;
  }

  void dispose(){
    spi.dispose(); //SPI
    drdy.dispose(); //GPIO
    cs?.dispose(); //GPIO
    rst?.dispose(); //GPIO
  }

  static const int RADD_STATUS = 0x00;
  static const int RADD_MUX = 0x01;
  static const int RADD_ADCON = 0x02;
  static const int RADD_DRATE = 0x03;
  static const int RADD_IO = 0x04;
  static const int RADD_OFC0 = 0x05;
  static const int RADD_OFC1 = 0x06;
  static const int RADD_OFC2 = 0x07;
  static const int RADD_FSC0 = 0x08;
  static const int RADD_FSC1 = 0x09;
  static const int RADD_FSC2 = 0x0A;

  static const int CMD_WAKEUP = 0x00;
  static const int CMD_RDATA = 0x01;
  static const int CMD_RDATAC = 0x03;
  static const int CMD_SDATAC = 0x0f;
  static const int CMD_RREG = 0x10;
  static const int CMD_WREG = 0x50;
  static const int CMD_SELFCAL = 0xF0;
  static const int CMD_SELFOCAL = 0xF1;
  static const int CMD_SELFGCAL = 0xF2;
  static const int CMD_SYSOCAL = 0xF3;
  static const int CMD_SYSGCAL = 0xF4;
  static const int CMD_SYNC = 0xFC;
  static const int CMD_STANDBY = 0xFD;
  static const int CMD_RESET = 0xFE;

  static const int MUXP_AIN0 = 0x00;
  static const int MUXP_AIN1 = 0x10;
  static const int MUXP_AIN2 = 0x20;
  static const int MUXP_AIN3 = 0x30;
  static const int MUXP_AIN4 = 0x40;
  static const int MUXP_AIN5 = 0x50;
  static const int MUXP_AIN6 = 0x60;
  static const int MUXP_AIN7 = 0x70;
  static const int MUXP_AINCOM = 0x80;

  static const int MUXN_AIN0 = 0x00;
  static const int MUXN_AIN1 = 0x01;
  static const int MUXN_AIN2 = 0x02;
  static const int MUXN_AIN3 = 0x03;
  static const int MUXN_AIN4 = 0x04;
  static const int MUXN_AIN5 = 0x05;
  static const int MUXN_AIN6 = 0x06;
  static const int MUXN_AIN7 = 0x07;
  static const int MUXN_AINCOM = 0x08;

  static const int GAIN_1 = 0x00;
  static const int GAIN_2 = 0x01;
  static const int GAIN_4 = 0x02;
  static const int GAIN_8 = 0x03;
  static const int GAIN_16 = 0x04;
  static const int GAIN_32 = 0x05;
  static const int GAIN_64 = 0x06;

  static const int DRATE_30000SPS = 0xF0;
  static const int DRATE_15000SPS = 0xE0;
  static const int DRATE_7500SPS = 0xD0;
  static const int DRATE_3750SPS = 0xC0;
  static const int DRATE_2000SPS = 0xB0;
  static const int DRATE_1000SPS = 0xA1;
  static const int DRATE_500SPS = 0x92;
  static const int DRATE_100SPS = 0x82;
  static const int DRATE_60SPS = 0x72;
  static const int DRATE_50SPS = 0x63;
  static const int DRATE_30SPS = 0x53;
  static const int DRATE_25SPS = 0x43;
  static const int DRATE_15SPS = 0x33;
  static const int DRATE_10SPS = 0x23;
  static const int DRATE_5SPS = 0x13;
  static const int DRATE_2_5SPS = 0x03;

  static const int CLKOUT_OFF    = 0x00;
  static const int CLKOUT_1     =  0x20;
  static const int CLKOUT_HALF   = 0x40;
  static const int CLKOUT_QUARTER = 0x60;

  static const int IO_DIR_OUT = 0x00;
  static const int IO_DIR_IN = 0x10;

}
