/*
 * Copyright (C) 2016 Freescale Semiconductor, Inc.
 *
 * SPDX-License-Identifier:     GPL-2.0+
 */

#include <common.h>
#include <stdlib.h>
#include <linux/string.h>
#include <fsl_fastboot.h>
#include <fsl_avb.h>

/* as libavb's bootctl doesn't have the get_var support
 * we add the getvar support on our side ...*/
#ifndef MAX_PTN
#define MAX_PTN 32
#endif
#define SLOT_NUM 2
static char *slot_suffix[SLOT_NUM] = {"_a", "_b"};

static int strcmp_l1(const char *s1, const char *s2) {
	if (!s1 || !s2)
		return -1;
	return strncmp(s1, s2, strlen(s1));
}

static bool slot_is_bootable(AvbABSlotData* slot) {
  return slot->priority > 0 &&
         (slot->successful_boot || (slot->tries_remaining > 0));
}

int slotidx_from_suffix(char *suffix) {
	int slot = -1;

	if (!strcmp(suffix, "_a") ||
			!strcmp(suffix, "a"))
		slot = 0;
	else if (!strcmp(suffix, "_b") ||
			!strcmp(suffix, "b"))
		slot = 1;

	return slot;
}

bool is_slotvar_avb(char *cmd) {

	assert(cmd != NULL);
	if (!strcmp_l1("has-slot:", cmd) ||
		!strcmp_l1("slot-successful:", cmd) ||
		!strcmp_l1("slot-count", cmd) ||
		!strcmp_l1("slot-suffixes", cmd) ||
		!strcmp_l1("current-slot", cmd) ||
		!strcmp_l1("slot-unbootable:", cmd) ||
		!strcmp_l1("slot-retry-count:", cmd))
		return true;
	return false;
}

int get_curr_slot(AvbABData *ab_data) {
	if (slot_is_bootable(&ab_data->slots[0]) &&
		slot_is_bootable(&ab_data->slots[1])) {
		if (ab_data->slots[1].priority > ab_data->slots[0].priority)
			return 1;
		else
			return 0;
	} else if (slot_is_bootable(&ab_data->slots[0]))
		return 0;
	else if (slot_is_bootable(&ab_data->slots[1]))
		return 1;
	else
		return -1;
}

extern struct fastboot_ptentry g_ptable[MAX_PTN];
extern unsigned int g_pcount;

static bool has_slot(char *cmd) {
	unsigned int n;
	char *ptr;

	for (n = 0; n < g_pcount; n++) {
		ptr = strstr(g_ptable[n].name, cmd);
		if (ptr != NULL) {
			ptr += strlen(cmd);
			if (!strcmp(ptr, "_a") || !strcmp(ptr, "_b"))
				return true;
		}
	}
	return false;
}

int get_slotvar_avb(AvbABOps *ab_ops, char *cmd, char *buffer, size_t size) {

	AvbABData ab_data;
	AvbABSlotData *slot_data;
	int slot;

	if ((ab_ops == NULL) || (cmd == NULL) || (buffer == NULL))
		return -1;

	char *str = cmd;
	if (!strcmp_l1("has-slot:", cmd)) {
		str += strlen("has-slot:");
		if (has_slot(str))
			strlcpy(buffer, "yes", size);
		else
			strlcpy(buffer, "no", size);
		return 0;

	} else if (!strcmp_l1("slot-suffixes", cmd)) {
		strlcpy(buffer, "_a,_b", size);
		return 0 ;

	} else if (!strcmp_l1("slot-count", cmd)) {
		strlcpy(buffer, "2", size);
		return 0 ;
	}

	/* load ab meta */
	if (ab_ops->read_ab_metadata == NULL ||
			ab_ops->read_ab_metadata(ab_ops, &ab_data) != AVB_IO_RESULT_OK) {
		strlcpy(buffer, "ab data read error", size);
		return -1 ;
	}

	if (!strcmp_l1("current-slot", cmd)) {
		int curr = get_curr_slot(&ab_data);
		if (curr >= 0 && curr < SLOT_NUM)
			strlcpy(buffer, slot_suffix[curr] + sizeof(unsigned char), size);
		else {
			strlcpy(buffer, "no bootable slot", size);
			return -1;
		}

	} else if (!strcmp_l1("slot-successful:", cmd)) {
		str += strlen("slot-successful:");
		slot = slotidx_from_suffix(str);
		if (slot < 0) {
			strlcpy(buffer, "no such slot", size);
			return -1;
		} else {
			slot_data = &ab_data.slots[slot];
			bool succ = (slot_data->successful_boot != 0);
			strlcpy(buffer, succ ? "yes" : "no", size);
		}

	} else if (!strcmp_l1("slot-unbootable:", cmd)) {
		str += strlen("slot-unbootable:");
		slot = slotidx_from_suffix(str);
		if (slot < 0) {
			strlcpy(buffer, "no such slot", size);
			return -1;
		} else {
			slot_data = &ab_data.slots[slot];
			bool bootable = slot_is_bootable(slot_data);
			strlcpy(buffer, bootable ? "no" : "yes", size);
		}

	} else if (!strcmp_l1("slot-retry-count:", cmd)) {
		str += strlen("slot-retry-count:");
		slot = slotidx_from_suffix(str);
		if (slot < 0) {
			strlcpy(buffer, "no such slot", size);
			return -1;
		}
		else {
			slot_data = &ab_data.slots[slot];
			char var[7];
			sprintf(var, "%d",
				slot_data->tries_remaining);
			strlcpy(buffer, var, size);
		}

	} else {
		strlcpy(buffer, "no such slot command", size);
		return -1;
	}

	return 0;
}

char *select_slot(AvbABOps *ab_ops) {
	AvbABData ab_data;
	int curr;

	if (ab_ops == NULL) {
		return NULL;
	}

	/* load ab meta */
	if (ab_ops->read_ab_metadata == NULL ||
			ab_ops->read_ab_metadata(ab_ops, &ab_data) != AVB_IO_RESULT_OK) {
		return NULL;
	}
	curr = get_curr_slot(&ab_data);
	if (curr >= 0 && curr < SLOT_NUM)
		return slot_suffix[curr];
	else
		return NULL;
}
