/*
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */ 

#include <avr/io.h>
#include <avr/interrupt.h>

#define F_CPU 1000000 // Das steht hier nur zur Information

// Im Folgenden werden die Pins und Ports definiert
// SGND  - geschaltete Masse am Display pro Spalte
// SEG   - geschaltete Spannung fuer die einzelnen Segmente
// SOUND - Der Trigger des Soundprozessors
#define PORT_SGND   PORTD
#define DDR_SGND    DDRD
#define PORT_SEG1   PORTC
#define DDR_SEG1    DDRC
#define PORT_SEG2   PORTB
#define DDR_SEG2    DDRB
#define PORT_SOUND  PORTB
#define DDR_SOUND   DDRB
#define SOUNDPIN    7

// GOALSW - GoalSwitch
#define PORT_GOALSW PORTD
#define DDR_GOALSW  DDRD
#define PIN_GOALSW  PIND
#define RESETKEY    5 // Reset-Knopf
#define TEAM1KEY    6 // Lichtschranke 1
#define TEAM2KEY    7 // Lichtschranke 2

// Diese Konstanten werden genutzt um den Modus
// der Anzeige zu setzen und zu identifizieren
#define DISPLAY_BLANK 0
#define DISPLAY_SCORE 1
#define DISPLAY_YEAH  2
#define DISPLAY_HELO  3

// Die Anzahl der 10ms, nach denen 
// das Display abgeschaltet werden soll
#define TIMEOUTTICKS 60000 // 10 Minuten

volatile uint8_t countTimer=0;
volatile uint32_t noActivityTicks=0;
volatile uint8_t lastGoalSide;
volatile uint8_t gndPosition;
volatile uint8_t displayMode;
volatile uint8_t score[2];
static uint8_t numbers[20] = 
  { 119, 18, 107, 91, 30, 93, 125, 19, 127, 95, // 0 bis 9
    62, 63, 109, 94, 0, // YEAH
    119, 100, 109, 62// HELO
    //100, 63, 119, 125, 0 // GOAL
  }; 

inline void switchGND();
inline void buildNumber();
inline void sleep(uint8_t t);
inline void displayBitMask(uint8_t bitMask);
inline uint8_t isPressed(uint8_t key);
inline void launchGoalCelebration(uint8_t playerSide);

// Bei 1MHz Grundtakt läuft Timer0 alle 256µs über.
// Um auf rund 10ms zu kommen, wird die Zeitzaehlvariable
// nur jedes 39. Mal inkrementiert.
SIGNAL (SIG_OVERFLOW0){
  static unsigned char count_ovl0;
  unsigned char ovl0 = count_ovl0+1;

  if (ovl0 >= 39) { //10ms bei 39
    ovl0 = 0;
    ++countTimer;
    if (++noActivityTicks > TIMEOUTTICKS){
      // Es ist laenger nichts passiert, also 
      // Display abschalten
      noActivityTicks=0;
      displayMode=DISPLAY_BLANK;
      // Punktstaende auf null setzen
      score[0]=score[1]=0;
    }
  }

  // Die Zustaende der hinteren fuenf Bits 
  // auf null setzen (Masse auf alle Spalten
  // legen)
  PORT_SGND &= ~31; // 0b11100000

  // erst die Pinzustaende definieren
  // und DANN der entsprechenden Spalte
  // Masse geben, damit nichts flimmert
  buildNumber(); // Zahl darstellen
  switchGND();  // Masse um eine Stelle weiterreichen

  count_ovl0 = ovl0;
}

inline void switchGND(){
  // Masse-Bit weiter nach vorne schieben
  PORT_SGND |= 1 << gndPosition;
  // die Masse-Bits gehen nur von 0 bis 4
  // Also muss ab der fuenften Verschiebung 
  // zurueck auf null gesetzt werden
  if (++gndPosition > 4)
    gndPosition=0;
}

inline void buildNumber(){
  // Je nach Displaymodus eine andere Ansteuer-Logik
  // verfolgen
  //
  // Die Masse-Bit-Position (gndPosition)
  // sagt uns, welche Zahl gerade angesteuert werden muss.
  // Die beiden Stellen der beiden Teams sind jeweils Bit
  // 0 und 1, sowie 2 und 3. Der Doppelpunkt ist die Nummer 4.
  switch(displayMode){
  case DISPLAY_SCORE:  // Punktestand anzeigen
    switch(gndPosition){
    case 0:
      // EinserStelle des Punktstandes
      displayBitMask(numbers[score[0] %10]);
      break;
    case 1:
      // ZehnerStelle des Punktstandes
      // Abrundung erfolgt automatisch, 
      // da in Integer gespeichert
      displayBitMask(numbers[score[0] /10]);
      break;
    case 2:
      displayBitMask(numbers[score[1] %10]);
      break;
    case 3:
      displayBitMask(numbers[score[1] /10]);
      break;
    case 4:
      // Einfach nur den Doppelpunkt darstellen.
      // 0b00000101
      displayBitMask(3);
      break;
    }
    break;
  case DISPLAY_YEAH: // Nach Torschuss "YEAH" blinken lassen
    if ((countTimer %30) < 15) // 150ms an, 150ms aus
      // Masse-Bit-Nummer + Offset von 10 ergibt den richtigen Buchstaben
      displayBitMask(numbers[gndPosition+10]);
    else
      // Nichts anzeigen, wenn der Timer zwischen den An-Phasen ist
      displayBitMask(0);
    break;
  case DISPLAY_HELO: // "HELO" anzeigen nachdem der Strom eingesteckt wurde
    // Masse-Bit-Nummer + Offset von 15 ergibt den richtigen Buchstaben
    displayBitMask(numbers[gndPosition+15]);
    break;
  default: // Nichts anzeigen. Das ist bei Modus DISPLAY_BLANK der Fall
    displayBitMask(0);
  }
}

// t*10ms lang nichts tun 
inline void sleep(uint8_t t){
  countTimer=0;
  while (countTimer < t);
}

// Eine gegebene BitMaske auf den LED-Segmenten darstellen
inline void displayBitMask(uint8_t bitMask){
  // Da auf dem Microcontroller keine 7 Pins auf
  // einem Port direkt aufeinander frei waren,
  // wird die Bitmaske hier auf zwei Ports verteilt
  PORT_SEG1 &= ~(63); // 0b11000000
  PORT_SEG1 |= (bitMask & 63);
  PORT_SEG2 &= ~1;
  PORT_SEG2 |= (bitMask >> 6) & 1;
}

// Pruefung des Zustandes eines Schalters
// Funktioniert auch bestens bei den
// Lichtschranken
inline uint8_t isPressed(uint8_t key){
  if (!(PIN_GOALSW & (1 << key))){
    sleep(5);
    // Wenn der Zustand nach 50ms immer noch
    // derselbe ist, dann kann man den Schalter
    // als gedrueckt/die Lichtschranke als
    // unterbrochen betrachten
    if (!(PIN_GOALSW & (1 << key)))
      return 1;
  }
  return 0;
}

// Wenn ein Tor geschossen wurde, kann mit dieser Funktion
// der Displaymodus auf DISPLAY_YEAH gestellt und der
// externe Soundprozessor getriggert werden
inline void launchGoalCelebration(uint8_t playerSide){
  noActivityTicks=0; // Aktivitaetszaehler zuruecksetzen
  displayMode=DISPLAY_YEAH; // Display auf YEAH stellen
  ++score[playerSide]; // Torstand inkrementieren
  lastGoalSide=playerSide; // Dies ist fuer den Resetknopf wichtig
  // Nur eine Spannungsflanke an den Soundprozessor senden
  PORT_SOUND |= (1 << SOUNDPIN);
  sleep(1);
  PORT_SOUND &= ~(1 << SOUNDPIN);
  sleep(200);
  // Nach 2 Sekunden wieder den Punktstand anzeigen
  displayMode=DISPLAY_SCORE;
}

void ioinit() {
  // PD0 bis PD4 werden für die geschaltete Masse verwendet
  DDR_SGND |= 31;  // 0b00011111
  DDR_SEG1 |= 127; // 0b00111111
  DDR_SEG2 |= 1;
  
  // Sound-Pin initialisieren
  DDR_SOUND |= (1 << SOUNDPIN);
  PORT_SOUND &= ~(1 << SOUNDPIN);
  
  // PD6 und PD7 auf Eingang stellen und Pull-Up aktivieren
  DDR_GOALSW &= ~((1 << TEAM1KEY) | (1 << TEAM2KEY)) | (1 << RESETKEY);
  PORT_GOALSW |= (1 << TEAM1KEY) | (1 << TEAM2KEY) | (1 << RESETKEY);

  /* Timer0 ohne Prescaler starten */
  TCCR0 = 1 << CS00;
  /* Timer0-Overflow-Interrupt aktivieren */
  TIMSK |= (1 << TOIE0);
  
  sei(); // Interrupts zulassen
}

int main() {
  ioinit();

  // Zur Begrueszung nach dem Einschalten HELO anzeigen
  displayMode=DISPLAY_HELO;
  sleep(255);
  displayMode=DISPLAY_SCORE;

  while (1) {
    if (isPressed(TEAM1KEY)){ // Torschuss bei Team1
      launchGoalCelebration(0);
    }
    if (isPressed(TEAM2KEY)){ // Torschuss bei Team2
      launchGoalCelebration(1);
    }
    if (isPressed(RESETKEY)){ // Resetknopf wurde gedrueck
      if (score[lastGoalSide]>0) 
        --score[lastGoalSide]; // Letztes Tor annulieren
      sleep(100);
      if (isPressed(RESETKEY)){
        // Wenn der Knopf lange gedrueckt wird,
        // dann kompletten Spielstand zuruecksetzen
        score[0]=score[1]=0;
      }
    }
  }
}


