#include "Core.h"
#include "Config.h"
#include "Flash.h"
#include "MainUnit.h"
#include "Gps.h"
#include "Network.h"
#include "Rtc.h"
#include "Alerts.h"
#include "Logging.h"

#define SMS_BUFFER_SIZE		140
#define NO_ALERTS_NOTIFIED	0

using namespace utils;

namespace core {
	#define CURRENT_LOGGER "core"

	uint16_t sleepTime = SLEEP_TIME_SECONDS;
	uint8_t stoppedInARow = SLEEP_STOPPED_THRESHOLD - 1;
	TRACKER_MOVING_STATE movingState = TRACKER_MOVING_STATE::MOVING;

	namespace details {

		void appendToSmsBuffer(char * buffer, const char * fmt, ...) {
			va_list args;
			va_start(args, fmt);

			size_t bufferLeft = SMS_BUFFER_SIZE - strlen(buffer);
			char * p = buffer + strlen(buffer);
			vsnprintf_P(p, bufferLeft, fmt, args);

			va_end(args);
		}

	}

	void main() {
		bool acquired = false;
		PositionEntryMetadata metadata;

		if(movingState >= TRACKER_MOVING_STATE::ABOUT_TO_STOP) {
			//forcing when the tracker is about to stop (which should result in STOPPED a few lines below)
			positions::prepareBackup(movingState == TRACKER_MOVING_STATE::ABOUT_TO_STOP);
		}

		acquired = positions::acquire(metadata);

		if (acquired) {
			positions::appendLast(metadata);

			movingState = updateSleepTime();
			gps::preserveCurrentCoordinates();
		}

		alerts::clear(metadata);
		alerts::add(notifyFailures(metadata));

		if(movingState >= TRACKER_MOVING_STATE::STOPPED) {
			positions::doBackup(movingState == TRACKER_MOVING_STATE::STOPPED); //forcing at the moment the tracker stop
		}

		if (acquired) updateRtcTime();

		hardware::sim808::powerOff(); //forcing power off, regardless of the counts and current use
		mainunit::deepSleep(sleepTime);
	}

	uint8_t notifyFailures(PositionEntryMetadata &metadata) {
		#define CURRENT_LOGGER_FUNCTION "notifyFailures"

		SIM808_NETWORK_REGISTRATION_STATE networkStatus;
		char buffer[SMS_BUFFER_SIZE];
		const __FlashStringHelper * backupFailureString = F(" Backup battery failure ?");
		bool notified = false;

		uint8_t triggered = alerts::getTriggered(metadata);
		NOTICE_FORMAT("triggered : %B", triggered);

		if (!triggered) return NO_ALERTS_NOTIFIED;

		network::powerOn();
		networkStatus = network::waitForRegistered(NETWORK_TOTAL_TIMEOUT_MS);

		if (network::isAvailable(networkStatus)) {
			strncpy_P(buffer, PSTR("Alerts !"), SMS_BUFFER_SIZE);
			if (bitRead(triggered, ALERT_BATTERY_LEVEL_1) || bitRead(triggered, ALERT_BATTERY_LEVEL_2)) {
				details::appendToSmsBuffer(buffer, PSTR("\n- Battery at %d%%."), metadata.batteryLevel);
			}

			if (bitRead(triggered, ALERT_RTC_TEMPERATURE_FAILURE)) {
				details::appendToSmsBuffer(buffer, PSTR("\n- Temperature is %dC.%S"), metadata.temperature, backupFailureString);
			}

			if (bitRead(triggered, ALERT_RTC_CLOCK_FAILURE)) {
				details::appendToSmsBuffer(buffer, PSTR("\n- RTC was stopped.%S"), backupFailureString);
			}

#if ALERTS_ON_SERIAL
			NOTICE_FORMAT("%s", buffer);
			notified = true;
#else
			notified = network::sendSms(buffer);
#endif
			if (!notified) NOTICE_MSG("SMS not sent !");
		}

		network::powerOff();
		return notified ? triggered : NO_ALERTS_NOTIFIED; //If not notified, the alerts state should not be persisted (so we can retry to notify them)
	}

	void updateRtcTime() {
		tmElements_t time;
		gps::getTime(time);
		rtc::setTime(time);
	}

	TRACKER_MOVING_STATE updateSleepTime() {
		#define CURRENT_LOGGER_FUNCTION "updateSleepTime"

		TRACKER_MOVING_STATE state = TRACKER_MOVING_STATE::MOVING;
		uint8_t velocity = gps::getVelocity();

		sleepTime = mapSleepTime(velocity);

		if (velocity < SLEEP_TIMING_MIN_MOVING_VELOCITY) {
			float distance = gps::getDistanceFromPrevious(); //did we missed positions because we were sleeping ?
			if (distance > GPS_MISSED_POSITION_GAP_KM) stoppedInARow = 0;
			else stoppedInARow = min(stoppedInARow + 1, SLEEP_STOPPED_THRESHOLD + 1); //avoid overflow on REALLY long stops

			if (stoppedInARow < SLEEP_STOPPED_THRESHOLD) {
				sleepTime = SLEEP_PAUSING_TIME_SECONDS;
				state = stoppedInARow == SLEEP_STOPPED_THRESHOLD - 1 ?
					TRACKER_MOVING_STATE::ABOUT_TO_STOP :
					TRACKER_MOVING_STATE::PAUSED;
			}
			else if (stoppedInARow == SLEEP_STOPPED_THRESHOLD) state = TRACKER_MOVING_STATE::STOPPED;
			else state = TRACKER_MOVING_STATE::STATIC;
		}
		else stoppedInARow = 0;

		NOTICE_FORMAT("stop %d, state %d, %dkmh => %ds", stoppedInARow, state, velocity, sleepTime);
		return state;
	}

	uint16_t mapSleepTime(uint8_t velocity) {
		#define CURRENT_LOGGER_FUNCTION "mapSleepTime"

		uint16_t result;
		uint16_t currentTime = 0xFFFF;

		if (rtc::isAccurate()) {
			tmElements_t time;
			rtc::getTime(time);

			currentTime = SLEEP_TIMING_TIME(time.hour, time.minute);
		}

		for (uint8_t i = flash::getArraySize(config::defaultSleepTimings); i--;) {
			sleepTimings_t timing;
			flash::read(&config::defaultSleepTimings[i], timing);

			if (velocity < timing.speed) continue;
			if (currentTime != 0xFFFF && (currentTime < timing.timeMin || currentTime > timing.timeMax)) continue;

			result = timing.seconds;
			break;

		}

		VERBOSE_FORMAT("%d,%d", velocity, result);
		return result;
	}
}