/*
 * Copyright (c) 2012, 2014 The Linux Foundation. All rights reserved.
 *
 * 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 Linux Foundation 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 "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
 * 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.
 */

#include <sys/ioctl.h>
#include <sys/stat.h>
#include <linux/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

#ifndef ANDROID
#include <stdint.h>
#define strlcat g_strlcat
#endif

#include "alsa_ucm.h"
#include "msm8960_use_cases.h"
#if defined(QC_PROP)
	#include "acdb-loader.h"
#else
	#define acdb_loader_init_ACDB() 0
	#define acdb_loader_deallocate_ACDB() 0
#endif
/* Function prototypes */
static void print_help_menu(void);
static void alsaucm_test_cmd_svr(void);
static int process_cmd(char *cmdStr);

/* Global data */
snd_use_case_mgr_t *uc_mgr;

/* Defines */
enum ucm_cmd_id {
    UCM_OPEN = 0,
    UCM_SET,
    UCM_LISTCARDS,
    UCM_LIST,
    UCM_GET,
    UCM_GETI,
    UCM_RESET,
    UCM_RELOAD,
    UCM_HELP,
    UCM_QUIT,
    UCM_UNKNOWN
};

struct cmd {
    enum ucm_cmd_id code;
    const char *cmd_str;
};

static struct cmd cmds[] = {
    { UCM_OPEN, "open" },
    { UCM_SET,  "set" },
    { UCM_LISTCARDS,  "listcards" },
    { UCM_LIST,  "list" },
    { UCM_GET,  "get" },
    { UCM_GETI,  "geti" },
    { UCM_RESET,  "reset" },
    { UCM_RELOAD,  "reload" },
    { UCM_HELP,  "help" },
    { UCM_QUIT,  "quit" },
    { UCM_UNKNOWN, NULL }
};

#define CVD_VERSION_MIXER_CTL           "CVD Version"
#define MAX_CVD_VERSION_STRING_SIZE     100

static void get_cvd_version(char *cvd_version)
{
    int ret = 0;
    int count;
    struct mixer_ctl *ctl;
    struct mixer *mixer;
    const char* device = "/dev/snd/controlC0";

    mixer = mixer_open(device);
    if (!mixer) {
        fprintf(stderr, "fail to open mixer\n");

       return;
    }

    ctl = mixer_get_ctl_by_name(mixer, CVD_VERSION_MIXER_CTL);
    if (!ctl) {
        fprintf(stderr, "fail to get mixer ctl\n");

        goto done;
     }
     mixer_ctl_update(ctl);

     count = mixer_ctl_get_num_values(ctl);

     if (count > MAX_CVD_VERSION_STRING_SIZE)
         count = MAX_CVD_VERSION_STRING_SIZE;

     ret = mixer_ctl_get_array(ctl, cvd_version, count);
     if (ret != 0) {
         fprintf(stderr, "fail to get mixer_ctl_get_array\n");

         goto done;
     }

done:
       mixer_close(mixer);
       return;
}

static void alsaucm_test_cmd_svr(void)
{
    int fd;
    ssize_t read_count;
    char cmdstr[256] = {'\0'};
    char ch;
    char *exit_str = "quit";
    char *cvd_version = NULL;

    cvd_version = calloc(1, MAX_CVD_VERSION_STRING_SIZE);
    if (!cvd_version)
        fprintf(stderr, "fail to allocate cvd_version\n");
    else
        get_cvd_version(cvd_version);

    if ((acdb_loader_init_v2(NULL, cvd_version, 0)) < 0) {
        fprintf(stderr, "Failed to initialize ACDB\n");
        if (cvd_version)
            free(cvd_version);
        return 0;
    }
    if (cvd_version)
        free(cvd_version);

    if (mknod("/data/alsaucm_test", S_IFIFO | 0666, 0) == 0) {
        fd = open("/data/alsaucm_test", O_RDONLY);
        while (1) {
            read_count = read(fd, &ch, 1);
            if (read_count == 0) {
                sleep(2);
                continue;
            } else if (read_count < 0) {
                fprintf(stderr, "alsaucm_test: error reading cmd\n");
                break;
            }

            if (ch != '\n') {
                strlcat(cmdstr, &ch , (2+strlen(cmdstr)));
                continue;
            } else {
                if (!strncmp(cmdstr, exit_str, strlen(cmdstr))) {
                    /* free UCM instace */
                    if (uc_mgr) {
                        snd_use_case_mgr_close(uc_mgr);
                        uc_mgr = NULL;
                    }
                    break;
                } else {
                    process_cmd(cmdstr);
                    memset(cmdstr, 0, sizeof(cmdstr));
                }
            }
        }
        printf("alsaucm_test: exit server mode\n");
        close(fd);
        remove("/data/alsaucm_test");
    } else {
        fprintf(stderr, "alsaucm_test: Failed to create server\n");
    }

    acdb_loader_deallocate_ACDB();
}


static void print_help_menu(void)
{
    printf("\nAvailable commands:\n"
           "  open NAME                  open card NAME\n"
           "  reset                      reset sound card to default state\n"
           "  reload                     reload configuration\n"
           "  listcards                  list available cards\n"
           "  list IDENTIFIER            list command\n"
           "  get IDENTIFIER             get string value\n"
           "  geti IDENTIFIER            get integer value\n"
           "  set IDENTIFIER VALUE       set string value\n"
           "  help                     help\n"
           "  quit                     quit\n");
}

int main(int argc, char **argv)
{
    char *help_str = "help";
    argc--;
    argv++;

    if (argc > 0) {
        if (!strncmp(argv[0], help_str, strlen(argv[0])))
            print_help_menu();
    } else
	    alsaucm_test_cmd_svr();
    return 0;
}

static int process_cmd(char *cmdStr)
{
    const char **list = NULL , *str = NULL;
    long lval;
    int err, i;
    char *command = NULL;
    int count = 0;
    char *identifier = NULL, *value = NULL;
    struct cmd *cmd = NULL;

    command = strtok_r(cmdStr, " ", &value);
    identifier = strtok_r(NULL, " ", &value);

    if (command == NULL) {
        fprintf(stderr, "NULL pointer encountered. Invalid value for command");
        return -1;
    }

    for (cmd = cmds; cmd->cmd_str != NULL; cmd++) {
        if (strncmp(cmd->cmd_str, command, strlen(cmd->cmd_str)) == 0)
            break;
    }

    if (cmd->cmd_str == NULL) {
        fprintf(stderr, "Unknown command '%s'\n", command);
        return -EINVAL;
    }

    if ((identifier == NULL) && ((cmd->code != UCM_HELP) &&
        (cmd->code != UCM_LISTCARDS) && (cmd->code != UCM_RESET) &&
        (cmd->code != UCM_RELOAD)))
    {
        fprintf(stderr, "NULL pointer encountered. Invalid value for identifier");
        return -1;
    }

    switch (cmd->code) {
    case UCM_HELP:
        print_help_menu();
        break;

    case UCM_OPEN:
        if (uc_mgr) {
            snd_use_case_mgr_close(uc_mgr);
            uc_mgr = NULL;
        }

        err = snd_use_case_mgr_open(&uc_mgr, identifier);
        if (err < 0) {
            fprintf(stderr, "%s: error failed to open sound card %s: %d\n", cmd->cmd_str, identifier, err);
            return err;
        }
        break;

    case UCM_LISTCARDS:
        err = snd_use_case_card_list(&list);
        if (err < 0) {
            fprintf(stderr, "%s: error failed to get card list: %d\n", cmd->cmd_str, err);
            return err;
        }
        if (err == 0) {
            printf("list is empty\n");
            return 0;
        }

        for (i = 0; i < err; i++)
            printf("  %i: %s\n", i+1, list[i]);
        snd_use_case_free_list(list, err);
        break;

    case UCM_RESET:
        if (!uc_mgr) {
            fprintf(stderr, "No card is opened before. %s command can't be executed\n", cmd->cmd_str);
            return -EINVAL;
        }

        err = snd_use_case_mgr_reset(uc_mgr);
        if (err < 0) {
            fprintf(stderr, "%s: error failed to reset sound card %d\n", cmd->cmd_str, err);
            return err;
        }
        break;

    case UCM_RELOAD:
        if (!uc_mgr) {
            fprintf(stderr, "No card is opened before. %s command can't be executed\n", cmd->cmd_str);
            return -EINVAL;
        }

        err = snd_use_case_mgr_reload(uc_mgr);
        if (err < 0) {
            fprintf(stderr, "%s: error failed to reload manager %d\n", cmd->cmd_str, err);
            return err;
        }
        break;

    case UCM_LIST:
        if (!uc_mgr) {
            fprintf(stderr, "No card is opened before. %s command can't be executed\n", cmd->cmd_str);
            return -EINVAL;
        }

        err = snd_use_case_get_list(uc_mgr, identifier, &list);
        if (err < 0) {
            fprintf(stderr, "%s: error failed to get list %s: %d\n", cmd->cmd_str, identifier, err);
            return err;
        }
        if (err == 0) {
           printf("list is empty\n");
           return 0;
        }
        for (i = 0; i < err; i++) {
            printf("  %i: %s\n", i+1, list[i]);
        }
        snd_use_case_free_list(list, err);
        break;

    case UCM_SET:
        if (!uc_mgr) {
            fprintf(stderr, "No card is opened before. %s command can't be executed\n", cmd->cmd_str);
            return -EINVAL;
        }

        err = snd_use_case_set(uc_mgr, identifier, value);
        if (err < 0) {
            fprintf(stderr, "%s: error failed to set %s=%s: %d\n", cmd->cmd_str, identifier, value, err);
            return err;
        }
        break;

    case UCM_GET:
        if (!uc_mgr) {
            fprintf(stderr, "No card is opened before. %s command can't be executed\n", cmd->cmd_str);
            return -EINVAL;
        }

        err = snd_use_case_get(uc_mgr, identifier, &str);
        if (err < 0) {
            fprintf(stderr, "%s: error failed to get %s: %d\n", cmd->cmd_str, identifier, err);
            return err;
        }
        if (str != NULL)
            printf("  %s=%s\n", identifier, str);
        free((void *)str);
        break;

    case UCM_GETI:
        if (!uc_mgr) {
           fprintf(stderr, "No card is opened before. %s command can't be executed\n", cmd->cmd_str);
           return -EINVAL;
        }

        err = snd_use_case_geti(uc_mgr, identifier, &lval);
        if (err < 0) {
            fprintf(stderr, "%s: error failed to get integer %s: %d\n", cmd->cmd_str, identifier, err);
            return lval;
        }
        printf("  %s=%li\n", identifier, lval);
        break;

    default:
        break;
    }
    return 0;
}

