#include "han_Parser.h"
#include "Cosem.h"


extern int SML_print(const char *, ...);
#define han_debug SML_print


Han_Parser::Han_Parser(uint16_t (dp)(uint8_t, uint8_t), uint8_t m, uint8_t *key, uint8_t *auth) {
    dispatch = dp;
    meter = m;
    memmove(encryptionKey, key, 16);
    if (auth) {
      memmove(authenticationKey, auth, 16);
    } else {
      memset(authenticationKey, 0, 16);
    }
}

Han_Parser::~Han_Parser(void) {
  if (hdlcParser) delete hdlcParser;
  if (mbusParser) delete mbusParser;
  if (gbtParser) delete gbtParser;
  if (gcmParser) delete gcmParser;
  if (llcParser) delete llcParser;
  if (dlmsParser) delete dlmsParser;
  if (dsmrParser) delete dsmrParser;
}

int Han_Parser::serial_available(void) {
  return dispatch(meter, 0);
}
int Han_Parser::serial_read(void) {
  return dispatch(meter, 1);
}

int16_t Han_Parser::serial_readBytes(uint8_t *buf, uint16_t size) {
  if (size > serial_available()) {
    size = serial_available();
  }
  for (uint16_t cnt = 0; cnt < size; cnt++) {
    buf[cnt] = serial_read();
  }
  return size;
}

bool Han_Parser::readHanPort(uint8_t **out, uint16_t *size, uint8_t flags) {

	if (!serial_available()) return false;

	// Before reading, empty serial buffer to increase chance of getting first byte of a data transfer
	if (!serialInit) {
		serial_readBytes(hanBuffer, BUF_SIZE_HAN);
		serialInit = true;
		return false;
	}

	DataParserContext ctx = {0};
	ctx.flags = flags;
	int pos = DATA_PARSE_INCOMPLETE;
	// For each byte received, check if we have a complete frame we can handle
	while (serial_available() && pos == DATA_PARSE_INCOMPLETE) {
    yield();
    // If buffer was overflowed, reset
		if (len >= BUF_SIZE_HAN) {
			serial_readBytes(hanBuffer, BUF_SIZE_HAN);
			len = 0;
			han_debug(PSTR("Buffer overflow, resetting"));
			return false;
		}
		hanBuffer[len++] = serial_read();
		ctx.length = len;
		pos = unwrapData((uint8_t *) hanBuffer, ctx);
		if(ctx.type > 0 && pos >= 0) {
			if(ctx.type == DATA_TAG_DLMS) {
				han_debug(PSTR("Received valid DLMS at %d"), pos);
			} else if(ctx.type == DATA_TAG_DSMR) {
				han_debug(PSTR("Received valid DSMR at %d"), pos);
			} else {
				// TODO: Move this so that payload is sent to MQTT
				han_debug(PSTR("Unknown tag %02X at pos %d"), ctx.type, pos);
				len = 0;
				return false;
			}
		}
	}
	if (pos == DATA_PARSE_INCOMPLETE) {
		return false;
	} else if(pos == DATA_PARSE_UNKNOWN_DATA) {
		han_debug(PSTR("Unknown data payload:"));
		len = len + serial_readBytes(hanBuffer + len, BUF_SIZE_HAN - len);
		//debugPrint(hanBuffer, 0, len);
		len = 0;
		return false;
	}

	if (pos == DATA_PARSE_INTERMEDIATE_SEGMENT) {
		len = 0;
		return false;
	} else if (pos < 0) {
		printHanReadError(pos);
		len += serial_readBytes(hanBuffer + len, BUF_SIZE_HAN - len);
		while (serial_available()) serial_read(); // Make sure it is all empty, in case we overflowed buffer above
		len = 0;
		return false;
	}

	// Data is valid, clear the rest of the buffer to avoid tainted parsing
	for (int i = pos + ctx.length; i < BUF_SIZE_HAN; i++) {
		hanBuffer[i] = 0x00;
	}

	//AmsData data;
	char* payload = ((char *) (hanBuffer)) + pos;
	if (ctx.type == DATA_TAG_DLMS) {
		han_debug(PSTR("Using application data:"));

		//if (Debug.isActive(RemoteDebug::VERBOSE)) debugPrint((byte*) payload, 0, ctx.length);

		// Rudimentary detector for L&G proprietary format
		if (payload[0] == CosemTypeStructure && payload[2] == CosemTypeArray && payload[1] == payload[3]) {
			//data = LNG(payload, meterState.getMeterType(), &meterConfig, ctx, &Debug);
		} else {
			// TODO: Split IEC6205675 into DataParserKaifa and DataParserObis. This way we can add other means of parsing, for those other proprietary formats
			//data = IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx);
		}
	} else if(ctx.type == DATA_TAG_DSMR) {
		//data = IEC6205621(payload);
	}

	*out = hanBuffer + pos;
	*size = ctx.length;
	len = 0;
	return true;
}


int16_t Han_Parser::unwrapData(uint8_t *buf, DataParserContext &context) {
	int16_t ret = 0;
	bool doRet = false;
	uint16_t end = BUF_SIZE_HAN;
	uint8_t tag = (*buf);
	uint8_t lastTag = DATA_TAG_NONE;
	while (tag != DATA_TAG_NONE) {
		int16_t curLen = context.length;
		int8_t res = 0;
		switch(tag) {
			case DATA_TAG_HDLC:
				if (hdlcParser == NULL) hdlcParser = new HDLCParser();
				res = hdlcParser->parse(buf, context);
				break;
			case DATA_TAG_MBUS:
				if (mbusParser == NULL) mbusParser =  new MBUSParser();
				res = mbusParser->parse(buf, context);
				break;
			case DATA_TAG_GBT:
				if (gbtParser == NULL) gbtParser = new GBTParser();
				res = gbtParser->parse(buf, context);
				break;
			case DATA_TAG_GCM:
				if (gcmParser == NULL) gcmParser = new GCMParser(encryptionKey, authenticationKey);
				res = gcmParser->parse(buf, context);
				break;
			case DATA_TAG_LLC:
				if (llcParser == NULL) llcParser = new LLCParser();
				res = llcParser->parse(buf, context);
				break;
			case DATA_TAG_DLMS:
				if (dlmsParser == NULL) dlmsParser = new DLMSParser();
				res = dlmsParser->parse(buf, context);
				if (res >= 0) doRet = true;
				break;
			case DATA_TAG_DSMR:
				if (dsmrParser == NULL) dsmrParser = new DSMRParser();
				res = dsmrParser->parse(buf, context, lastTag != DATA_TAG_NONE);
				if (res >= 0) doRet = true;
				break;
			default:
				han_debug(PSTR("Ended up in default case while unwrapping...(tag %02X)"), tag);
				return DATA_PARSE_UNKNOWN_DATA;
		}
		lastTag = tag;
		if (res == DATA_PARSE_INCOMPLETE) {
			return res;
		}
		if (context.length > end) return false;
		if (Debug) {
			switch(tag) {
				case DATA_TAG_HDLC:
					han_debug(PSTR("HDLC frame:"));
					break;
				case DATA_TAG_MBUS:
					han_debug(PSTR("MBUS frame:"));
					break;
				case DATA_TAG_GBT:
					han_debug(PSTR("GBT frame:"));
					break;
				case DATA_TAG_GCM:
					han_debug(PSTR("GCM frame:"));
					break;
				case DATA_TAG_LLC:
					han_debug(PSTR("LLC frame:"));
					break;
				case DATA_TAG_DLMS:
					han_debug(PSTR("DLMS frame:"));
					break;
				case DATA_TAG_DSMR:
					han_debug(PSTR("DSMR frame:"));
					break;
			}
		}
		if (res == DATA_PARSE_FINAL_SEGMENT) {
			if (tag == DATA_TAG_MBUS) {
				res = mbusParser->write(buf, context);
			}
		}

		if (res < 0) {
			return res;
		}
		buf += res;
		end -= res;
		ret += res;

		// If we are ready to return, do that
		if (doRet) {
			context.type = tag;
			return ret;
		}

		// Use start byte of new buffer position as tag for next round in loop
		tag = (*buf);
	}
	han_debug(PSTR("Got to end of unwrap method..."));
	return DATA_PARSE_UNKNOWN_DATA;
}

void Han_Parser::printHanReadError(int16_t pos) {
		switch(pos) {
			case DATA_PARSE_BOUNDRY_FLAG_MISSING:
				han_debug(PSTR("Boundry flag missing"));
				break;
			case DATA_PARSE_HEADER_CHECKSUM_ERROR:
				han_debug(PSTR("Header checksum error"));
				break;
			case DATA_PARSE_FOOTER_CHECKSUM_ERROR:
				han_debug(PSTR("Frame checksum error"));
				break;
			case DATA_PARSE_INCOMPLETE:
				han_debug(PSTR("Received frame is incomplete"));
				break;
			case GCM_AUTH_FAILED:
				han_debug(PSTR("Decrypt authentication failed"));
				break;
			case GCM_ENCRYPTION_KEY_FAILED:
				han_debug(PSTR("Setting decryption key failed"));
				break;
			case GCM_DECRYPT_FAILED:
				han_debug(PSTR("Decryption failed"));
				break;
			case MBUS_FRAME_LENGTH_NOT_EQUAL:
				han_debug(PSTR("Frame length mismatch"));
				break;
			case DATA_PARSE_INTERMEDIATE_SEGMENT:
				han_debug(PSTR("Intermediate segment received"));
				break;
			case DATA_PARSE_UNKNOWN_DATA:
				han_debug(PSTR("Unknown data format %02X"), hanBuffer[0]);
				break;
			default:
				han_debug(PSTR("Unspecified error while reading data: %d"), pos);
				break;
		}
}
