#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <sys/time.h>
#include <wiringPi.h>

#define OUT_PIN    0 // IR LED:            GPIO 17
#define IN_PIN     1 // IR RECEIVER DIODE: GPIO 18
#define POWER_SW   2 // PUSH BUTTON:       GPIO 27
#define STATUS_LED 3 // LED:               GPIO 22

#define DEBUG_LEVEL 0

#define NO_SIGNAL      0
#define HELLO_UP       1 // on  for 126 µs (msg end: 109 µs)
#define HELLO_DOWN     2 // off for 537 µs (msg end: 590 µs)
#define MSG_BEGIN_DOWN 3 // off for 726 µs
#define MSG_UP         4 // on  for  52 µs
#define MSG_0_DOWN     5 // off for 120 µs
#define MSG_1_DOWN     6 // off for 299 µs

#define SND_WAIT 3000
#define SND_HELLO_UP 126
#define SND_HELLO_DOWN 537
#define SND_MSG_DOWN 725
#define SND_MSG_UP 52
#define SND_MSG_DOWN1 299
#define SND_MSG_DOWN0 120
#define SND_MSG_END 109
#define SND_MSG_BETWEEN 620

const char* symbols[] = {
                "■", "▲", "☎", "D", "E", "F", "G", "H",
                "I", "V", "S", "L", "M", ":", "ぃ", "ぅ",
                "PO","Ké","“", "”", "・", "…", "ぁ", "ぇ",
                "ぉ", " ", " ", " ", " ", " ", " ", " ",
                "A", "B", "C", "D", "E", "F", "G", "H",
                "I", "J", "K", "L", "M", "N", "O", "P",
                "Q", "R", "S", "T", "U", "V", "W", "X",
                "Y", "Z", "(", ")", ":", ";", "[", "]",
                "a", "b", "c", "d", "e", "f", "g", "h",
                "i", "j", "k", "l", "m", "n", "o", "p",
                "q", "r", "s", "t", "u", "v", "w", "x",
                "y", "z", " ", " ", " ", " ", " ", " ",
                "Ä", "Ö", "Ü", "ä", "ö", "ü", " ", " ",
                " ", " ", " ", " ", " ", " ", " ", " ",
                "'d","'l","'m","'r","'s","'t" "'v"," ",
                " ", " ", " ", " ", " ", " ", " ", "←",
                "'", "PK","MN","-", " ", " ", "?", "!",
                ".", "&", "é", "→", "▷", "▶", "▼", "♂",
                "$", "×", ".", "/", ",", "♀", "0", "1",
                "2", "3", "4", "5", "6", "7", "8", "9"
        };

const char* items[] = {
		"BERRY",
		"PRZCUREBERRY",
		"MINT BERRY",
		"ICE BERRY",
		"BURNT BERRY",
		"PSNCUREBERRY",
		"GUARD SPEC",
		"X DEFENSE",
		"X ATTACK",
		"BITTER BERRY",
		"DIRE HIT",
		"X SPECIAL",
		"X ACCURACY",
		"EON MAIL",
		"MORPH MAIL",
		"MUSIC MAIL",
		"MIRACLEBERRY",
		"GOLD BERRY",
		"REVIVE",
		"GREAT BALL",
		"SUPER REPEL",
		"MAX REPEL",
		"ELIXIR",
		"ETHER",
		"WATER STONE",
		"FIRE STONE",
		"LEAF STONE",
		"THUNDERSTONE",
		"MAX ETHER",
		"MAX ELIXIR",
		"MAX REVIVE",
		"SCOPE LENS",
		"HP UP",
		"PP UP",
		"RARE CANDY",
		"BLUESKY MAIL",
		"MIRAGE MAIL"
	};

const char* decorations[] = {
		"JIGGLYPUFF DOLL",
		"POLIWAG DOLL",
		"DIGLETT DOLL",
		"STARYU DOLL",
		"MAGIKARP DOLL",
		"ODDISH DOLL",
		"GENGAR DOLL",
		"SHELLDER DOLL",
		"GRIMER DOLL",
		"VOLTORB DOLL",
		"CLEFAIRY POSTER",
		"JIGGLYPUFF POSTER",
		"SUPER NES",
		"WEEDLE DOLL",
		"GEODUDE DOLL",
		"MACHOP DOLL",
		"MAGNAPLANT",
		"TROPICPLANT",
		"NES",
		"NINTENDO 64",
		"BULBASAUR DOLL",
		"SQUIRTLE DOLL",
		"PINK BED",
		"POLKADOT BED",
		"RED CARPET",
		"BLUE CARPET",
		"YELLOW CARPET",
		"GREEN CARPET",
		"JUMBOPLANT",
		"VIRTUAL BOY",
		"BIG ONIX DOLL",
		"PIKACHU POSTER",
		"BIG LAPRAS DOLL",
		"SURF PIKACHU DOLL",
		"PIKACHU BED",
		"UNOWN DOLL",
		"TENTACOOL DOLL",
};


const char* pokemon[] = {
        "",
        "BULBASAUR",
        "IVYSAUR",
        "VENUSAUR",
        "CHARMANDER",
        "CHARMELEON",
        "CHARIZARD",
        "SQUIRTLE",
        "WARTORTLE",
        "BLASTOISE",
        "CATERPIE",
        "METAPOD",
        "BUTTERFREE",
        "WEEDLE",
        "KAKUNA",
        "BEEDRILL",
        "PIDGEY",
        "PIDGEOTTO",
        "PIDGEOT",
        "RATTATA",
        "RATICATE",
        "SPEAROW",
        "FEAROW",
        "EKANS",
        "ARBOK",
        "PIKACHU",
        "RAICHU",
        "SANDSHREW",
        "SANDSLASH",
        "NIDORAN♀",
        "NIDORINA",
        "NIDOQUEEN",
        "NIDORAN♂",
        "NIDORINO",
        "NIDOKING",
        "CLEFAIRY",
        "CLEFABLE",
        "VULPIX",
        "NINETALES",
        "JIGGLYPUFF",
        "WIGGLYTUFF",
        "ZUBAT",
        "GOLBAT",
        "ODDISH",
        "GLOOM",
        "VILEPLUME",
        "PARAS",
        "PARASECT",
        "VENONAT",
        "VENOMOTH",
        "DIGLETT",
        "DUGTRIO",
        "MEOWTH",
        "PERSIAN",
        "PSYDUCK",
        "GOLDUCK",
        "MANKEY",
        "PRIMEAPE",
        "GROWLITHE",
        "ARCANINE",
        "POLIWAG",
        "POLIWHIRL",
        "POLIWRATH",
        "ABRA",
        "KADABRA",
        "ALAKAZAM",
        "MACHOP",
        "MACHOKE",
        "MACHAMP",
        "BELLSPROUT",
        "WEEPINBELL",
        "VICTREEBEL",
        "TENTACOOL",
        "TENTACRUEL",
        "GEODUDE",
        "GRAVELER",
        "GOLEM",
        "PONYTA",
        "RAPIDASH",
        "SLOWPOKE",
        "SLOWBRO",
        "MAGNEMITE",
        "MAGNETON",
        "FARFETCH’D",
        "DODUO",
        "DODRIO",
        "SEEL",
        "DEWGONG",
        "GRIMER",
        "MUK",
        "SHELLDER",
        "CLOYSTER",
        "GASTLY",
        "HAUNTER",
        "GENGAR",
        "ONIX",
        "DROWZEE",
        "HYPNO",
        "KRABBY",
        "KINGLER",
        "VOLTORB",
        "ELECTRODE",
        "EXEGGCUTE",
        "EXEGGUTOR",
        "CUBONE",
        "MAROWAK",
        "HITMONLEE",
        "HITMONCHAN",
        "LICKITUNG",
        "KOFFING",
        "WEEZING",
        "RHYHORN",
        "RHYDON",
        "CHANSEY",
        "TANGELA",
        "KANGASKHAN",
        "HORSEA",
        "SEADRA",
        "GOLDEEN",
        "SEAKING",
        "STARYU",
        "STARMIE",
        "MR. MIME",
        "SCYTHER",
        "JYNX",
        "ELECTABUZZ",
        "MAGMAR",
        "PINSIR",
        "TAUROS",
        "MAGIKARP",
        "GYARADOS",
        "LAPRAS",
        "DITTO",
        "EEVEE",
        "VAPOREON",
        "JOLTEON",
        "FLAREON",
        "PORYGON",
        "OMANYTE",
        "OMASTAR",
        "KABUTO",
        "KABUTOPS",
        "AERODACTYL",
        "SNORLAX",
        "ARTICUNO",
        "ZAPDOS",
        "MOLTRES",
        "DRATINI",
        "DRAGONAIR",
        "DRAGONITE",
        "MEWTWO",
        "MEW",
        "CHIKORITA",
        "BAYLEEF",
        "MEGANIUM",
        "CYNDAQUIL",
        "QUILAVA",
        "TYPHLOSION",
        "TOTODILE",
        "CROCONAW",
        "FERALIGATR",
        "SENTRET",
        "FURRET",
        "HOOTHOOT",
        "NOCTOWL",
        "LEDYBA",
        "LEDIAN",
        "SPINARAK",
        "ARIADOS",
        "CROBAT",
        "CHINCHOU",
        "LANTURN",
        "PICHU",
        "CLEFFA",
        "IGGLYBUFF",
        "TOGEPI",
        "TOGETIC",
        "NATU",
        "XATU",
        "MAREEP",
        "FLAAFFY",
        "AMPHAROS",
        "BELLOSSOM",
        "MARILL",
        "AZUMARILL",
        "SUDOWOODO",
        "POLITOED",
        "HOPPIP",
        "SKIPLOOM",
        "JUMPLUFF",
        "AIPOM",
        "SUNKERN",
        "SUNFLORA",
        "YANMA",
        "WOOPER",
        "QUAGSIRE",
        "ESPEON",
        "UMBREON",
        "MURKROW",
        "SLOWKING",
        "MISDREAVUS",
        "UNOWN",
        "WOBBUFFET",
        "GIRAFARIG",
        "PINECO",
        "FORRETRESS",
        "DUNSPARCE",
        "GLIGAR",
        "STEELIX",
        "SNUBBULL",
        "GRANBULL",
        "QWILFISH",
        "SCIZOR",
        "SHUCKLE",
        "HERACROSS",
        "SNEASEL",
        "TEDDIURSA",
        "URSARING",
        "SLUGMA",
        "MAGCARGO",
        "SWINUB",
        "PILOSWINE",
        "CORSOLA",
        "REMORAID",
        "OCTILLERY",
        "DELIBIRD",
        "MANTINE",
        "SKARMORY",
        "HOUNDOUR",
        "HOUNDOOM",
        "KINGDRA",
        "PHANPY",
        "DONPHAN",
        "PORYGON2",
        "STANTLER",
        "SMEARGLE",
        "TYROGUE",
        "HITMONTOP",
        "SMOOCHUM",
        "ELEKID",
        "MAGBY",
        "MILTANK",
        "BLISSEY",
        "RAIKOU",
        "ENTEI",
        "SUICUNE",
        "LARVITAR",
        "PUPITAR",
        "TYRANITAR",
        "LUGIA",
        "HO-OH",
        "CELEBI",
        "????????",
        "????????",
        "????????",
        "????????"
};

const char* moves[] = {
        "",
        "POUND",
        "KARATE CHOP",
        "DOUBLE SLAP",
        "COMET PUNCH",
        "MEGA PUNCH",
        "PAY DAY",
        "FIRE PUNCH",
        "ICE PUNCH",
        "THUNDER PUNCH",
        "SCRATCH",
        "VICE GRIP",
        "GUILLOTINE",
        "RAZOR WIND",
        "SWORDS DANCE",
        "CUT",
        "GUST",
        "WING ATTACK",
        "WHIRLWIND",
        "FLY",
        "BIND",
        "SLAM",
        "VINE WHIP",
        "STOMP",
        "DOUBLE KICK",
        "MEGA KICK",
        "JUMP KICK",
        "ROLLING KICK",
        "SAND ATTACK",
        "HEADBUTT",
        "HORN ATTACK",
        "FURY ATTACK",
        "HORN DRILL",
        "TACKLE",
        "BODY SLAM",
        "WRAP",
        "TAKE DOWN",
        "THRASH",
        "DOUBLE-EDGE",
        "TAIL WHIP",
        "POISON STING",
        "TWINEEDLE",
        "PIN MISSILE",
        "LEER",
        "BITE",
        "GROWL",
        "ROAR",
        "SING",
        "SUPERSONIC",
        "SONIC BOOM",
        "DISABLE",
        "ACID",
        "EMBER",
        "FLAMETHROWER",
        "MIST",
        "WATER GUN",
        "HYDRO PUMP",
        "SURF",
        "ICE BEAM",
        "BLIZZARD",
        "PSYBEAM",
        "BUBBLE BEAM",
        "AURORA BEAM",
        "HYPER BEAM",
        "PECK",
        "DRILL PECK",
        "SUBMISSION",
        "LOW KICK",
        "COUNTER",
        "SEISMIC TOSS",
        "STRENGTH",
        "ABSORB",
        "MEGA DRAIN",
        "LEECH SEED",
        "GROWTH",
        "RAZOR LEAF",
        "SOLAR BEAM",
        "POISON POWDER",
        "STUN SPORE",
        "SLEEP POWDER",
        "PETAL DANCE",
        "STRING SHOT",
        "DRAGON RAGE",
        "FIRE SPIN",
        "THUNDER SHOCK",
        "THUNDERBOLT",
        "THUNDER WAVE",
        "THUNDER",
        "ROCK THROW",
        "EARTHQUAKE",
        "FISSURE",
        "DIG",
        "TOXIC",
        "CONFUSION",
        "PSYCHIC",
        "HYPNOSIS",
        "MEDITATE",
        "AGILITY",
        "QUICK ATTACK",
        "RAGE",
        "TELEPORT",
        "NIGHT SHADE",
        "MIMIC",
        "SCREECH",
        "DOUBLE TEAM",
        "RECOVER",
        "HARDEN",
        "MINIMIZE",
        "SMOKESCREEN",
        "CONFUSE RAY",
        "WITHDRAW",
        "DEFENSE CURL",
        "BARRIER",
        "LIGHT SCREEN",
        "HAZE",
        "REFLECT",
        "FOCUS ENERGY",
        "BIDE",
        "METRONOME",
        "MIRROR MOVE",
        "SELF-DESTRUCT",
        "EGG BOMB",
        "LICK",
        "SMOG",
        "SLUDGE",
        "BONE CLUB",
        "FIRE BLAST",
        "WATERFALL",
        "CLAMP",
        "SWIFT",
        "SKULL BASH",
        "SPIKE CANNON",
        "CONSTRICT",
        "AMNESIA",
        "KINESIS",
        "SOFT-BOILED",
        "HIGH JUMP KICK",
        "GLARE",
        "DREAM EATER",
        "POISON GAS",
        "BARRAGE",
        "LEECH LIFE",
        "LOVELY KISS",
        "SKY ATTACK",
        "TRANSFORM",
        "BUBBLE",
        "DIZZY PUNCH",
        "SPORE",
        "FLASH",
        "PSYWAVE",
        "SPLASH",
        "ACID ARMOR",
        "CRABHAMMER",
        "EXPLOSION",
        "FURY SWIPES",
        "BONEMERANG",
        "REST",
        "ROCK SLIDE",
        "HYPER FANG",
        "SHARPEN",
        "CONVERSION",
        "TRI ATTACK",
        "SUPER FANG",
        "SLASH",
        "SUBSTITUTE",
        "STRUGGLE",
        "SKETCH",
        "TRIPLE KICK",
        "THIEF",
        "SPIDER WEB",
        "MIND READER",
        "NIGHTMARE",
        "FLAME WHEEL",
        "SNORE",
        "CURSE",
        "FLAIL",
        "CONVERSION 2",
        "AEROBLAST",
        "COTTON SPORE",
        "REVERSAL",
        "SPITE",
        "POWDER SNOW",
        "PROTECT",
        "MACH PUNCH",
        "SCARY FACE",
        "FEINT ATTACK",
        "SWEET KISS",
        "BELLY DRUM",
        "SLUDGE BOMB",
        "MUD-SLAP",
        "OCTAZOOKA",
        "SPIKES",
        "ZAP CANNON",
        "FORESIGHT",
        "DESTINY BOND",
        "PERISH SONG",
        "ICY WIND",
        "DETECT",
        "BONE RUSH",
        "LOCK-ON",
        "OUTRAGE",
        "SANDSTORM",
        "GIGA DRAIN",
        "ENDURE",
        "CHARM",
        "ROLLOUT",
        "FALSE SWIPE",
        "SWAGGER",
        "MILK DRINK",
        "SPARK",
        "FURY CUTTER",
        "STEEL WING",
        "MEAN LOOK",
        "ATTRACT",
        "SLEEP TALK",
        "HEAL BELL",
        "RETURN",
        "PRESENT",
        "FRUSTRATION",
        "SAFEGUARD",
        "PAIN SPLIT",
        "SACRED FIRE",
        "MAGNITUDE",
        "DYNAMIC PUNCH",
        "MEGAHORN",
        "DRAGON BREATH",
        "BATON PASS",
        "ENCORE",
        "PURSUIT",
        "RAPID SPIN",
        "SWEET SCENT",
        "IRON TAIL",
        "METAL CLAW",
        "VITAL THROW",
        "MORNING SUN",
        "SYNTHESIS",
        "MOONLIGHT",
        "HIDDEN POWER",
        "CROSS CHOP",
        "TWISTER",
        "RAIN DANCE",
        "SUNNY DAY",
        "CRUNCH",
        "MIRROR COAT",
        "PSYCH UP",
        "EXTREME SPEED",
        "ANCIENT POWER",
        "SHADOW BALL",
        "FUTURE SIGHT",
        "ROCK SMASH",
        "WHIRLPOOL",
        "BEAT UP",
        "????????",
        "????????",
        "????????",
        "????????"
};

void delayMicrosecondsHard(unsigned int howLong) {
	struct timeval tNow, tLong, tEnd ;

	gettimeofday (&tNow, NULL) ;
	tLong.tv_sec  = howLong / 1000000;
	tLong.tv_usec = howLong % 1000000;
	timeradd (&tNow, &tLong, &tEnd);

	while (timercmp (&tNow, &tEnd, <)) {
		gettimeofday (&tNow, NULL);
	}
}

int power_off() {
	int read = digitalRead(POWER_SW);
	return read==LOW?1:0;
}

int last_read = 0;
unsigned int last_time = 0;

int read_ir(unsigned int wait) {
	unsigned int end = micros() + wait;
	while (end > micros()) {
		int read = digitalRead(IN_PIN);
		if (read != last_read) {
			last_read = read;
			unsigned int time = micros();
			unsigned int delay = time - last_time;
			last_time = time;
			// read, delay
			if (read == LOW) {
				// received a 01 edge; 0-duration is stored in delay (in micro seconds)
#if DEBUG_LEVEL >= 2
				printf("LOW: %u µs\n",delay);
#endif
				if (delay < 40 || delay > 1400) return NO_SIGNAL;
				if (delay < 170) return MSG_0_DOWN;
				if (delay < 420) return MSG_1_DOWN;
				if (delay < 680) return HELLO_DOWN;
				return MSG_BEGIN_DOWN;
			}else{
#if DEBUG_LEVEL >= 2
				printf("HIGH: %u µs\n",delay);
#endif
				// received a 10 edge; 1-duration is stored in delay (in micro seconds)
				if (delay < 40 || delay > 320) return NO_SIGNAL; // error
				if (delay < 160) return MSG_UP;
				return HELLO_UP;
			}
		}
	}
}

int receive_hello() {
	unsigned int wait = 50000;
	unsigned int end = micros() + wait;
	while (end > micros()) {
		int val = read_ir(end - micros());
		if (val == HELLO_UP || val == MSG_UP) {
			if (read_ir(5000) != HELLO_DOWN) continue;
			val = read_ir(5000);
			if (val != HELLO_UP && val != MSG_UP) continue;
#if DEBUG_LEVEL >= 1
			printf("received hello\n");
#endif
			return 0; // success
		}
	}
	return -1; // not received
}

void send_hello() {
#if DEBUG_LEVEL >= 1
	printf("send hello\n");
#endif
	delayMicrosecondsHard(SND_WAIT);
	digitalWrite(OUT_PIN, 1);
	delayMicrosecondsHard(SND_HELLO_UP);
	digitalWrite(OUT_PIN, 0);
	delayMicrosecondsHard(SND_HELLO_DOWN);
	digitalWrite(OUT_PIN, 1);
	delayMicrosecondsHard(SND_HELLO_UP);
	digitalWrite(OUT_PIN, 0);
	delayMicrosecondsHard(100);
}

// -1: not received; 0 to bufsize: received, number of bytes returned
int receive_msg(unsigned char* buf, int bufsize) {
	unsigned int wait = 50000;
	unsigned int end = micros() + wait;
	int val;
	while (end > micros()) {
		val = read_ir(end - micros());
		if (val == NO_SIGNAL) continue;
		if (val == HELLO_DOWN || val == MSG_BEGIN_DOWN) continue;
		if (val == HELLO_UP || val == MSG_UP) break;
		return -1;
	}
	val = read_ir(5000);
	if (val != MSG_BEGIN_DOWN && val != HELLO_DOWN) return -1; // error
	int bit = 7;
	int byte = 0;
	for (int i=0;i<bufsize;i++) buf[i]=0;
	while (1) {
		int val = read_ir(5000);
		if (val == HELLO_UP) break; // end of message reached
		if (val != MSG_UP) return -1; // error
		val = read_ir(5000);
		if (val != MSG_0_DOWN && val != MSG_1_DOWN) return -1;
		// read bit
		if (byte >= bufsize) return -1; // did not fit into buffer
		if (val==MSG_1_DOWN) buf[byte] |= (1<<bit);
		bit = (bit+7)%8;
		byte += bit/7;
	}
	if (bit != 7) return -1; // last byte incomplete
#if DEBUG_LEVEL >= 1
	printf("msg: ");
	for (int i=0; i<byte; i++)
		printf("0x%02X ",buf[i]);
	printf("\n");
#endif
	return byte;
}

void send_msg(const unsigned char* msg, int length) {
#if DEBUG_LEVEL >= 1
	printf("send: ");
	for (int i=0; i<length; i++)
		printf("0x%02X ",msg[i]);
	printf("\n");
#endif
	digitalWrite(OUT_PIN, HIGH);
	delayMicrosecondsHard(SND_HELLO_UP);
	digitalWrite(OUT_PIN, LOW);
	delayMicrosecondsHard(SND_MSG_DOWN);
	for (int i=0; i<length; i++) {
		for (int b=7; b>=0; b--) {
			digitalWrite(OUT_PIN, HIGH);
			delayMicrosecondsHard(SND_MSG_UP);
			digitalWrite(OUT_PIN, LOW);
			if (msg[i] & (1<<b)) {
				// send 1
				delayMicrosecondsHard(SND_MSG_DOWN1);
			}else{
				// send 0
				delayMicrosecondsHard(SND_MSG_DOWN0);
			}
		}
	}
	digitalWrite(OUT_PIN, HIGH);
	delayMicrosecondsHard(SND_MSG_END);
	digitalWrite(OUT_PIN, LOW);
	delayMicrosecondsHard(SND_MSG_BETWEEN);
}

int receive_ack() {
	unsigned char buf = 0;
	if (receive_msg(&buf, 1) == 1) return (buf == 0x6C)?0:-1;
	return -1;
}

void send_ack() {
	delayMicrosecondsHard(2000);
	const unsigned char ACK = 0x6C;
	send_msg(&ACK, 1);
}

int receive_data(unsigned char* buf, int bufsize) {
	unsigned char header[2];
	unsigned char checksum[2];
	if (receive_msg(header, 2) != 2) return -1;
	if (header[0] != 0x5A) return -1;
	if (header[1] > bufsize) return -1; // too many data
	if (receive_msg(buf, header[1]) != header[1]) return -1;
	if (receive_msg(checksum, 2) != 2) return -1;
	int chksum = header[0] + header[1];
	for (int i=0; i<header[1]; i++) chksum += buf[i];
	if (checksum[0] != (unsigned char) (chksum & 0xFF)
			|| checksum[1] != (unsigned char) ((chksum & 0xFF00) >> 8))
		return -1;
	send_ack();
	return header[1];
}

int send_data(const unsigned char* msg, int length) {
	delayMicrosecondsHard(2000);
	if (length > 255 || length < 0) return -1;
	int chksum = 0x5A + length;
	for (int i=0; i<length; i++) chksum += msg[i];
	unsigned char checksum[] = {
			(unsigned char) (chksum & 0xFF),
			(unsigned char) ((chksum & 0xFF00) >> 8)
		};
	unsigned char header[] = {
			0x5A,
			(unsigned char) length
		};
	send_msg(header, 2);
	send_msg(msg, length);
	send_msg(checksum, 2);
	return receive_ack();
}

int exchange_payload(
		const unsigned char* send,
		int length,
		unsigned char* receive,
		int max_length,
		unsigned char* region) {
	unsigned char tmp, region_code;
	int len = 0;

	// prepare to send
	send_hello();
	if (receive_hello() == -1) return -1;
	tmp = 0x96;
	if (send_data(&tmp, 1) == -1) return -1;
	if (send_data(0, 0) == -1) return -1;

	// get region code
	if (receive_hello() == -1) return -1;
	send_hello();
	if (receive_data(&region_code, 1) == -1) return -1;
	if (region) *region = region_code;
	if (receive_data(0, 0) == -1) return -1;

	// send payload
	send_hello();
	if (receive_hello() == -1) return -1;
	if (send_data(send, length) == -1) return -1;
	if (send_data(0, 0) == -1) return -1;



	// prepare to receive
	if (receive_hello() == -1) return -1;
	send_hello();
	if (receive_data(&tmp, 1) == -1) return -1;
	if (tmp != 0x96) return -1; // error: unexpected payload received
	if (receive_data(0, 0) == -1) return -1;

	// send region code
	send_hello();
	if (receive_hello() == -1) return -1;
	if (send_data(&region_code, 1) == -1) return -1;
	if (send_data(0, 0) == -1) return -1;

	// receive payload
	if (receive_hello() == -1) return -1;
	send_hello();
	len = receive_data(receive, max_length);
	if (len == -1) return -1;
	if (receive_data(0, 0) == -1) return -1;


	// finish
	send_hello();
	if (receive_hello() == -1) return -1;
	if (send_data(0, 0) == -1) return -1;


#if DEBUG_LEVEL >= 1
	printf("received payload: ");
	for (int i=0;i<len;i++) {
		printf("0x%02X ",receive[i]);
	}
	printf("\n");
#endif

	// success
	return len;
}


int is_valid_item(unsigned int ID, unsigned char item) {
	if (ID > 0xFFFF) {
		return 0;
	}
	if (item >= 0x18 && item < 0x20) {
		return (item & 0x07) == ((ID>>12) & 0x07);
	}else if (item < 0x22) {
		int pos = (((item>>1)+7)&0x07) | ((item>>1)&0x08) | ((item>>2)&0x08);
		return (item & 0x01) == ((ID>>pos) & 0x01);
	}else{
		return 0;
	}
}


int is_valid_deco(unsigned int ID, unsigned char deco) {
	if (ID > 0xFFFF) {
		return 0;
	}
	return is_valid_item(((ID&0xFF)<<8)|(ID>>8), deco);
}


void rt() {
	struct sched_param priority;
	priority.sched_priority = sched_get_priority_max(SCHED_FIFO);
	sched_setscheduler(0, SCHED_FIFO, &priority);
}

void no_rt() {
	struct sched_param priority;
	priority.sched_priority = 0;
	sched_setscheduler(0, SCHED_OTHER, &priority);
}

int main(int argc, char* argv[]) {

	if (wiringPiSetup() == -1) return 1;
	pinMode(OUT_PIN, OUTPUT);
	pinMode(IN_PIN,  INPUT);
	pullUpDnControl(IN_PIN, PUD_UP);
	pinMode(STATUS_LED, OUTPUT);
	pinMode(POWER_SW,  INPUT);
	pullUpDnControl(POWER_SW, PUD_UP);

	unsigned char data1[20];
	unsigned char data2[38];

	// gift data
	// status is set to "NOT READY" to force error (just collect payloads of other player)
	unsigned char snd1[] = {
			0x01, // version (GC, S, or PIKACHU)
			0x00, 0x00, // ID: 00000 (identifier to prevent multiple gifts from one person)
			0x82, 0x80, 0x8b, 0x50, 0x50, 0x50, 0x50, 0x50, 0x00, 0x00, 0x00, // NAME: CAL
			0xfb, // POKEDEX: 251
			0x00, 0x00, 0x00, // ITEM: BERRY
			0xad, // 0x00: okay; 0xad: must receive gift from pokemon center before
			0x00 // counter of contacts this day; only 5 are allowed
	};
	// pokemon data
	unsigned char snd2[] = {
			0x32,0x9A,0x4D,0xEB,0x22,0x71, // MEGANIUM
			0x32,0x9D,0x34,0x62,0x81,0xAC, // TYPHLOSION
			0x32,0xA0,0x2C,0xB8,0xA3,0x67, // FERALIGATR
			0xFF,0x00,0x00,0x00,0x00,0x00, // (end marker)
			0x00,0x00,0x00,0x00,0x00,0x00,
			0x00,0x00,0x00,0x00,0x00,0x00,
			0x00,0x00
	};
	memcpy(data1, snd1, 20);
	memcpy(data2, snd2, 38);


	do {
		no_rt();
		printf("waiting for ir signal...\n");
		while (receive_hello() == -1) {
			if (power_off()) {
				digitalWrite(STATUS_LED, 1);
				system("sudo halt");
			}
		}
		digitalWrite(STATUS_LED, 1);
		rt();
		printf("trying to connect...\n");
		// ignore hello message and send our own (later) in order to become master.
		// as master we receive the region code before we need to send our own.
		// thus we can always send the correct region code.
		delayMicroseconds(50000);
		unsigned char rec1[50];
		unsigned char rec2[50];
		unsigned char region_code = 0;

		int len1 = exchange_payload(data1, 20, rec1, sizeof(rec1), &region_code);
		if (len1 != 20) {
			digitalWrite(STATUS_LED, 0);
			printf("error: communication failed\n");
			continue;
		}
		int exchange_second_payload = 1;
		if (data1[0] == 0x03 || rec1[0] == 0x03) {
			exchange_second_payload = 0;
		}
		if (exchange_second_payload) {
			// wait additional time between payloads (more than SND_WAIT)
			delayMicrosecondsHard(2000);
			int len2 = exchange_payload(data2, 38, rec2, sizeof(rec2), 0);
			digitalWrite(STATUS_LED, 0);
			if (len2 != 38) {
				printf("error: communication failed\n");
				continue;
			}
		}else{
			digitalWrite(STATUS_LED, 0);
		}
		if (rec1[18]) {
			printf("error: must retrieve gift at pokemon center\n");
			continue;
		}
		printf("success\n");
		// print info:
		char name[50];
		{
			int pos = 0;
			for (int i=0; i<11; i++) {
				int p = rec1[3+i];
				if (p == 50)
					name[pos++] = 0;
				else if (p >= 0x60) {
					int l = strlen(symbols[p-0x60]);
					memcpy(&name[pos],symbols[p-0x60],l);
					pos+=l;
				}
			}
			name[pos] = 0;
		}
		unsigned int ID = (((unsigned int)rec1[1])<<8)+((unsigned int)rec1[2]);
		const char* region = "unknown";
		switch (region_code) {
			case 0x90:
				region = "USA";
				break;
			case 0x96:
				region = "ESP";
				break;
			case 0x99:
				region = "ITA";
				break;
			case 0x9A:
				region = "FRA";
				break;
			case 0x9F:
				region = "GER";
				break;
		}
		printf("region code: 0x%02X (%s)\n",region_code,region);
		printf("version: 0x%02X (%s)\n",rec1[0],
				rec1[0]==0x01?"GOLD or CRYSTAL":
				(rec1[0]==0x02?"SILVER":
				(rec1[0]==0x03?"POKÉMON PIKACHU 2 GS":"unknown")));
		printf("id: %05u\n",ID);
		printf("name: %s\n", name);
		printf("today's gift counter: %u%s\n",rec1[19],rec1[19]>=5?" (maximum reached)":"");
		printf("pokédex entries: %u\n",rec1[14]);
		if (exchange_second_payload) {
			for (int i=0; i<6; i++) {
				if (rec2[6*i]==0xFF) {
					break;
				}
				printf("pokémon: %s (lv %u): ", pokemon[rec2[6*i+1]], rec2[6*i]);
				for (int j=0; j<4; j++) {
					if (!rec2[6*i+2+j]) break;
					printf("%s", moves[rec2[6*i+2+j]]);
					if (j<3 && rec2[6*i+3+j]) printf(", ");
				}
				printf("\n");
			}
		}
		if (rec1[15]) {
			printf("gift sent: %s (alternatively %s)\n",
					decorations[rec1[17]<=36?rec1[17]:24],
					items[rec1[16]<=36?rec1[16]:20]);
		}else{
			printf("gift sent: %s\n",items[rec1[16]<=36?rec1[16]:20]);
		}
		if (rec1[0] == 0x01 || rec1[0] == 0x02) {
			// VALIDITY CHECK PKMN GSC
			if (!is_valid_item(ID, rec1[16]) || !is_valid_deco(ID, rec1[17])) {
				printf("hint: item or decoration does not match trainer ID hypothesis.\n");
				printf("      please report your trainer ID number: %05u\n",ID);
			}
		}else if (rec1[0] == 0x03) {
			// VALIDITY CHECK: PIKACHU
		} else {
			// INVALID DEVICE
		}
		memcpy(data1, rec1, 20);
		if (exchange_second_payload) {
			memcpy(data2, rec2, 38);
		}else{
			data1[0] = 0x03;
		}
	} while(1);
}
