/* Copyright 2020 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * Test USB Protocol Layer module.
 */
#include "common.h"
#include "mock/tcpc_mock.h"
#include "mock/tcpm_mock.h"
#include "mock/usb_pe_sm_mock.h"
#include "mock/usb_tc_sm_mock.h"
#include "task.h"
#include "tcpm/tcpci.h"
#include "tcpm/tcpm.h"
#include "test_util.h"
#include "timer.h"
#include "usb_emsg.h"
#include "usb_pd.h"
#include "usb_pd_test_util.h"
#include "usb_pe_sm.h"
#include "usb_prl_sm.h"
#include "usb_sm_checks.h"
#include "usb_tc_sm.h"
#include "util.h"

#define PORT0 0

/* Install Mock TCPC and MUX drivers */
const struct tcpc_config_t tcpc_config[CONFIG_USB_PD_PORT_MAX_COUNT] = {
	{
		.drv = &mock_tcpc_driver,
	},
};

static void enable_prl(int port, int en)
{
	tcpm_set_rx_enable(port, en);

	mock_tc_port[port].pd_enable = en;

	task_wait_event(10*MSEC);

	prl_set_rev(port, TCPCI_MSG_SOP, mock_tc_port[port].rev);
}

static int test_receive_control_msg(void)
{
	int port = PORT0;
	uint16_t header = PD_HEADER(PD_CTRL_DR_SWAP,
		pd_get_power_role(port),
		pd_get_data_role(port),
		mock_tc_port[port].msg_rx_id,
		0, mock_tc_port[port].rev, 0);

	/* Set up the message to be received. */
	mock_tcpm_rx_msg(port, header, 0, NULL);

	/* Process the message. */
	task_wait_event(10*MSEC);

	/* Check results. */
	TEST_NE(mock_pe_port[port].mock_pe_message_received, 0, "%d");
	TEST_EQ(header, rx_emsg[port].header, "%d");
	TEST_EQ(rx_emsg[port].len, 0, "%d");

	TEST_LE(mock_pe_port[port].mock_pe_error, 0, "%d");
	TEST_EQ(mock_pe_port[port].mock_pe_message_discarded, 0, "%d");
	TEST_EQ(mock_pe_port[port].mock_got_soft_reset, 0, "%d");
	TEST_EQ(mock_pe_port[port].mock_pe_got_hard_reset, 0, "%d");
	TEST_EQ(mock_pe_port[port].mock_pe_hard_reset_sent, 0, "%d");

	return EC_SUCCESS;
}

static int test_send_control_msg(void)
{
	int port = PORT0;

	/* Set up the message to be sent. */
	prl_send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_ACCEPT);
	task_wait_event(MSEC);
	/* Simulate the TX complete that the PD_INT handler would signal */
	pd_transmit_complete(port, TCPC_TX_COMPLETE_SUCCESS);

	task_wait_event(10*MSEC);

	/* Check results. */
	TEST_NE(mock_pe_port[port].mock_pe_message_sent, 0, "%d");
	TEST_LE(mock_pe_port[port].mock_pe_error, 0, "%d");
	TEST_EQ(mock_pe_port[port].mock_pe_message_discarded, 0, "%d");
	TEST_EQ(mock_pe_port[port].mock_got_soft_reset, 0, "%d");
	TEST_EQ(mock_pe_port[port].mock_pe_got_hard_reset, 0, "%d");
	TEST_EQ(mock_pe_port[port].mock_pe_hard_reset_sent, 0, "%d");

	return EC_SUCCESS;
}

static int test_discard_queued_tx_when_rx_happens(void)
{
	int port = PORT0;
	uint16_t header = PD_HEADER(PD_CTRL_DR_SWAP,
		pd_get_power_role(port),
		pd_get_data_role(port),
		mock_tc_port[port].msg_rx_id,
		0, mock_tc_port[port].rev, 0);
	uint8_t *buf = tx_emsg[port].buf;
	uint8_t len = 8;
	uint8_t i = 0;

	/* Set up the message to be sent. */
	for (i = 0 ; i < len ; i++)
		buf[i] = (uint8_t)i;

	tx_emsg[port].len = len;
	prl_send_data_msg(port, TCPCI_MSG_SOP, PD_DATA_SOURCE_CAP);

	/* Set up the message to be received. */
	mock_tcpm_rx_msg(port, header, 0, NULL);

	/* Process the message. */
	task_wait_event(10*MSEC);

	/* Check results. Source should have discarded its message queued up
	 * to TX, and should have received the message from the sink.
	 */
	TEST_NE(mock_pe_port[port].mock_pe_message_discarded, 0, "%d");
	TEST_NE(mock_pe_port[port].mock_pe_message_received, 0, "%d");
	TEST_EQ(mock_pe_port[port].mock_pe_message_sent, 0, "%d");

	TEST_LE(mock_pe_port[port].mock_pe_error, 0, "%d");
	TEST_EQ(mock_pe_port[port].mock_got_soft_reset, 0, "%d");
	TEST_EQ(mock_pe_port[port].mock_pe_got_hard_reset, 0, "%d");
	TEST_EQ(mock_pe_port[port].mock_pe_hard_reset_sent, 0, "%d");

	return EC_SUCCESS;
}

void before_test(void)
{
	mock_tc_port_reset();
	mock_tc_port[PORT0].rev = PD_REV30;
	mock_tc_port[PORT0].power_role = PD_ROLE_SOURCE;
	mock_tc_port[PORT0].data_role = PD_ROLE_DFP;

	mock_tcpm_reset();
	mock_pe_port_reset();

	prl_reset_soft(PORT0);
	enable_prl(PORT0, 1);
}

void after_test(void)
{
	enable_prl(PORT0, 0);
}

void run_test(int argc, char **argv)
{
	RUN_TEST(test_receive_control_msg);
	RUN_TEST(test_send_control_msg);
	RUN_TEST(test_discard_queued_tx_when_rx_happens);
	/* TODO add tests here */


	/* Do basic state machine validity checks last. */
	RUN_TEST(test_prl_no_parent_cycles);
	RUN_TEST(test_prl_all_states_named);

	test_print_result();
}
