/*
 * Copyright (c) 2009 by Oleg Trifonov <otrifonow@gmail.com>
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of either the GNU General Public License version 2
 * or the GNU Lesser General Public License version 2.1, both as
 * published by the Free Software Foundation.
 */


#include "config.h"

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/iom8.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <avr/eeprom.h> 


/*
 * Прототипы
 */
//void main(void) __attribute__((noreturn));
void playWav();
void playNotes();
void playMelody(uint16_t number);
void playNote(uint8_t noteIndex, uint8_t noteDuration);


#include "mmc.h"
#include "debug.h"
#include "notes.h"
#include "default_melody.h"

/*
 * Адреса EEPROM
 */
#define EEPROM_ADDR_OFFSET				(uint32_t*)4
#define EEPROM_ADDR_COUNTER			(uint32_t*)8


/*
 * Переменные 
 */

// смещение начала блока данных на карте памяти
offset_t blockBeginOffset;

// полный размер блока данных
offset_t blockFullSize;

// количество мелодий
uint16_t melodies_count;

uint16_t timer_delta_count;				// переиод проигрываемой мелодии в тиках таймера

uint8_t volatile timer_nextSample;		// выставляется в 1 в обработчике таймера, если надо выставить следующее значение на ЦАП

uint32_t runCounter;							// счетчик запусков звонка

uint8_t notesVolume = 0xFF;				// громкость при проигрывании по нотам


ISR(TIMER1_COMPA_vect) {
	OCR1A += timer_delta_count;
	timer_nextSample = 1;
}


/**
 * Проигрывает аудиофрагмент с текущего смещения
 */
void playWav() {	
	uint32_t length;
	uint16_t sampleRate;
	uint8_t nextWaveValue;

	MSG("PlayWav() ");
	length = mmc_read_get_dword();
	sampleRate = mmc_read_get_word();
	MSG_DEC("length: ", length);
	MSG_DEC("sample rate: ", sampleRate);	

	timer_delta_count = F_CPU/sampleRate;
	timer_nextSample = 0;	
	nextWaveValue = mmc_read_get_byte();
	// конфигурируем T/C1
	TCCR1A = 0x00;
	TCCR1B = 0x01;
	OCR1A = timer_delta_count;
	TIMSK = 0x10;		// enable output compare interrupt for OCR1A
	TCNT1H = 0;
	TCNT1L = 0;
	sei();
	for ( uint32_t count = 0; ; ) {
		if ( timer_nextSample == 1 ) {
			timer_nextSample = 0;
			PORTD = nextWaveValue;
			if ( ++count >= length )
				break;
			// включаем светодиоды по уровню
			if ( nextWaveValue <= 0x80 )  {
/*
				uint8_t ampl = 0x80-nextWaveValue;
				uint8_t level;
				if ( ampl > 120 )
					level = 5;
				else if ( ampl > 100 )
					level = 4;
				else if ( ampl > 75 )
					level = 3;
				else if ( ampl > 45 )
					level = 2;
				else if ( ampl > 15 )
					level = 1;
				else
					level = 0;
				setLedsLevel(level);
*/
				setLedsLevel((0x80-nextWaveValue) / 7);
			}
			
			nextWaveValue = mmc_read_get_byte();
		}
	}
	PORTD = 0;
	cli();
}


/**
 * Проигрывает мелодию по нотам с текущего смещения
 */
void playNotes() {
	uint16_t length = mmc_read_get_word();
	for ( uint16_t i = 0; i < length; i++ ) {
		uint8_t note = mmc_read_get_byte();
		uint8_t duration = mmc_read_get_byte();
		playNote(note, duration);
	}
}

/**
 * Проигрывает мелодию по ее индексу
 */
void playMelody(uint16_t number) {
	uint32_t offset;				// смещение при чтении мелодии 
	uint32_t fragmentOffset;	// смещение текущего фрагмента
	uint8_t fragmentType;		// тип фрагмента

	MSG_DEC("PlayMelody() ", number);
	// читаем смещение мелодии из таблицы и позиционируемся
	mmc_read_seek(blockBeginOffset + 2 + number*4);
	offset = blockBeginOffset + mmc_read_get_dword();
	MSG_HEX("offset: ", offset, 4);
	mmc_read_seek(offset);

	for (;;) {
		fragmentOffset = mmc_read_get_dword();
		if ( fragmentOffset == 0 ) {
			MSG("end of melody");
			break;
		}
		fragmentOffset += blockBeginOffset;
		MSG_HEX("fragment offset: ", fragmentOffset, 4);
		offset += 4;
		
		// переходим к фрагменту
		mmc_read_seek(fragmentOffset);
		fragmentType = mmc_read_get_byte();
		if ( fragmentType == 1 )
			playWav();
		else if ( fragmentType == 2 ) {
			playNotes();
		} else {
			MSG_DEC("!!! ERROR! invalid fragment type: ", fragmentType);
		}
		
		// возвращаемся к мелодии
		mmc_read_seek(offset);
	}
}

/**
 * Проигрывание ноты. 
 *
 * \param[in] noteIndex индекс ноты в массиве нот.
 * \param[in] noteDuration длительность звучания ноты, выраженная в интервалах по 1/32сек (noteDuration = 32 соответствует 1 сек)
 */
void playNote(uint8_t noteIndex, uint8_t noteDuration) {
	uint32_t duration_us;		// время звучания ноты в микросекандах
	uint8_t max;					// если 0, то в данный момент напряжение будет сброшено в 0, если 1 - то установлено на максимум
	uint16_t note_freq;			// частота этой ноты, Гц
	uint16_t timer_t_us;			// количество микросекунд, через которые вызывается обработчик таймера в цикле

	// если это не нота, а спецкоманда
	if ( noteIndex == 0xFF ) {		// смена громкости
		notesVolume = noteDuration;
		return;
	}

	duration_us = 31250L*noteDuration;
	//duration_us = 25000L*noteDuration;			// время звучания ноты в микросекандах
	max = 1;				// если 0, то в данный момент напряжение будет сброшено в 0, если 1 - то установлено на максимум
	note_freq = pgm_read_word(&notes[noteIndex]);
	timer_t_us = 1000000/(note_freq*2);				// количество микросекунд, через которые вызывается обработчик таймера в цикле
	timer_delta_count = F_CPU/8/(note_freq*2);
	timer_nextSample = 0;

	// в зависимости от ноты зажигаем светодиод
	if ( noteIndex == 0 )
		setLeds(0);
	else
		setLeds(pgm_read_byte(&notes_leds[(noteIndex-1) % 12]));


	// конфигурируем T/C1
	TCCR1A = 0x00;
	TCCR1B = 0x02;
	OCR1A = timer_delta_count;
	TIMSK = 0x10;		// enable output compare interrupt for OCR1A
	TCNT1H = 0;
	TCNT1L = 0;
	sei();
	for (uint32_t duration = 0; ; ) {
		if ( timer_nextSample == 1 ) {
			timer_nextSample = 0;
			if ( noteIndex != 0 ) {			// 0я нота - пауза
				if ( max ) {
					PORTD = notesVolume;
					max = 0;
				} else {
					PORTD = 0;
					max = 1;
				}
			}
			duration += timer_t_us;
			if ( duration >= duration_us )
				break;
		}
	}
	PORTD = 0;
	cli();
}


/**
 * Возвращает true, если нажата кнопка предотвращения зацикливания мелодий
 */
inline uint8_t isCounterStoped() {
	// конфигурируем пин на чтение
	STOP_DDR  &= ~(1 << STOP_PNUM);
	// выставляем "1"
	STOP_PORT |= (1 << STOP_PNUM);
	if ( (STOP_PIN & (1<<STOP_PNUM)) ) {
		// восстанавливаем пин
		STOP_PORT &= ~(1<<STOP_PNUM);
		return 0;
	} else
		return 1;
}


/**
 * Включает реле для самоблокирования кнопки
 */
inline void releOn() {
	// конфигурируем пин на вывод
	STOP_DDR |= (1 << RELE_PNUM);
	// выставляем "1"
	RELE_PORT |= (1 << RELE_PNUM);
}



/**
 * Выключает реле для отключения питания
 */
inline void releOff() {
	RELE_PORT &= ~(1 << RELE_PNUM);
}


/**
 * Иннициализация светодиодов
 */
inline void initLeds() {
	// конфигурируем пины на вывод
	LED_DDR |= (1 << LED_1) | (1 << LED_2) | (1 << LED_3) | (1 << LED_4) | (1 << LED_5);
}


/**
 * Включает/выключает светодиод
 *
 * \param[in] led идентификатор светодиода (LED_x)
 * \param[in] on 1-зажечь светодиод, 2-погасить
 */
inline void setLed(uint8_t led, uint8_t on) {
	if ( on )
		LED_PORT |= (1 << led);
	else
		LED_PORT &= ~(1 << led);
}



/**
 * Высвечивает все светодиоды до заданного уровня
 *
 * \param[in] level уровень от 0 до 5, при 0 все погашено, при 1, светится 1й светодиод, при 2 - два первые, .. при 5 - все
 */
inline void setLedsLevel(uint8_t level) {	
	for ( uint8_t i = LED_1; i <= LED_5; i++ ) {
		setLed(i, i-LED_1+1 <= level);
	}
}



/**
 * Зажигает те светодиоды, для которых установлен бит в маске
 *
 * \param[in] mask маска, для установленых первых 5 бит которой будут включены светодиоды
 */
inline void setLeds(uint8_t mask) {
	uint8_t to_port = LED_PORT;
	to_port &= 0b11100000;
	to_port |= mask;
	LED_PORT = to_port;
}



void main(void) {
	uint8_t baseOffsetNotSaved = 1;			// если этот флаг установлен, то надо сохранить смещение в EEPROM

	// Иннициализация
	releOn();
	initLeds();

	MSG("Starting...");

	// на всякий случай делаем задержку при включении
	playNote(0, 8);

	uart_init();
	if ( mmc_init() != 0 ) {
		playDefaultMelody();
		MSG("QUIT");
		return;
	}
	mmc_read_init();

	// иннициализация портов
	DDRD = 0xFF;

	// читаем данные из EEPROM
	blockBeginOffset = eeprom_read_dword(EEPROM_ADDR_OFFSET);
	runCounter = eeprom_read_dword(EEPROM_ADDR_COUNTER);
	MSG_HEX("EEPROM offset: ", blockBeginOffset, 4);
	MSG_DEC("EEPROM counter: ", runCounter);

	// если удалось прочитать смещение, то проверяем сигнатуру
	if ( blockBeginOffset != 0xFFFFFFFF ) {
		// пробуем проверить сигнатуру
		MSG("verify signature");
		mmc_read_seek(blockBeginOffset);
		if ( mmc_read_get_dword() == SIGNATURE )
			baseOffsetNotSaved = 0;
		else {
			MSG("WRONG SIGNATURE!");
		}
	}

	// если смещение из EEPROM извлечь не удалось
	if ( baseOffsetNotSaved == 1 ) {
		// Поиск сигнатуры на карте
		mmc_read_seek(0);
		for ( blockBeginOffset = 0; ; blockBeginOffset += 4 )
			if ( mmc_read_get_dword() == SIGNATURE ) {
				// сохраняем смещение в EEPROM
				eeprom_write_dword(EEPROM_ADDR_OFFSET, blockBeginOffset);
				MSG_HEX("EEPROM save offset: ", blockBeginOffset, 4);
				break;
			}
	}
	blockBeginOffset += 4;

	// чтение длины блока данных и количества мелодий
	mmc_read_seek(blockBeginOffset);
	blockFullSize = mmc_read_get_dword();
	blockBeginOffset += 4;
	melodies_count = mmc_read_get_word();
	MSG_HEX("data offset: ", blockBeginOffset, 4);
	MSG_DEC("size: ", blockFullSize);
	MSG_DEC("sounds count: ", melodies_count);
	

//	for (uint8_t i = 0; i < melodies_count; i++)
//		playMelody(i);	
	playMelody(runCounter % melodies_count);

	// Завершение работы
	mmc_read_done();

	// если не нажата кнопка остановки смены мелодий, инкрементируем счетчик и сохраняем его
	MSG_DEC("Stopped pressed: ", isCounterStoped());
	if ( isCounterStoped() == 0 ) {
		eeprom_write_dword(EEPROM_ADDR_COUNTER, runCounter+1);
	}


	MSG("QUIT");
	// на всякий случай делаем задержку перед отключением
	playNote(0, 64);
	releOff();
}
