/*
  Exemplo de um painel de senhas para atendimento utilizando
  barramento I2C por software com SCL compartilhado e UMA Franzininho DIY.
  by Anderson Costa

  Inspirado nos exemplos da comunidade Wokwi
  https://wokwi.com/arduino/projects/307841659182252608 by Koepel
  https://wokwi.com/arduino/projects/307849131382014528 by sutaburosu
*/

#include <SoftwareWire.h>
#include <TinyDebug.h>
#include "LiquidCrystal_I2C.h"

#define MAX_PWDS 9999 // Define a senha máxima para reset automático
#define MAX_OUTS 6    // Define o número máximo de Guichês

// Cria três barramentos I2C de software
SoftwareWire wire[] = {
  SoftwareWire(PB1, SCL),
  SoftwareWire(PB3, SCL),
  SoftwareWire(PB5, SCL),
};

// Cria uma instância em vetor para cada tela do LCD
LiquidCrystal_I2C lcd[] = {
  LiquidCrystal_I2C(&wire[0], 0x27, 16, 2),
  LiquidCrystal_I2C(&wire[0], 0x3F, 16, 2),
  LiquidCrystal_I2C(&wire[1], 0x27, 16, 2),
  LiquidCrystal_I2C(&wire[1], 0x3F, 16, 2),
  LiquidCrystal_I2C(&wire[2], 0x27, 16, 2),
  LiquidCrystal_I2C(&wire[2], 0x3F, 16, 2),
};

constexpr auto lcd_count = sizeof(lcd) / sizeof(lcd[0]);

void setup() {
  Debug.begin();
  // Inicializa os LCDs
  for (uint8_t screen = 0; screen < lcd_count; screen++) {
    lcd[screen].init();
    lcd[screen].backlight();
    lcd[screen].setCursor(0, 0);
    lcd[screen].print("Info Painel ");
    lcd[screen].print(screen + 1);
  }
}

char title[] = "SENHA     GUICHE";

byte last = 0;
byte position = MAX_OUTS;

// Define vetor de saídas
byte outputs[] = { NULL, 1, 2, 3, 4, 5, 6 };

// Define vetor das saídas que restaram (Reserva todas as saídas)
byte leftover[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};

byte firstRandom;
byte disableCount = 0;

void loop() {
  static uint16_t pos = 0;
  static uint16_t pwd = 0;
  uint8_t lscr = 0;
  uint8_t screen = (pos / 64) * 2 + ((pos / 16) & 1);
  uint8_t column = pos % 16;
  uint8_t row = (pos / 32) & 1;

  screen %= lcd_count;

  if (screen != lscr) {
    lscr = screen;
    pwd++;
  }

  // Verifica se está na linha do título
  if (row == 0) {
    lcd[screen].setCursor(column, row);
    lcd[screen].print(title[column]);
  } else {
    lcd[screen].setCursor(column, row);
    // Posição da senha
    if (column == 0)
      lcd[screen].print(pwd);
    // Posição do guichê
    if (column == 12) {
      lcd[screen].print(0);
      // Gera um indice aleatório da lista de saídas
      last = outputsRandom(position);
      lcd[screen].print(last);
      position--; // Decrementa o indice de saídas      
    }
  }

  // Reseta senha do dia
  if (pwd > MAX_PWDS) {
    pwd = 0;
  }

  pos++;

  // Verifica se ultrapassou na última posição
  if (pos > 192) {
    pos = 0;
    position = MAX_OUTS; // Inicializa o indice de saídas
    last = 0;            // Inicializa a variável do último sorteio
  }
}

byte outputsRandom(byte index)
{
  byte nr = 0;
  byte i = 0;

  do // Continua se o número for igual ao do guichê anterior
  {
    // Pega um número aleatório entre 1 e o indice informado
    nr = random(1, index + 2);
  } // Verifica se é o primeiro número sorteado
  while ((disableCount == 0) &&
         (index == (MAX_OUTS - disableCount)) &&
         (nr == firstRandom));

  // Verifica se é o primeiro número sorteado
  if (index == (MAX_OUTS - disableCount)) {
    firstRandom = nr;
  }

  // Percorre a lista de saídas
  for (byte s = 1; s <= MAX_OUTS; s++) {
    // Verifica se a saída não foi descartada
    if (outputs[s] != 0) {
      // Adiciona a saída que ainda não foi sorteada na lista
      leftover[++i] = outputs[s];
    }
  }

  // Percorre a lista de saídas
  for (byte s = 1; s <= MAX_OUTS; s++) {
    // Verifica o número que foi sorteado
    if (outputs[s] == leftover[nr]) {
      // Descarta o número sorteado da lista de saídas
      outputs[s] = 0;
    }
  }

  return nr;
}