/*
 * Copyright (C) 2013 The Android Open Source Project
 * Inspired by TinyHW, written by Mark Brown at Wolfson Micro
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define LOG_TAG "audio_route"
/*#define LOG_NDEBUG 0*/

#include <errno.h>
#include <expat.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>

#include <log/log.h>

#include <tinyalsa/asoundlib.h>

#define BUF_SIZE 1024
#define MIXER_XML_PATH "/system/etc/mixer_paths.xml"
#define INITIAL_MIXER_PATH_SIZE 8

enum update_direction {
    DIRECTION_FORWARD,
    DIRECTION_REVERSE,
    DIRECTION_REVERSE_RESET
};

union ctl_values {
    int *enumerated;
    long *integer;
    void *ptr;
    unsigned char *bytes;
};

struct mixer_state {
    struct mixer_ctl *ctl;
    unsigned int num_values;
    union ctl_values old_value;
    union ctl_values new_value;
    union ctl_values reset_value;
    unsigned int active_count;
};

struct mixer_setting {
    unsigned int ctl_index;
    unsigned int num_values;
    unsigned int type;
    union ctl_values value;
};

struct mixer_value {
    unsigned int ctl_index;
    int index;
    long value;
};

struct mixer_path {
    char *name;
    unsigned int size;
    unsigned int length;
    struct mixer_setting *setting;
};

struct audio_route {
    struct mixer *mixer;
    unsigned int num_mixer_ctls;
    struct mixer_state *mixer_state;

    unsigned int mixer_path_size;
    unsigned int num_mixer_paths;
    struct mixer_path *mixer_path;
};

struct config_parse_state {
    struct audio_route *ar;
    struct mixer_path *path;
    int level;
};

/* path functions */

static bool is_supported_ctl_type(enum mixer_ctl_type type)
{
    switch (type) {
    case MIXER_CTL_TYPE_BOOL:
    case MIXER_CTL_TYPE_INT:
    case MIXER_CTL_TYPE_ENUM:
    case MIXER_CTL_TYPE_BYTE:
        return true;
    default:
        return false;
    }
}

/* as they match in alsa */
static size_t sizeof_ctl_type(enum mixer_ctl_type type) {
    switch (type) {
    case MIXER_CTL_TYPE_BOOL:
    case MIXER_CTL_TYPE_INT:
        return sizeof(long);
    case MIXER_CTL_TYPE_ENUM:
        return sizeof(int);
    case MIXER_CTL_TYPE_BYTE:
        return sizeof(unsigned char);
    case MIXER_CTL_TYPE_INT64:
    case MIXER_CTL_TYPE_IEC958:
    case MIXER_CTL_TYPE_UNKNOWN:
    default:
        LOG_ALWAYS_FATAL("Unsupported mixer ctl type: %d, check type before calling", (int)type);
        return 0;
    }
}

static inline struct mixer_ctl *index_to_ctl(struct audio_route *ar,
                                             unsigned int ctl_index)
{
    return ar->mixer_state[ctl_index].ctl;
}

#if 0
static void path_print(struct audio_route *ar, struct mixer_path *path)
{
    unsigned int i;
    unsigned int j;

    ALOGE("Path: %s, length: %d", path->name, path->length);
    for (i = 0; i < path->length; i++) {
        struct mixer_ctl *ctl = index_to_ctl(ar, path->setting[i].ctl_index);

        ALOGE("  id=%d: ctl=%s", i, mixer_ctl_get_name(ctl));
        if (mixer_ctl_get_type(ctl) == MIXER_CTL_TYPE_BYTE) {
            for (j = 0; j < path->setting[i].num_values; j++)
                ALOGE("    id=%d value=0x%02x", j, path->setting[i].value.bytes[j]);
        } else if (mixer_ctl_get_type(ctl) == MIXER_CTL_TYPE_ENUM) {
            for (j = 0; j < path->setting[i].num_values; j++)
                ALOGE("    id=%d value=%d", j, path->setting[i].value.enumerated[j]);
        } else {
            for (j = 0; j < path->setting[i].num_values; j++)
                ALOGE("    id=%d value=%ld", j, path->setting[i].value.integer[j]);
        }
    }
}
#endif

static void path_free(struct audio_route *ar)
{
    unsigned int i;

    for (i = 0; i < ar->num_mixer_paths; i++) {
        free(ar->mixer_path[i].name);
        if (ar->mixer_path[i].setting) {
            size_t j;
            for (j = 0; j < ar->mixer_path[i].length; j++) {
                free(ar->mixer_path[i].setting[j].value.ptr);
            }
            free(ar->mixer_path[i].setting);
            ar->mixer_path[i].size = 0;
            ar->mixer_path[i].length = 0;
            ar->mixer_path[i].setting = NULL;
        }
    }
    free(ar->mixer_path);
    ar->mixer_path = NULL;
    ar->mixer_path_size = 0;
    ar->num_mixer_paths = 0;
}

static struct mixer_path *path_get_by_name(struct audio_route *ar,
                                           const char *name)
{
    unsigned int i;

    for (i = 0; i < ar->num_mixer_paths; i++)
        if (strcmp(ar->mixer_path[i].name, name) == 0)
            return &ar->mixer_path[i];

    return NULL;
}

static struct mixer_path *path_create(struct audio_route *ar, const char *name)
{
    struct mixer_path *new_mixer_path = NULL;

    if (path_get_by_name(ar, name)) {
        ALOGE("Path name '%s' already exists", name);
        return NULL;
    }

    /* check if we need to allocate more space for mixer paths */
    if (ar->mixer_path_size <= ar->num_mixer_paths) {
        if (ar->mixer_path_size == 0)
            ar->mixer_path_size = INITIAL_MIXER_PATH_SIZE;
        else
            ar->mixer_path_size *= 2;

        new_mixer_path = realloc(ar->mixer_path, ar->mixer_path_size *
                                 sizeof(struct mixer_path));
        if (new_mixer_path == NULL) {
            ALOGE("Unable to allocate more paths");
            return NULL;
        } else {
            ar->mixer_path = new_mixer_path;
        }
    }

    /* initialise the new mixer path */
    ar->mixer_path[ar->num_mixer_paths].name = strdup(name);
    ar->mixer_path[ar->num_mixer_paths].size = 0;
    ar->mixer_path[ar->num_mixer_paths].length = 0;
    ar->mixer_path[ar->num_mixer_paths].setting = NULL;

    /* return the mixer path just added, then increment number of them */
    return &ar->mixer_path[ar->num_mixer_paths++];
}

static int find_ctl_index_in_path(struct mixer_path *path,
                                  unsigned int ctl_index)
{
    unsigned int i;

    for (i = 0; i < path->length; i++)
        if (path->setting[i].ctl_index == ctl_index)
            return i;

    return -1;
}

static int alloc_path_setting(struct mixer_path *path)
{
    struct mixer_setting *new_path_setting;
    int path_index;

    /* check if we need to allocate more space for path settings */
    if (path->size <= path->length) {
        if (path->size == 0)
            path->size = INITIAL_MIXER_PATH_SIZE;
        else
            path->size *= 2;

        new_path_setting = realloc(path->setting,
                                   path->size * sizeof(struct mixer_setting));
        if (new_path_setting == NULL) {
            ALOGE("Unable to allocate more path settings");
            return -1;
        } else {
            path->setting = new_path_setting;
        }
    }

    path_index = path->length;
    path->length++;

    return path_index;
}

static int path_add_setting(struct audio_route *ar, struct mixer_path *path,
                            struct mixer_setting *setting)
{
    int path_index;

    if (find_ctl_index_in_path(path, setting->ctl_index) != -1) {
        struct mixer_ctl *ctl = index_to_ctl(ar, setting->ctl_index);

        ALOGE("Control '%s' already exists in path '%s'",
              mixer_ctl_get_name(ctl), path->name);
        return -1;
    }

    if (!is_supported_ctl_type(setting->type)) {
        ALOGE("unsupported type %d", (int)setting->type);
        return -1;
    }

    path_index = alloc_path_setting(path);
    if (path_index < 0)
        return -1;

    path->setting[path_index].ctl_index = setting->ctl_index;
    path->setting[path_index].type = setting->type;
    path->setting[path_index].num_values = setting->num_values;

    size_t value_sz = sizeof_ctl_type(setting->type);

    path->setting[path_index].value.ptr = calloc(setting->num_values, value_sz);
    /* copy all values */
    memcpy(path->setting[path_index].value.ptr, setting->value.ptr,
           setting->num_values * value_sz);

    return 0;
}

static int path_add_value(struct audio_route *ar, struct mixer_path *path,
                          struct mixer_value *mixer_value)
{
    unsigned int i;
    int path_index;
    unsigned int num_values;
    struct mixer_ctl *ctl;

    /* Check that mixer value index is within range */
    ctl = index_to_ctl(ar, mixer_value->ctl_index);
    num_values = mixer_ctl_get_num_values(ctl);
    if (mixer_value->index >= (int)num_values) {
        ALOGE("mixer index %d is out of range for '%s'", mixer_value->index,
              mixer_ctl_get_name(ctl));
        return -1;
    }

    path_index = find_ctl_index_in_path(path, mixer_value->ctl_index);
    if (path_index < 0) {
        /* New path */

        enum mixer_ctl_type type = mixer_ctl_get_type(ctl);
        if (!is_supported_ctl_type(type)) {
            ALOGE("unsupported type %d", (int)type);
            return -1;
        }
        path_index = alloc_path_setting(path);
        if (path_index < 0)
            return -1;

        /* initialise the new path setting */
        path->setting[path_index].ctl_index = mixer_value->ctl_index;
        path->setting[path_index].num_values = num_values;
        path->setting[path_index].type = type;

        size_t value_sz = sizeof_ctl_type(type);
        path->setting[path_index].value.ptr = calloc(num_values, value_sz);
        if (path->setting[path_index].type == MIXER_CTL_TYPE_BYTE)
            path->setting[path_index].value.bytes[0] = mixer_value->value;
        else if (path->setting[path_index].type == MIXER_CTL_TYPE_ENUM)
            path->setting[path_index].value.enumerated[0] = mixer_value->value;
        else
            path->setting[path_index].value.integer[0] = mixer_value->value;
    }

    if (mixer_value->index == -1) {
        /* set all values the same */
        if (path->setting[path_index].type == MIXER_CTL_TYPE_BYTE) {
            for (i = 0; i < num_values; i++)
                path->setting[path_index].value.bytes[i] = mixer_value->value;
        } else if (path->setting[path_index].type == MIXER_CTL_TYPE_ENUM) {
            for (i = 0; i < num_values; i++)
                path->setting[path_index].value.enumerated[i] = mixer_value->value;
        } else {
            for (i = 0; i < num_values; i++)
                path->setting[path_index].value.integer[i] = mixer_value->value;
        }
    } else {
        /* set only one value */
        if (path->setting[path_index].type == MIXER_CTL_TYPE_BYTE)
            path->setting[path_index].value.bytes[mixer_value->index] = mixer_value->value;
        else if (path->setting[path_index].type == MIXER_CTL_TYPE_ENUM)
            path->setting[path_index].value.enumerated[mixer_value->index] = mixer_value->value;
        else
            path->setting[path_index].value.integer[mixer_value->index] = mixer_value->value;
    }

    return 0;
}

static int path_add_path(struct audio_route *ar, struct mixer_path *path,
                         struct mixer_path *sub_path)
{
    unsigned int i;

    for (i = 0; i < sub_path->length; i++)
        if (path_add_setting(ar, path, &sub_path->setting[i]) < 0)
            return -1;

    return 0;
}

static int path_apply(struct audio_route *ar, struct mixer_path *path)
{
    unsigned int i;
    unsigned int ctl_index;
    struct mixer_ctl *ctl;
    enum mixer_ctl_type type;

    ALOGD("Apply path: %s", path->name != NULL ? path->name : "none");
    for (i = 0; i < path->length; i++) {
        ctl_index = path->setting[i].ctl_index;
        ctl = index_to_ctl(ar, ctl_index);
        type = mixer_ctl_get_type(ctl);
        if (!is_supported_ctl_type(type))
            continue;
        size_t value_sz = sizeof_ctl_type(type);
        memcpy(ar->mixer_state[ctl_index].new_value.ptr, path->setting[i].value.ptr,
                   path->setting[i].num_values * value_sz);
    }

    return 0;
}

static int path_reset(struct audio_route *ar, struct mixer_path *path)
{
    unsigned int i;
    unsigned int ctl_index;
    struct mixer_ctl *ctl;
    enum mixer_ctl_type type;

    ALOGV("Reset path: %s", path->name != NULL ? path->name : "none");
    for (i = 0; i < path->length; i++) {
        ctl_index = path->setting[i].ctl_index;
        ctl = index_to_ctl(ar, ctl_index);
        type = mixer_ctl_get_type(ctl);
        if (!is_supported_ctl_type(type))
            continue;
        size_t value_sz = sizeof_ctl_type(type);
        /* reset the value(s) */
        memcpy(ar->mixer_state[ctl_index].new_value.ptr,
               ar->mixer_state[ctl_index].reset_value.ptr,
               ar->mixer_state[ctl_index].num_values * value_sz);
    }

    return 0;
}

/* mixer helper function */
static int mixer_enum_string_to_value(struct mixer_ctl *ctl, const char *string)
{
    unsigned int i;
    unsigned int num_values = mixer_ctl_get_num_enums(ctl);

    if (string == NULL) {
        ALOGE("NULL enum value string passed to mixer_enum_string_to_value() for ctl %s",
              mixer_ctl_get_name(ctl));
        return 0;
    }

    /* Search the enum strings for a particular one */
    for (i = 0; i < num_values; i++) {
        if (strcmp(mixer_ctl_get_enum_string(ctl, i), string) == 0)
            break;
    }
    if (i == num_values) {
        ALOGE("unknown enum value string %s for ctl %s",
              string, mixer_ctl_get_name(ctl));
        return 0;
    }
    return i;
}

static void start_tag(void *data, const XML_Char *tag_name,
                      const XML_Char **attr)
{
    const XML_Char *attr_name = NULL;
    const XML_Char *attr_id = NULL;
    const XML_Char *attr_value = NULL;
    struct config_parse_state *state = data;
    struct audio_route *ar = state->ar;
    unsigned int i;
    unsigned int ctl_index;
    struct mixer_ctl *ctl;
    long value;
    unsigned int id;
    struct mixer_value mixer_value;
    enum mixer_ctl_type type;

    /* Get name, id and value attributes (these may be empty) */
    for (i = 0; attr[i]; i += 2) {
        if (strcmp(attr[i], "name") == 0)
            attr_name = attr[i + 1];
        if (strcmp(attr[i], "id") == 0)
            attr_id = attr[i + 1];
        else if (strcmp(attr[i], "value") == 0)
            attr_value = attr[i + 1];
    }

    /* Look at tags */
    if (strcmp(tag_name, "path") == 0) {
        if (attr_name == NULL) {
            ALOGE("Unnamed path!");
        } else {
            if (state->level == 1) {
                /* top level path: create and stash the path */
                state->path = path_create(ar, (char *)attr_name);
                if (state->path == NULL)
                    ALOGE("path created failed, please check the path if existed");
            } else {
                /* nested path */
                struct mixer_path *sub_path = path_get_by_name(ar, attr_name);
                if (!sub_path) {
                    ALOGE("unable to find sub path '%s'", attr_name);
                } else if (state->path != NULL) {
                    path_add_path(ar, state->path, sub_path);
                }
            }
        }
    }

    else if (strcmp(tag_name, "ctl") == 0) {
        /* Obtain the mixer ctl and value */
        ctl = mixer_get_ctl_by_name(ar->mixer, attr_name);
        if (ctl == NULL) {
            ALOGE("Control '%s' doesn't exist - skipping", attr_name);
            goto done;
        }

        switch (mixer_ctl_get_type(ctl)) {
        case MIXER_CTL_TYPE_BOOL:
        case MIXER_CTL_TYPE_INT:
            value = strtol((char *)attr_value, NULL, 0);
            break;
        case MIXER_CTL_TYPE_BYTE:
            value = (unsigned char) strtol((char *)attr_value, NULL, 16);
            break;
        case MIXER_CTL_TYPE_ENUM:
            value = mixer_enum_string_to_value(ctl, (char *)attr_value);
            break;
        default:
            value = 0;
            break;
        }

        /* locate the mixer ctl in the list */
        for (ctl_index = 0; ctl_index < ar->num_mixer_ctls; ctl_index++) {
            if (ar->mixer_state[ctl_index].ctl == ctl)
                break;
        }

        if (state->level == 1) {
            /* top level ctl (initial setting) */

            type = mixer_ctl_get_type(ctl);
            if (is_supported_ctl_type(type)) {
                /* apply the new value */
                if (attr_id) {
                    /* set only one value */
                    id = atoi((char *)attr_id);
                    if (id < ar->mixer_state[ctl_index].num_values)
                        if (type == MIXER_CTL_TYPE_BYTE)
                            ar->mixer_state[ctl_index].new_value.bytes[id] = value;
                        else if (type == MIXER_CTL_TYPE_ENUM)
                            ar->mixer_state[ctl_index].new_value.enumerated[id] = value;
                        else
                            ar->mixer_state[ctl_index].new_value.integer[id] = value;
                    else
                        ALOGE("value id out of range for mixer ctl '%s'",
                              mixer_ctl_get_name(ctl));
                } else {
                    /* set all values the same */
                    for (i = 0; i < ar->mixer_state[ctl_index].num_values; i++)
                        if (type == MIXER_CTL_TYPE_BYTE)
                            ar->mixer_state[ctl_index].new_value.bytes[i] = value;
                        else if (type == MIXER_CTL_TYPE_ENUM)
                            ar->mixer_state[ctl_index].new_value.enumerated[i] = value;
                        else
                            ar->mixer_state[ctl_index].new_value.integer[i] = value;
                }
            }
        } else {
            /* nested ctl (within a path) */
            mixer_value.ctl_index = ctl_index;
            mixer_value.value = value;
            if (attr_id)
                mixer_value.index = atoi((char *)attr_id);
            else
                mixer_value.index = -1;
            if (state->path != NULL)
                path_add_value(ar, state->path, &mixer_value);
        }
    }

done:
    state->level++;
}

static void end_tag(void *data, const XML_Char *tag_name)
{
    struct config_parse_state *state = data;
    (void)tag_name;

    state->level--;
}

static int alloc_mixer_state(struct audio_route *ar)
{
    unsigned int i;
    unsigned int num_values;
    struct mixer_ctl *ctl;
    enum mixer_ctl_type type;

    ar->num_mixer_ctls = mixer_get_num_ctls(ar->mixer);
    ar->mixer_state = calloc(ar->num_mixer_ctls, sizeof(struct mixer_state));
    if (!ar->mixer_state)
        return -1;

    for (i = 0; i < ar->num_mixer_ctls; i++) {
        ctl = mixer_get_ctl(ar->mixer, i);
        num_values = mixer_ctl_get_num_values(ctl);

        ar->mixer_state[i].ctl = ctl;
        ar->mixer_state[i].num_values = num_values;
        ar->mixer_state[i].active_count = 0;

        /* Skip unsupported types that are not supported yet in XML */
        type = mixer_ctl_get_type(ctl);

        if (!is_supported_ctl_type(type))
            continue;

        size_t value_sz = sizeof_ctl_type(type);
        ar->mixer_state[i].old_value.ptr = calloc(num_values, value_sz);
        ar->mixer_state[i].new_value.ptr = calloc(num_values, value_sz);
        ar->mixer_state[i].reset_value.ptr = calloc(num_values, value_sz);

        if (type == MIXER_CTL_TYPE_ENUM)
            ar->mixer_state[i].old_value.enumerated[0] = mixer_ctl_get_value(ctl, 0);
        else
            mixer_ctl_get_array(ctl, ar->mixer_state[i].old_value.ptr, num_values);

        memcpy(ar->mixer_state[i].new_value.ptr, ar->mixer_state[i].old_value.ptr,
               num_values * value_sz);
    }

    return 0;
}

static void free_mixer_state(struct audio_route *ar)
{
    unsigned int i;
    enum mixer_ctl_type type;

    for (i = 0; i < ar->num_mixer_ctls; i++) {
        type = mixer_ctl_get_type(ar->mixer_state[i].ctl);
        if (!is_supported_ctl_type(type))
            continue;

        free(ar->mixer_state[i].old_value.ptr);
        free(ar->mixer_state[i].new_value.ptr);
        free(ar->mixer_state[i].reset_value.ptr);
    }

    free(ar->mixer_state);
    ar->mixer_state = NULL;
}

/* Update the mixer with any changed values */
int audio_route_update_mixer(struct audio_route *ar)
{
    unsigned int i;
    unsigned int j;
    struct mixer_ctl *ctl;

    for (i = 0; i < ar->num_mixer_ctls; i++) {
        unsigned int num_values = ar->mixer_state[i].num_values;
        enum mixer_ctl_type type;

        ctl = ar->mixer_state[i].ctl;

        /* Skip unsupported types */
        type = mixer_ctl_get_type(ctl);
        if (!is_supported_ctl_type(type))
            continue;

        /* if the value has changed, update the mixer */
        bool changed = false;
        if (type == MIXER_CTL_TYPE_BYTE) {
            for (j = 0; j < num_values; j++) {
                if (ar->mixer_state[i].old_value.bytes[j] != ar->mixer_state[i].new_value.bytes[j]) {
                    changed = true;
                    break;
                }
            }
         } else if (type == MIXER_CTL_TYPE_ENUM) {
             for (j = 0; j < num_values; j++) {
                 if (ar->mixer_state[i].old_value.enumerated[j]
                         != ar->mixer_state[i].new_value.enumerated[j]) {
                     changed = true;
                     break;
                 }
             }
         } else {
            for (j = 0; j < num_values; j++) {
                if (ar->mixer_state[i].old_value.integer[j] != ar->mixer_state[i].new_value.integer[j]) {
                    changed = true;
                    break;
                }
            }
        }
        if (changed) {
            if (type == MIXER_CTL_TYPE_ENUM)
                mixer_ctl_set_value(ctl, 0, ar->mixer_state[i].new_value.enumerated[0]);
            else
                mixer_ctl_set_array(ctl, ar->mixer_state[i].new_value.ptr, num_values);

            size_t value_sz = sizeof_ctl_type(type);
            memcpy(ar->mixer_state[i].old_value.ptr, ar->mixer_state[i].new_value.ptr,
                   num_values * value_sz);
        }
    }

    return 0;
}

/* saves the current state of the mixer, for resetting all controls */
static void save_mixer_state(struct audio_route *ar)
{
    unsigned int i;
    enum mixer_ctl_type type;

    for (i = 0; i < ar->num_mixer_ctls; i++) {
        type = mixer_ctl_get_type(ar->mixer_state[i].ctl);
        if (!is_supported_ctl_type(type))
            continue;

        size_t value_sz = sizeof_ctl_type(type);
        memcpy(ar->mixer_state[i].reset_value.ptr, ar->mixer_state[i].new_value.ptr,
               ar->mixer_state[i].num_values * value_sz);
    }
}

/* Reset the audio routes back to the initial state */
void audio_route_reset(struct audio_route *ar)
{
    unsigned int i;
    enum mixer_ctl_type type;

    /* load all of the saved values */
    for (i = 0; i < ar->num_mixer_ctls; i++) {
        type = mixer_ctl_get_type(ar->mixer_state[i].ctl);
        if (!is_supported_ctl_type(type))
            continue;

        size_t value_sz = sizeof_ctl_type(type);
        memcpy(ar->mixer_state[i].new_value.ptr, ar->mixer_state[i].reset_value.ptr,
            ar->mixer_state[i].num_values * value_sz);
    }
}

/* Apply an audio route path by name */
int audio_route_apply_path(struct audio_route *ar, const char *name)
{
    struct mixer_path *path;

    if (!ar) {
        ALOGE("invalid audio_route");
        return -1;
    }

    path = path_get_by_name(ar, name);
    if (!path) {
        ALOGE("unable to find path '%s'", name);
        return -1;
    }

    path_apply(ar, path);

    return 0;
}

/* Reset an audio route path by name */
int audio_route_reset_path(struct audio_route *ar, const char *name)
{
    struct mixer_path *path;

    if (!ar) {
        ALOGE("invalid audio_route");
        return -1;
    }

    path = path_get_by_name(ar, name);
    if (!path) {
        ALOGE("unable to find path '%s'", name);
        return -1;
    }

    path_reset(ar, path);

    return 0;
}

/*
 * Operates on the specified path .. controls will be updated in the
 * order listed in the XML file
 */
static int audio_route_update_path(struct audio_route *ar, const char *name, int direction)
{
    struct mixer_path *path;
    unsigned int j;
    bool reverse = direction != DIRECTION_FORWARD;
    bool force_reset = direction == DIRECTION_REVERSE_RESET;

    if (!ar) {
        ALOGE("invalid audio_route");
        return -1;
    }

    path = path_get_by_name(ar, name);
    if (!path) {
        ALOGE("unable to find path '%s'", name);
        return -1;
    }

    for (size_t i = 0; i < path->length; ++i) {
        unsigned int ctl_index;
        enum mixer_ctl_type type;

        ctl_index = path->setting[reverse ? path->length - 1 - i : i].ctl_index;

        struct mixer_state * ms = &ar->mixer_state[ctl_index];

        type = mixer_ctl_get_type(ms->ctl);
        if (!is_supported_ctl_type(type)) {
            continue;
        }

        if (reverse && ms->active_count > 0) {
            if (force_reset)
                ms->active_count = 0;
            else
                ms->active_count--;
        } else if (!reverse) {
            ms->active_count++;
        }

       size_t value_sz = sizeof_ctl_type(type);
        /* if any value has changed, update the mixer */
        for (j = 0; j < ms->num_values; j++) {
            if (type == MIXER_CTL_TYPE_BYTE) {
                if (ms->old_value.bytes[j] != ms->new_value.bytes[j]) {
                    if (reverse && ms->active_count > 0) {
                        ALOGD("%s: skip to reset mixer control '%s' in path '%s' "
                            "because it is still needed by other paths", __func__,
                            mixer_ctl_get_name(ms->ctl), name);
                        memcpy(ms->new_value.bytes, ms->old_value.bytes,
                            ms->num_values * value_sz);
                        break;
                    }
                    mixer_ctl_set_array(ms->ctl, ms->new_value.bytes, ms->num_values);
                    memcpy(ms->old_value.bytes, ms->new_value.bytes, ms->num_values * value_sz);
                    break;
                }
            } else if (type == MIXER_CTL_TYPE_ENUM) {
                if (ms->old_value.enumerated[j] != ms->new_value.enumerated[j]) {
                    if (reverse && ms->active_count > 0) {
                        ALOGD("%s: skip to reset mixer control '%s' in path '%s' "
                            "because it is still needed by other paths", __func__,
                            mixer_ctl_get_name(ms->ctl), name);
                        memcpy(ms->new_value.enumerated, ms->old_value.enumerated,
                            ms->num_values * value_sz);
                        break;
                    }
                    mixer_ctl_set_value(ms->ctl, 0, ms->new_value.enumerated[0]);
                    memcpy(ms->old_value.enumerated, ms->new_value.enumerated,
                            ms->num_values * value_sz);
                    break;
                }
            } else if (ms->old_value.integer[j] != ms->new_value.integer[j]) {
                if (reverse && ms->active_count > 0) {
                    ALOGD("%s: skip to reset mixer control '%s' in path '%s' "
                        "because it is still needed by other paths", __func__,
                        mixer_ctl_get_name(ms->ctl), name);
                    memcpy(ms->new_value.integer, ms->old_value.integer,
                        ms->num_values * value_sz);
                    break;
                }
                mixer_ctl_set_array(ms->ctl, ms->new_value.integer, ms->num_values);
                memcpy(ms->old_value.integer, ms->new_value.integer, ms->num_values * value_sz);
                break;
            }
        }
    }
    return 0;
}

int audio_route_apply_and_update_path(struct audio_route *ar, const char *name)
{
    if (audio_route_apply_path(ar, name) < 0) {
        return -1;
    }
    return audio_route_update_path(ar, name, DIRECTION_FORWARD);
}

int audio_route_reset_and_update_path(struct audio_route *ar, const char *name)
{
    if (audio_route_reset_path(ar, name) < 0) {
        return -1;
    }
    return audio_route_update_path(ar, name, DIRECTION_REVERSE);
}

int audio_route_force_reset_and_update_path(struct audio_route *ar, const char *name)
{
    if (audio_route_reset_path(ar, name) < 0) {
        return -1;
    }

    return audio_route_update_path(ar, name, DIRECTION_REVERSE_RESET);
}

struct audio_route *audio_route_init(unsigned int card, const char *xml_path)
{
    struct config_parse_state state;
    XML_Parser parser;
    FILE *file;
    int bytes_read;
    void *buf;
    struct audio_route *ar;

    ar = calloc(1, sizeof(struct audio_route));
    if (!ar)
        goto err_calloc;

    ar->mixer = mixer_open(card);
    if (!ar->mixer) {
        ALOGE("Unable to open the mixer, aborting.");
        goto err_mixer_open;
    }

    ar->mixer_path = NULL;
    ar->mixer_path_size = 0;
    ar->num_mixer_paths = 0;

    /* allocate space for and read current mixer settings */
    if (alloc_mixer_state(ar) < 0)
        goto err_mixer_state;

    /* use the default XML path if none is provided */
    if (xml_path == NULL)
        xml_path = MIXER_XML_PATH;

    file = fopen(xml_path, "r");

    if (!file) {
        ALOGE("Failed to open %s: %s", xml_path, strerror(errno));
        goto err_fopen;
    }

    parser = XML_ParserCreate(NULL);
    if (!parser) {
        ALOGE("Failed to create XML parser");
        goto err_parser_create;
    }

    memset(&state, 0, sizeof(state));
    state.ar = ar;
    XML_SetUserData(parser, &state);
    XML_SetElementHandler(parser, start_tag, end_tag);

    for (;;) {
        buf = XML_GetBuffer(parser, BUF_SIZE);
        if (buf == NULL)
            goto err_parse;

        bytes_read = fread(buf, 1, BUF_SIZE, file);
        if (bytes_read < 0)
            goto err_parse;

        if (XML_ParseBuffer(parser, bytes_read,
                            bytes_read == 0) == XML_STATUS_ERROR) {
            ALOGE("Error in mixer xml (%s)", MIXER_XML_PATH);
            goto err_parse;
        }

        if (bytes_read == 0)
            break;
    }

    /* apply the initial mixer values, and save them so we can reset the
       mixer to the original values */
    audio_route_update_mixer(ar);
    save_mixer_state(ar);

    XML_ParserFree(parser);
    fclose(file);
    return ar;

err_parse:
    path_free(ar);
    XML_ParserFree(parser);
err_parser_create:
    fclose(file);
err_fopen:
    free_mixer_state(ar);
err_mixer_state:
    mixer_close(ar->mixer);
err_mixer_open:
    free(ar);
    ar = NULL;
err_calloc:
    return NULL;
}

void audio_route_free(struct audio_route *ar)
{
    free_mixer_state(ar);
    mixer_close(ar->mixer);
    path_free(ar);
    free(ar);
}
