/* Parse a controller string like "tcp:127.0.0.1:6633". */
static indigo_error_t
parse_controller(struct controller *controller, cJSON *root)
{
    indigo_cxn_params_tcp_over_ipv4_t *proto;
    char *proto_str, *ip;
    int port;
    int listen;
    int prio;
    indigo_error_t err;

    err = ind_cfg_lookup_string(root, "ip_addr", &ip);
    if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'ip_addr' must be a string");
        } else if (err == INDIGO_ERROR_NOT_FOUND) {
            AIM_LOG_ERROR("Config: Missing 'ip_addr' in controller spec");
        }
        return err;
    }

    err = ind_cfg_lookup_string(root, "protocol", &proto_str);
    if (err == INDIGO_ERROR_NOT_FOUND) {
        /* Allow default protocol value */
        proto_str = "tcp";
    } else if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'protocol' must be a string");
        }
        return err;
    }

    /* @TODO support more protocol types */
    if (strcmp(proto_str, "tcp")) {
        AIM_LOG_ERROR("Config: Invalid controller protocol: %s", proto_str);
        return INDIGO_ERROR_PARAM;
    }

    err = ind_cfg_lookup_int(root, "port", &port);
    if (err == INDIGO_ERROR_NOT_FOUND) {
        /* Allow default protocol value */
        port = 6633;
    } else if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'port' must be an integer");
        }
        return err;
    }

    if (port < 0 || port >= 0xffff) {
        AIM_LOG_ERROR("Config: Invalid controller port: %d", port);
        return INDIGO_ERROR_PARAM;
    }

    err = ind_cfg_lookup_bool(root, "listen", &listen);
    if (err == INDIGO_ERROR_NOT_FOUND) {
        listen = 0;
    } else if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'listen' must be a boolean");
        }
        return err;
    }

    err = ind_cfg_lookup_int(root, "priority", &prio);
    if (err == INDIGO_ERROR_NOT_FOUND) {
        prio = 0;
    } else if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'priority' must be an integer");
        }
        return err;
    }

    /* TODO validate IP */

    proto = &controller->proto.tcp_over_ipv4;
    proto->protocol = INDIGO_CXN_PROTO_TCP_OVER_IPV4;
    strncpy(proto->controller_ip, ip, sizeof(proto->controller_ip));
    proto->controller_port = port;
    controller->config.listen = listen;
    controller->config.cxn_priority = prio;
    controller->config.local = 0;
    controller->config.version = OFCONNECTIONMANAGER_CONFIG_OF_VERSION;

    return INDIGO_ERROR_NONE;
}
/* Parse a controller string like "tcp:127.0.0.1:6633". */
static indigo_error_t
parse_controller(struct controller *controller, cJSON *root)
{
    indigo_cxn_protocol_t proto;
    indigo_cxn_params_tcp_over_ipv4_t *params4;
    indigo_cxn_params_tcp_over_ipv6_t *params6;
    indigo_cxn_params_unix_t *params_unix;
    char *proto_str = NULL, *ip = NULL, *unix_path = NULL;
    int port;
    int listen;
    int local;
    int prio;
    indigo_error_t err;

    err = ind_cfg_lookup_string(root, "protocol", &proto_str);
    if (err == INDIGO_ERROR_NOT_FOUND) {
        /* Allow default protocol value */
        proto_str = "tcp";
    } else if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'protocol' must be a string");
        }
        return err;
    }

    if (strcmp(proto_str, "tcp") == 0) {
        proto = INDIGO_CXN_PROTO_TCP_OVER_IPV4;
    } else if (strcmp(proto_str, "tcp6") == 0) {
        proto = INDIGO_CXN_PROTO_TCP_OVER_IPV6;
    } else if (strcmp(proto_str, "tls") == 0) {
        proto = INDIGO_CXN_PROTO_TLS_OVER_IPV4;
    } else if (strcmp(proto_str, "tls6") == 0) {
        proto = INDIGO_CXN_PROTO_TLS_OVER_IPV6;
    } else if (strcmp(proto_str, "unix") == 0) {
        proto = INDIGO_CXN_PROTO_UNIX;
    } else {
        AIM_LOG_ERROR("Config: Invalid controller protocol: %s", proto_str);
        return INDIGO_ERROR_PARAM;
    }

    switch(proto) {
    case INDIGO_CXN_PROTO_TCP_OVER_IPV4:  /* fall-through */
    case INDIGO_CXN_PROTO_TCP_OVER_IPV6:  /* fall-through */
    case INDIGO_CXN_PROTO_TLS_OVER_IPV4:  /* fall-through */
    case INDIGO_CXN_PROTO_TLS_OVER_IPV6:
        err = ind_cfg_lookup_string(root, "ip_addr", &ip);
        if (err < 0) {
            if (err == INDIGO_ERROR_PARAM) {
                AIM_LOG_ERROR("Config: 'ip_addr' must be a string");
            } else if (err == INDIGO_ERROR_NOT_FOUND) {
                AIM_LOG_ERROR("Config: Missing 'ip_addr' in controller spec");
            }
            return err;
        }
        /*
         * WARNING: no validity checks for IP address.
         * Instead, they are performed by the cli front-end.
         */

        err = ind_cfg_lookup_int(root, "port", &port);
        if (err == INDIGO_ERROR_NOT_FOUND) {
            /* Allow default protocol value */
            port = 6633;
        } else if (err < 0) {
            if (err == INDIGO_ERROR_PARAM) {
                AIM_LOG_ERROR("Config: 'port' must be an integer");
            }
            return err;
        }
        if (port < 0 || port >= 0xffff) {
            AIM_LOG_ERROR("Config: Invalid controller port: %d", port);
            return INDIGO_ERROR_PARAM;
        }

        break;

    case INDIGO_CXN_PROTO_UNIX:
        err = ind_cfg_lookup_string(root, "unix_path", &unix_path);
        if (err < 0) {
            if (err == INDIGO_ERROR_PARAM) {
                AIM_LOG_ERROR("Config: 'unix_path' must be a string");
            } else if (err == INDIGO_ERROR_NOT_FOUND) {
                AIM_LOG_ERROR("Config: Missing 'unix_path' in controller spec");
            }
            return err;
        }
        break;

    default:
        AIM_DIE("Config: No handling for protocol %d", proto);
    }

    err = ind_cfg_lookup_bool(root, "listen", &listen);
    if (err == INDIGO_ERROR_NOT_FOUND) {
        listen = 0;
    } else if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'listen' must be a boolean");
        }
        return err;
    }

    err = ind_cfg_lookup_bool(root, "local", &local);
    if (err == INDIGO_ERROR_NOT_FOUND) {
        local = 0;
    } else if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'local' must be a boolean");
        }
        return err;
    }

    err = ind_cfg_lookup_int(root, "priority", &prio);
    if (err == INDIGO_ERROR_NOT_FOUND) {
        prio = 0;
    } else if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'priority' must be an integer");
        }
        return err;
    }

    /* populate 'controller' struct */
    switch (proto) {
    case INDIGO_CXN_PROTO_TCP_OVER_IPV4:  /* fall-through */
    case INDIGO_CXN_PROTO_TLS_OVER_IPV4:
        params4 = &controller->proto.tcp_over_ipv4;
        params4->protocol = proto;
        strncpy(params4->controller_ip, ip, sizeof(params4->controller_ip));
        params4->controller_port = port;
        break;
    case INDIGO_CXN_PROTO_TCP_OVER_IPV6:  /* fall-through */
    case INDIGO_CXN_PROTO_TLS_OVER_IPV6:
        params6 = &controller->proto.tcp_over_ipv6;
        params6->protocol = proto;
        strncpy(params6->controller_ip, ip, sizeof(params6->controller_ip));
        params6->controller_port = port;
        break;
    case INDIGO_CXN_PROTO_UNIX:
        params_unix = &controller->proto.unx;
        params_unix->protocol = proto;
        strncpy(params_unix->unix_path, unix_path, INDIGO_CXN_UNIX_PATH_LEN);
        break;
    default:
        AIM_DIE("Config: No handling for protocol %d", proto);
    }

    controller->config.listen = listen;
    controller->config.cxn_priority = prio;
    controller->config.local = local;
    controller->config.version = OFCONNECTIONMANAGER_CONFIG_OF_VERSION;

    if (!local) {
        controller->config.periodic_echo_ms = staged_config.keepalive_period_ms;
        controller->config.reset_echo_count = 3;
    } else {
        controller->config.periodic_echo_ms = 0;
        controller->config.reset_echo_count = 0;
    }

    return INDIGO_ERROR_NONE;
}