// SPDX-License-Identifier: BSD-3-Clause
// Copyright The Music Player Daemon Project

#include <mpd/client.h>
#include <mpd/status.h>
#include <mpd/entity.h>
#include <mpd/search.h>
#include <mpd/tag.h>
#include <mpd/message.h>

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

static int
handle_error(struct mpd_connection *c)
{
	assert(mpd_connection_get_error(c) != MPD_ERROR_SUCCESS);

	fprintf(stderr, "%s\n", mpd_connection_get_error_message(c));
	mpd_connection_free(c);
	return EXIT_FAILURE;
}

static void
print_tag(const struct mpd_song *song, enum mpd_tag_type type,
	  const char *label)
{
	unsigned i = 0;
	const char *value;

	while ((value = mpd_song_get_tag(song, type, i++)) != NULL)
		printf("%s: %s\n", label, value);
}

static int
readpicture(struct mpd_connection *c, const char *uri)
{
	unsigned long long offset = 0;
	unsigned long long total_size;

	do {
		char offset_s[32];
		snprintf(offset_s, sizeof(offset_s), "%lu", (unsigned long)offset);

		if (!mpd_send_command(c, "readpicture", uri, offset_s, NULL))
			return handle_error(c);

		struct mpd_pair *pair = mpd_recv_pair_named(c, "size");
		if (pair == NULL) {
			if (mpd_connection_get_error(c) != MPD_ERROR_SUCCESS)
				return handle_error(c);
			fprintf(stderr, "No 'size'\n");
			return EXIT_FAILURE;
		}

		total_size = strtoull(pair->value, NULL, 10);

		mpd_return_pair(c, pair);

		pair = mpd_recv_pair_named(c, "binary");
		if (pair == NULL) {
			if (mpd_connection_get_error(c) != MPD_ERROR_SUCCESS)
				return handle_error(c);
			fprintf(stderr, "No 'binary'\n");
			return EXIT_FAILURE;
		}

		const unsigned long long chunk_size =
			strtoull(pair->value, NULL, 10);
		mpd_return_pair(c, pair);

		if (chunk_size == 0)
			break;

		char *p = malloc(chunk_size);
		if (p == NULL)
			abort();

		if (!mpd_recv_binary(c, p, chunk_size))
			return handle_error(c);

		if (!mpd_response_finish(c))
			return handle_error(c);

		if (fwrite(p, chunk_size, 1, stdout) != 1)
			exit(EXIT_FAILURE);

		free(p);

		offset += chunk_size;
	} while (offset < total_size);

	return EXIT_SUCCESS;
}

int main(int argc, char ** argv) {
	struct mpd_connection *conn;

	conn = mpd_connection_new(NULL, 0, 30000);

	if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS)
		return handle_error(conn);

	if(argc==1) {
		for(int i=0;i<3;i++) {
			printf("version[%i]: %i\n",i,
			       mpd_connection_get_server_version(conn)[i]);
		}

		struct mpd_status * status;
		struct mpd_song *song;
		const struct mpd_audio_format *audio_format;

		mpd_command_list_begin(conn, true);
		mpd_send_status(conn);
		mpd_send_current_song(conn);
		mpd_command_list_end(conn);

		status = mpd_recv_status(conn);
		if (status == NULL)
			return handle_error(conn);

		printf("volume: %i\n", mpd_status_get_volume(status));
		printf("repeat: %i\n", mpd_status_get_repeat(status));
		printf("queue version: %u\n", mpd_status_get_queue_version(status));
		printf("queue length: %i\n", mpd_status_get_queue_length(status));
		if (mpd_status_get_error(status) != NULL)
			printf("error: %s\n", mpd_status_get_error(status));

		if (mpd_status_get_state(status) == MPD_STATE_PLAY ||
		    mpd_status_get_state(status) == MPD_STATE_PAUSE) {
			printf("song: %i\n", mpd_status_get_song_pos(status));
			printf("elaspedTime: %i\n",mpd_status_get_elapsed_time(status));
			printf("elasped_ms: %u\n", mpd_status_get_elapsed_ms(status));
			printf("totalTime: %i\n", mpd_status_get_total_time(status));
			printf("bitRate: %i\n", mpd_status_get_kbit_rate(status));
		}

		audio_format = mpd_status_get_audio_format(status);
		if (audio_format != NULL) {
			printf("sampleRate: %i\n", audio_format->sample_rate);
			printf("bits: %i\n", audio_format->bits);
			printf("channels: %i\n", audio_format->channels);
		}

		mpd_status_free(status);

		if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS)
			return handle_error(conn);

		mpd_response_next(conn);

		while ((song = mpd_recv_song(conn)) != NULL) {
			printf("uri: %s\n", mpd_song_get_uri(song));
			print_tag(song, MPD_TAG_ARTIST, "artist");
			print_tag(song, MPD_TAG_ALBUM, "album");
			print_tag(song, MPD_TAG_TITLE, "title");
			print_tag(song, MPD_TAG_TRACK, "track");
			print_tag(song, MPD_TAG_NAME, "name");
			print_tag(song, MPD_TAG_DATE, "date");

			if (mpd_song_get_duration(song) > 0) {
				printf("time: %u\n", mpd_song_get_duration(song));
			}

			printf("pos: %u\n", mpd_song_get_pos(song));

			mpd_song_free(song);
		}

		if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS ||
		    !mpd_response_finish(conn))
			return handle_error(conn);
	}
	else if(argc==3 && strcmp(argv[1],"lsinfo")==0) {
		struct mpd_entity * entity;

		if (!mpd_send_list_meta(conn, argv[2]))
			return handle_error(conn);

		while ((entity = mpd_recv_entity(conn)) != NULL) {
			const struct mpd_song *song;
			const struct mpd_directory *dir;
			const struct mpd_playlist *pl;

			switch (mpd_entity_get_type(entity)) {
			case MPD_ENTITY_TYPE_UNKNOWN:
				break;

			case MPD_ENTITY_TYPE_SONG:
				song = mpd_entity_get_song(entity);
				printf("uri: %s\n", mpd_song_get_uri(song));
				print_tag(song, MPD_TAG_ARTIST, "artist");
				print_tag(song, MPD_TAG_ALBUM, "album");
				print_tag(song, MPD_TAG_TITLE, "title");
				print_tag(song, MPD_TAG_TRACK, "track");
				break;

			case MPD_ENTITY_TYPE_DIRECTORY:
				dir = mpd_entity_get_directory(entity);
				printf("directory: %s\n", mpd_directory_get_path(dir));
				break;

			case MPD_ENTITY_TYPE_PLAYLIST:
				pl = mpd_entity_get_playlist(entity);
				printf("playlist: %s\n",
				       mpd_playlist_get_path(pl));
				break;
			}

			mpd_entity_free(entity);
		}

		if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS ||
		    !mpd_response_finish(conn))
			return handle_error(conn);
	} else if (argc == 3 && strcmp(argv[1], "readpicture") == 0) {
		int result = readpicture(conn, argv[2]);
		if (result != EXIT_SUCCESS)
			return result;
	} else if(argc==2 && strcmp(argv[1],"artists")==0) {
		struct mpd_pair *pair;

		if (!mpd_search_db_tags(conn, MPD_TAG_ARTIST) ||
		    !mpd_search_commit(conn))
			return handle_error(conn);

		while ((pair = mpd_recv_pair_tag(conn,
						 MPD_TAG_ARTIST)) != NULL) {
			printf("%s\n", pair->value);
			mpd_return_pair(conn, pair);
		}

		if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS ||
		    !mpd_response_finish(conn))
			return handle_error(conn);
	} else if (argc == 2 && strcmp(argv[1], "playlists") == 0) {
		if (!mpd_send_list_playlists(conn))
			return handle_error(conn);

		struct mpd_playlist *playlist;
		while ((playlist = mpd_recv_playlist(conn)) != NULL) {
			printf("%s\n",
			       mpd_playlist_get_path(playlist));
			mpd_playlist_free(playlist);
		}

		if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS ||
		    !mpd_response_finish(conn))
			return handle_error(conn);
	} else if (argc == 2 && strcmp(argv[1], "idle") == 0) {
		enum mpd_idle idle = mpd_run_idle(conn);
		if (idle == 0 &&
		    mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS)
			return handle_error(conn);

		for (unsigned j = 0;; ++j) {
			enum mpd_idle i = 1 << j;
			const char *name = mpd_idle_name(i);

			if (name == NULL)
				break;

			if (idle & i)
				printf("%s\n", name);
		}
	} else if (argc == 3 && strcmp(argv[1], "subscribe") == 0) {
		/* subscribe to a channel and print all messages */

		if (!mpd_run_subscribe(conn, argv[2]))
			return handle_error(conn);

		while (mpd_run_idle_mask(conn, MPD_IDLE_MESSAGE) != 0) {
			if (!mpd_send_read_messages(conn))
				return handle_error(conn);

			struct mpd_message *msg;
			while ((msg = mpd_recv_message(conn)) != NULL) {
				printf("%s\n", mpd_message_get_text(msg));
				mpd_message_free(msg);
			}

			if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS ||
			    !mpd_response_finish(conn))
				return handle_error(conn);
		}

		return handle_error(conn);
	} else if (argc == 2 && strcmp(argv[1], "channels") == 0) {
		/* print a list of channels */

		if (!mpd_send_channels(conn))
			return handle_error(conn);

		struct mpd_pair *pair;
		while ((pair = mpd_recv_channel_pair(conn)) != NULL) {
			printf("%s\n", pair->value);
			mpd_return_pair(conn, pair);
		}

		if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS ||
		    !mpd_response_finish(conn))
			return handle_error(conn);
	} else if (argc == 4 && strcmp(argv[1], "message") == 0) {
		/* send a message to a channel */

		if (!mpd_run_send_message(conn, argv[2], argv[3]))
			return handle_error(conn);
	} else if (argc == 3 && strcmp(argv[1], "fingerprint") == 0) {
		char buffer[8192];

		const char *fingerprint = mpd_run_getfingerprint_chromaprint(conn, argv[2],
									     buffer, sizeof(buffer));
		if (fingerprint == NULL)
			return handle_error(conn);

		printf("%s\n", fingerprint);
	} else if (argc == 2 && strcmp(argv[1], "listneighbors") == 0) {
		struct mpd_neighbor *neighbor;

		if (!mpd_send_list_neighbors(conn))
			return handle_error(conn);

		while ((neighbor = mpd_recv_neighbor(conn)) != NULL) {
			printf("uri: %s\n display name: %s\n",
				mpd_neighbor_get_uri(neighbor),
				mpd_neighbor_get_display_name(neighbor));
			mpd_neighbor_free(neighbor);
		}

		if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS ||
		    !mpd_response_finish(conn))
			return handle_error(conn);
	} else if (argc == 3 && strcmp(argv[1], "newpartition") == 0) {
		if (!mpd_run_newpartition(conn, argv[2]))
			return handle_error(conn);
	} else if (argc == 2 && strcmp(argv[1], "listpartitions") == 0) {
		struct mpd_pair *pair;
		struct mpd_partition *part;

		if (!mpd_send_listpartitions(conn))
			return handle_error(conn);

		while ((pair = mpd_recv_partition_pair(conn)) != NULL) {
			part = mpd_partition_new(pair);
			if (part == NULL)
				return handle_error(conn);
			mpd_return_pair(conn, pair);

			printf("partition name: %s\n",
					mpd_partition_get_name(part));
			mpd_partition_free(part);
		}
		if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS)
			handle_error(conn);
	} else if (argc == 2 && strcmp(argv[1], "idle_partition") == 0) {
		while (mpd_run_idle_mask(conn, MPD_IDLE_PARTITION) != 0) {
			struct mpd_pair *pair;
			struct mpd_partition *part;

			if (!mpd_send_listpartitions(conn))
				return handle_error(conn);

			while ((pair = mpd_recv_partition_pair(conn)) != NULL) {
				part = mpd_partition_new(pair);
				if (part == NULL)
					return handle_error(conn);
				mpd_return_pair(conn, pair);

				printf("partition name: %s\n",
					mpd_partition_get_name(part));
				mpd_partition_free(part);
			}
			if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS)
				handle_error(conn);
		}
		if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS)
			handle_error(conn);
	} else if (argc == 3 && strcmp(argv[1], "switchpartition") == 0) {
		if (!mpd_run_switch_partition(conn, argv[2]))
			return handle_error(conn);
		printf("switched to partition %s\n", argv[2]);
	} else if (argc == 2 && strcmp(argv[1], "replay_gain_status") == 0) {
		const enum mpd_replay_gain_mode mode =
			mpd_run_replay_gain_status(conn);

		if (mode == MPD_REPLAY_UNKNOWN)
			return handle_error(conn);

		printf("replay gain status: %d\n", mode);
	} else if (argc == 3 && strcmp(argv[1], "replay_gain_mode") == 0) {
		const enum mpd_replay_gain_mode mode =
			mpd_parse_replay_gain_name(argv[2]);

		if (mode == MPD_REPLAY_UNKNOWN) {
			printf("Unknown replay mode %s\n", argv[2]);
			return EXIT_FAILURE;
		}

		if (!mpd_run_replay_gain_mode(conn, mode))
			return handle_error(conn);
	} else if (argc == 2 && strcmp(argv[1], "getvol") == 0) {
		int volume = mpd_run_get_volume(conn);
		if (volume == -1)
			return handle_error(conn);

		printf("Volume: %d\n", volume);
	} else if (argc == 3 && strcmp(argv[1], "binarylimit") == 0) {
		unsigned limit = (unsigned)strtoul(argv[2], NULL, 10);
		if (!mpd_run_binarylimit(conn, limit))
			return handle_error(conn);
	}

	mpd_connection_free(conn);

	return 0;
}
