/*
 * Copyright 2017, Digi International Inc.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

#include <errno.h>
#include <libgen.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <libdigiapix/pwm.h>


#define DEFAULT_PWM_FREQUENCY	1000
#define DEFAULT_PWM_ALIAS	"DEFAULT_PWM"

#define ARG_PWM_CHIP		0
#define ARG_PWM_CHANNEL		1

static pwm_t *pwm_line;
static int running = 1;

/*
 * usage_and_exit() - Show usage information and exit with 'exitval' return
 *					  value
 *
 * @name:	Application name.
 * @exitval:	The exit code.
 */
static void usage_and_exit(char *name, int exitval)
{
	fprintf(stdout,
		"Example application using libdigiapix PWM support\n"
		"\n"
		"Usage: %s\n"
		"                     %s is used as PWM alias\n"
		"                     %dHz is used as Frequency\n\n"
		"Usage: %s <pwm-alias> <pwm-freq>\n"
		"<pwm-alias>          PWM alias\n"
		"<pwm-freq>           Frequency to use (Hz)\n\n"
		"Usage: %s <pwm-chip> <pwm-channel> <pwm-freq>\n"
		"<pwm-chip-number>    PWM chip number\n"
		"<pwm-channel-number> PWM channel number\n"
		"<pwm-freq>           Frequency to use (Hz)\n"
		"\n"
		"Aliases for PWM can be configured in the library config file\n"
		"\n", name, DEFAULT_PWM_ALIAS, DEFAULT_PWM_FREQUENCY, name, name);

	exit(exitval);
}

/*
 * cleanup() - Frees all the allocated memory before exiting
 */
static void cleanup(void)
{
	running = 0;
	if (pwm_line) {
		ldx_pwm_set_duty_cycle(pwm_line, 0);
		ldx_pwm_enable(pwm_line, PWM_DISABLED);
		ldx_pwm_free(pwm_line);
	}
}

/*
 * sigaction_handler() - Handler to execute after receiving a signal
 *
 * @signum:	Received signal.
 */
static void sigaction_handler(int signum)
{
	/* 'atexit' executes the cleanup function */
	exit(EXIT_FAILURE);
}

/*
 * register_signals() - Registers program signals
 */
static void register_signals(void)
{
	struct sigaction action;

	action.sa_handler = sigaction_handler;
	action.sa_flags = 0;
	sigemptyset(&action.sa_mask);

	sigaction(SIGHUP, &action, NULL);
	sigaction(SIGINT, &action, NULL);
	sigaction(SIGTERM, &action, NULL);
}

/*
 * parse_argument() - Parses the given string argument and returns the
 *					  corresponding integer value
 *
 * @argv:	Argument to parse in string format.
 * @arg_type:	Type of the argument to parse.
 *
 * Return: The parsed integer argument, -1 on error.
 */
static int parse_argument(char *argv, int arg_type)
{
	char *endptr;
	long value;

	errno = 0;
	value = strtol(argv, &endptr, 10);

	if ((errno == ERANGE && (value == LONG_MAX || value == LONG_MIN))
			  || (errno != 0 && value == 0))
		return -1;

	if (endptr == argv){
		switch (arg_type) {
		case ARG_PWM_CHIP:
			return ldx_pwm_get_chip(endptr);
		case ARG_PWM_CHANNEL:
			return ldx_pwm_get_channel(endptr);
		default:
			return -1;
		}
	}
	return value;
}

int main(int argc, char **argv)
{
	int ret, ascending = 1, pwm_chip, pwm_channel, pwm_freq = 0;
	char *name = basename(argv[0]);
	int duty_cycle[] = {0, 1, 3, 5, 10, 15, 20, 30, 50, 100};
	int d = 0;
	int levels = sizeof(duty_cycle) / sizeof(duty_cycle[0]);

	/* Check if the PWM values are passed in the command line. */
	if (argc == 1) {
		pwm_chip = ldx_pwm_get_chip(DEFAULT_PWM_ALIAS);
		pwm_channel = ldx_pwm_get_channel(DEFAULT_PWM_ALIAS);
		pwm_freq = DEFAULT_PWM_FREQUENCY;
	} else if (argc == 3) {
		pwm_chip = parse_argument(argv[1], ARG_PWM_CHIP);
		pwm_channel = parse_argument(argv[1], ARG_PWM_CHANNEL);
		pwm_freq = atoi(argv[2]);
	} else if (argc == 4) {
		pwm_chip = parse_argument(argv[1], ARG_PWM_CHIP);
		pwm_channel = parse_argument(argv[2], ARG_PWM_CHANNEL);
		pwm_freq = atoi(argv[3]);
	} else {
		usage_and_exit(name, EXIT_FAILURE);
	}

	if (pwm_chip < 0 || pwm_channel < 0 ) {
		printf("Unable to parse PWM chip or channel\n");
		return EXIT_FAILURE;
	}

	if (pwm_freq <= 0) {
		printf("Frequency must be greater than 0\n");
		return EXIT_FAILURE;
	}

	pwm_line = ldx_pwm_request(pwm_chip, pwm_channel, REQUEST_SHARED);

	/* Check PWM. */
	if (!pwm_line) {
		printf("Failed to initialize PWM\n");
		return EXIT_FAILURE;
	}

	/* Register signals and exit cleanup function */
	atexit(cleanup);
	register_signals();

	printf("Setting PWM frequency to %dHz...", pwm_freq);
	ret = ldx_pwm_set_freq(pwm_line, pwm_freq);
	if (ret != PWM_CONFIG_ERROR_NONE) {
		printf("Failed\n");
		return EXIT_FAILURE;
	}

	/* Check the frequency. */
	ret = ldx_pwm_get_freq(pwm_line);
	printf("%s\n", ret >= 0 ? "Done" : "Failed");
	if (ret == -1) {
		return EXIT_FAILURE;
	}

	printf("Enabling PWM %d:%d...", pwm_line->chip, pwm_line->channel);
	ret = ldx_pwm_enable(pwm_line, PWM_ENABLED);
	if (ret != EXIT_SUCCESS) {
		printf("Failed\n");
		return EXIT_FAILURE;
	}

	ret = ldx_pwm_is_enabled(pwm_line);
	printf("%s\n", ret == PWM_ENABLED ? "Done" : "Failed");
	if (ret != PWM_ENABLED) {
		return EXIT_FAILURE;
	}

	/* Main application loop.*/
	while (running) {
		/* Set the duty cycle. */
		ret = ldx_pwm_set_duty_cycle_percentage(pwm_line, duty_cycle[d]);
		if (ret != PWM_CONFIG_ERROR_NONE) {
			printf("Failed to set the duty cycle\n");
			return EXIT_FAILURE;
		}
		if (ascending) {
			if (++d == levels - 1)
				ascending = 0;
		} else {
			if (--d == 0)
				ascending = 1;
		}
		usleep(150000);
	}

	return EXIT_SUCCESS;
}
