/*
 * Copyright 2016-2018, Intel Corporation
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *
 *     * Neither the name of the copyright holder nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * check.c -- functions performing checks in proper order
 */

#include <stdint.h>

#include "out.h"
#include "libpmempool.h"
#include "pmempool.h"
#include "pool.h"
#include "check.h"
#include "check_util.h"

#define CHECK_RESULT_IS_STOP(result)\
	((result) == CHECK_RESULT_ERROR ||\
		(result) == CHECK_RESULT_INTERNAL_ERROR ||\
		((result) == CHECK_RESULT_CANNOT_REPAIR) ||\
		((result) == CHECK_RESULT_NOT_CONSISTENT))

struct step {
	void (*func)(PMEMpoolcheck *);
	enum pool_type type;
	bool part;
};

static const struct step steps[] = {
	{
		.type		= POOL_TYPE_ANY,
		.func		= check_bad_blocks,
		.part		= true,
	},
	{
		.type		= POOL_TYPE_ANY,
		.func		= check_backup,
		.part		= true,
	},
	{
		.type		= POOL_TYPE_BLK | POOL_TYPE_LOG |
					POOL_TYPE_OBJ,
		.func		= check_sds,
		.part		= true,
	},
	{
		.type		= POOL_TYPE_BLK | POOL_TYPE_LOG |
					POOL_TYPE_OBJ |
					POOL_TYPE_UNKNOWN,
		.func		= check_pool_hdr,
		.part		= true,
	},
	{
		.type		= POOL_TYPE_BLK | POOL_TYPE_LOG |
					POOL_TYPE_OBJ |
					POOL_TYPE_UNKNOWN,
		.func		= check_pool_hdr_uuids,
		.part		= true,
	},
	{
		.type		= POOL_TYPE_LOG,
		.func		= check_log,
		.part		= false,
	},
	{
		.type		= POOL_TYPE_BLK,
		.func		= check_blk,
		.part		= false,
	},
	{
		.type		= POOL_TYPE_BLK | POOL_TYPE_BTT,
		.func		= check_btt_info,
		.part		= false,
	},
	{
		.type		= POOL_TYPE_BLK | POOL_TYPE_BTT,
		.func		= check_btt_map_flog,
		.part		= false,
	},
	{
		.type		= POOL_TYPE_BLK | POOL_TYPE_LOG |
					POOL_TYPE_BTT,
		.func		= check_write,
		.part		= false,
	},
	{
		.func		= NULL,
	},
};

/*
 * check_init -- initialize check process
 */
int
check_init(PMEMpoolcheck *ppc)
{
	LOG(3, NULL);

	if (!(ppc->data = check_data_alloc()))
		goto error_data_malloc;
	if (!(ppc->pool = pool_data_alloc(ppc)))
		goto error_pool_malloc;

	return 0;

error_pool_malloc:
	check_data_free(ppc->data);
error_data_malloc:
	return -1;
}

#ifdef _WIN32
void
convert_status_cache(PMEMpoolcheck *ppc, char *buf, size_t size)
{
	cache_to_utf8(ppc->data, buf, size);
}
#endif

/*
 * status_get -- (internal) get next check_status
 *
 * The assumed order of check_statuses is: all info messages, error or question.
 */
static struct check_status *
status_get(PMEMpoolcheck *ppc)
{
	struct check_status *status = NULL;

	/* clear cache if exists */
	check_clear_status_cache(ppc->data);

	/* return next info if exists */
	if ((status = check_pop_info(ppc->data)))
		return status;

	/* return error if exists */
	if ((status = check_pop_error(ppc->data)))
		return status;

	if (ppc->result == CHECK_RESULT_ASK_QUESTIONS) {
		/*
		 * push answer for previous question and return info if answer
		 * is not valid
		 */
		if (check_push_answer(ppc))
			if ((status = check_pop_info(ppc->data)))
				return status;

		/* if has next question ask it */
		if ((status = check_pop_question(ppc->data)))
			return status;

		/* process answers otherwise */
		ppc->result = CHECK_RESULT_PROCESS_ANSWERS;
	} else if (CHECK_RESULT_IS_STOP(ppc->result))
		check_end(ppc->data);

	return NULL;
}

/*
 * check_step -- perform single check step
 */
struct check_status *
check_step(PMEMpoolcheck *ppc)
{
	LOG(3, NULL);

	struct check_status *status = NULL;
	/* return if we have information or questions to ask or check ended */
	if ((status = status_get(ppc)) || check_is_end(ppc->data))
		return status;

	/* get next step and check if exists */
	const struct step *step = &steps[check_step_get(ppc->data)];
	if (step->func == NULL) {
		check_end(ppc->data);
		return status;
	}

	/*
	 * step would be performed if pool type is one of the required pool type
	 * and it is not part if parts are excluded from current step
	 */
	if (!(step->type & ppc->pool->params.type) ||
			(ppc->pool->params.is_part && !step->part)) {
		/* skip test */
		check_step_inc(ppc->data);
		return NULL;
	}

	/* perform step */
	step->func(ppc);

	/* move on to next step if no questions were generated */
	if (ppc->result != CHECK_RESULT_ASK_QUESTIONS)
		check_step_inc(ppc->data);

	/* get current status and return */
	return status_get(ppc);
}

/*
 * check_fini -- stop check process
 */
void
check_fini(PMEMpoolcheck *ppc)
{
	LOG(3, NULL);

	pool_data_free(ppc->pool);
	check_data_free(ppc->data);
}

/*
 * check_is_end -- return if check has ended
 */
int
check_is_end(struct check_data *data)
{
	return check_is_end_util(data);
}

/*
 * check_status_get -- extract pmempool_check_status from check_status
 */
struct pmempool_check_status *
check_status_get(struct check_status *status)
{
	return check_status_get_util(status);
}
