/*******************************************************************************
 * Copyright (c) 2013, 2016 Wind River Systems, Inc. and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v1.0 which accompany this distribution.
 * The Eclipse Public License is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 * You may elect to redistribute this code under either of these licenses.
 *
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/

#include <tcf/config.h>
#if ENABLE_ContextMux
#include <tcf/framework/context.h>
#include <tcf/framework/context-dispatcher.h>
#include <assert.h>

typedef struct ContextExtensionMux {
    ContextIf * ctx_iface; /* context interface */
} ContextExtensionMux;

#define EXT(ctx) ((ContextExtensionMux *)((char *)(ctx) + context_extension_offset))

#if ENABLE_ExtendedMemoryErrorReports
/* Last memory access error info */
static MemoryErrorInfo mem_err_info;
static int get_err_info_errno;
#endif

static size_t context_extension_offset = 0;

/* Set memory error info structure. Each underlying context (TOS, physical context) handles its
 * own memory error info structure; we use the context dispatcher to provide a unique source for
 * memory error information (we get the error from underlying context and set the global memory
 * error).
 */

#if ENABLE_ExtendedMemoryErrorReports
int set_dispatcher_mem_error_info(MemoryErrorInfo * info) {
    if (info->error == 0) {
        memset(&mem_err_info, 0, sizeof(mem_err_info));
        return 0;
    }
    /* Errno must already have been set by caller */
    assert(errno == info->error);
    get_err_info_errno = 0;
    mem_err_info = *info;
    return -1;
}

static int set_mem_error_info_unsupported(size_t size) {
    get_err_info_errno = 0;
    memset(&mem_err_info, 0, sizeof(mem_err_info));
    mem_err_info.error = errno = ERR_UNSUPPORTED;
    mem_err_info.size_error = size;
    return -1;
}

static int set_mem_error_info(ContextExtensionMux * ext, int rc) {
    int error = errno;
    get_err_info_errno = 0;
    memset(&mem_err_info, 0, sizeof(mem_err_info));
    if (rc < 0) {
        if (ext->ctx_iface->context_get_mem_error_info != NULL &&
                ext->ctx_iface->context_get_mem_error_info(&mem_err_info) < 0) {
            get_err_info_errno = errno;
        }
    }
    errno = error;
    return rc;
}
#else
#  define set_mem_error_info(ext, rc) rc
#  define set_mem_error_info_unsupported(size) -1
#endif

const char * context_suspend_reason(Context * ctx) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_suspend_reason == NULL) return NULL;
    return ext->ctx_iface->context_suspend_reason(ctx);
}

int context_has_state(Context * ctx) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_has_state == NULL) return 0;
    return ext->ctx_iface->context_has_state(ctx);
}

int context_stop(Context * ctx) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_stop == NULL) {
        errno = ERR_UNSUPPORTED;
        return -1;
    }
    return ext->ctx_iface->context_stop(ctx);
}

int context_continue(Context * ctx) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_continue == NULL) {
        return context_resume(ctx, RM_RESUME, 0, 0);
    }
    return ext->ctx_iface->context_continue(ctx);
}

int context_resume(Context * ctx, int mode, ContextAddress range_start, ContextAddress range_end) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_resume == NULL) {
        errno = ERR_UNSUPPORTED;
        return -1;
    }
    return ext->ctx_iface->context_resume(ctx, mode, range_start, range_end);
}

int context_can_resume(Context * ctx, int mode) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_can_resume == NULL) return 0;
    return ext->ctx_iface->context_can_resume(ctx, mode);
}

int context_single_step(Context * ctx) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_single_step == NULL) {
        return context_resume(ctx, RM_STEP_INTO, 0, 0);
    }
    return ext->ctx_iface->context_single_step(ctx);
}

int context_write_mem(Context * ctx, ContextAddress address, void * buf, size_t size) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_write_mem == NULL) return set_mem_error_info_unsupported(size);
    return set_mem_error_info(ext, ext->ctx_iface->context_write_mem(ctx, address, buf, size));
}

int context_read_mem(Context * ctx, ContextAddress address, void * buf, size_t size) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_read_mem == NULL) return set_mem_error_info_unsupported(size);
    return set_mem_error_info(ext, ext->ctx_iface->context_read_mem(ctx, address, buf, size));
}

#if ENABLE_MemoryAccessModes
int context_write_mem_ext(Context * ctx, MemoryAccessMode * mode, ContextAddress address, void * buf, size_t size) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_write_mem_ext == NULL) return set_mem_error_info_unsupported(size);
    return set_mem_error_info(ext, ext->ctx_iface->context_write_mem_ext(ctx, mode, address, buf, size));
}

int context_read_mem_ext(Context * ctx, MemoryAccessMode * mode, ContextAddress address, void * buf, size_t size) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_read_mem_ext == NULL) return set_mem_error_info_unsupported(size);
    return set_mem_error_info(ext, ext->ctx_iface->context_read_mem_ext(ctx, mode, address, buf, size));
}
#endif

unsigned context_word_size(Context * ctx) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_word_size == NULL) return 0;
    return ext->ctx_iface->context_word_size(ctx);
}

int context_read_reg(Context * ctx, RegisterDefinition * def, unsigned offs, unsigned size, void * buf) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_read_reg == NULL) {
        errno = ERR_UNSUPPORTED;
        return -1;
    }
    return ext->ctx_iface->context_read_reg(ctx, def, offs, size, buf);
}

int context_write_reg(Context * ctx, RegisterDefinition * def, unsigned offs, unsigned size, void * buf) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_write_reg == NULL) {
        errno = ERR_UNSUPPORTED;
        return -1;
    }
    return ext->ctx_iface->context_write_reg(ctx, def, offs, size, buf);
}

Context * context_get_group(Context * ctx, int group) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_get_group == NULL) {
        if (group == CONTEXT_GROUP_BREAKPOINT) return NULL;
        return ctx;
    }
    return ext->ctx_iface->context_get_group(ctx, group);
}

int context_get_canonical_addr(Context * ctx, ContextAddress addr, Context ** canonical_ctx,
        ContextAddress * canonical_addr, ContextAddress * block_addr, ContextAddress * block_size) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_get_canonical_addr == NULL) {
        errno = ERR_UNSUPPORTED;
        return -1;
    }
    return ext->ctx_iface->context_get_canonical_addr(ctx, addr, canonical_ctx, canonical_addr,
            block_addr, block_size);
}

int context_get_memory_map(Context * ctx, MemoryMap * map) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_get_memory_map == NULL) return 0;
    return ext->ctx_iface->context_get_memory_map(ctx, map);
}

int context_unplant_breakpoint(ContextBreakpoint * bp) {
    ContextExtensionMux * ext = EXT(bp->ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_unplant_breakpoint == NULL) {
        errno = ERR_UNSUPPORTED;
        return -1;
    }
    return ext->ctx_iface->context_unplant_breakpoint(bp);
}

int context_plant_breakpoint(ContextBreakpoint * bp) {
    ContextExtensionMux * ext = EXT(bp->ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_plant_breakpoint == NULL) {
        errno = ERR_UNSUPPORTED;
        return -1;
    }
    return ext->ctx_iface->context_plant_breakpoint(bp);
}

int context_get_supported_bp_access_types(Context * ctx) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_get_supported_bp_access_types == NULL) return 0;
    return ext->ctx_iface->context_get_supported_bp_access_types(ctx);
}

#if ENABLE_ContextStateProperties
int context_get_state_properties(Context * ctx, const char *** names, const char *** values, int * cnt) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_get_state_properties == NULL) {
        *cnt = 0;
        return 0;
    }
    return (ext->ctx_iface->context_get_state_properties(ctx, names, values, cnt));
}
#endif

#if ENABLE_ExtendedBreakpointStatus
int context_get_breakpoint_status(ContextBreakpoint * bp, const char *** names, const char *** values, int * cnt) {
    ContextExtensionMux * ext = EXT(bp->ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_get_breakpoint_status == NULL) {
        *cnt = 0;
        return 0;
    }
    return ext->ctx_iface->context_get_breakpoint_status(bp, names, values, cnt);
}
#endif

#if ENABLE_ContextBreakpointCapabilities
int context_get_breakpoint_capabilities(Context * ctx, const char *** names, const char *** values, int * cnt) {
    ContextExtensionMux * ext = ctx ? EXT(ctx) : NULL;
    if (ext == NULL || ext->ctx_iface == NULL || ext->ctx_iface->context_get_breakpoint_capabilities == NULL) {
        *cnt = 0;
        return 0;
    }
    return ext->ctx_iface->context_get_breakpoint_capabilities(ctx, names, values, cnt);
}
#endif

#if ENABLE_ContextMemoryProperties
int context_get_memory_properties(Context * ctx, const char *** names, const char *** values, int * cnt) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_get_memory_properties == NULL) {
        *cnt = 0;
        return 0;
    }
    return ext->ctx_iface->context_get_memory_properties(ctx, names, values, cnt);
}
#endif

#if ENABLE_ContextExtraProperties
int context_get_extra_properties(Context * ctx, const char *** names, const char *** values, int * cnt) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_get_extra_properties == NULL) {
        *cnt = 0;
        return 0;
    }
    return ext->ctx_iface->context_get_extra_properties(ctx, names, values, cnt);
}
#endif

#if ENABLE_ContextISA
int context_get_isa(Context * ctx, ContextAddress addr, ContextISA * isa) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->context_get_isa == NULL) {
        errno = ERR_UNSUPPORTED;
        return -1;
    }
    return ext->ctx_iface->context_get_isa(ctx, addr, isa);
}
#endif

#if ENABLE_ExtendedMemoryErrorReports
int context_get_mem_error_info(MemoryErrorInfo * info) {
    if (get_err_info_errno != 0) {
        errno = get_err_info_errno;
        return -1;
    }
    if (mem_err_info.error == 0) {
        set_errno(ERR_OTHER, "Extended memory error info not available");
        return -1;
    }
    *info = mem_err_info;
    return 0;
}
#endif

RegisterDefinition * get_reg_definitions(Context * ctx) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->cpudefs_if.get_reg_definitions == NULL) return NULL;
    return ext->ctx_iface->cpudefs_if.get_reg_definitions(ctx);
}

RegisterDefinition * get_PC_definition(Context * ctx) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->cpudefs_if.get_PC_definition == NULL) return NULL;
    return ext->ctx_iface->cpudefs_if.get_PC_definition(ctx);
}

RegisterDefinition * get_reg_by_id(Context * ctx, unsigned id, RegisterIdScope * scope) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->cpudefs_if.get_reg_by_id == NULL) return NULL;
    return ext->ctx_iface->cpudefs_if.get_reg_by_id(ctx, id, scope);
}

int read_reg_bytes(StackFrame * frame, RegisterDefinition * reg_def, unsigned offs, unsigned size, uint8_t * buf) {
    ContextExtensionMux * ext = EXT(frame->ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->cpudefs_if.read_reg_bytes == NULL) {
        errno = ERR_UNSUPPORTED;
        return -1;
    }
    return ext->ctx_iface->cpudefs_if.read_reg_bytes(frame, reg_def, offs, size, buf);
}

int write_reg_bytes(StackFrame * frame, RegisterDefinition * reg_def, unsigned offs, unsigned size, uint8_t * buf) {
    ContextExtensionMux * ext = EXT(frame->ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->cpudefs_if.write_reg_bytes == NULL) {
        errno = ERR_UNSUPPORTED;
        return -1;
    }
    return ext->ctx_iface->cpudefs_if.write_reg_bytes(frame, reg_def, offs, size, buf);
}

uint8_t * get_break_instruction(Context * ctx, size_t * size) {
    ContextExtensionMux * ext = EXT(ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->cpudefs_if.get_break_instruction == NULL) return NULL;
    return ext->ctx_iface->cpudefs_if.get_break_instruction(ctx, size);
}

#if ENABLE_StackCrawlMux
int crawl_stack_frame(StackFrame * frame, StackFrame * down) {
    ContextExtensionMux * ext = EXT(frame->ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->cpudefs_if.crawl_stack_frame == NULL) return 0;
    return ext->ctx_iface->cpudefs_if.crawl_stack_frame(frame, down);
}
#endif

#if ENABLE_StackRegisterLocations
int write_reg_location(StackFrame * frame, RegisterDefinition * reg_def, LocationExpressionCommand * cmds, unsigned cmds_cnt) {
    ContextExtensionMux * ext = EXT(frame->ctx);
    if (ext->ctx_iface == NULL || ext->ctx_iface->cpudefs_if.write_reg_location == NULL) {
        errno = ERR_UNSUPPORTED;
        return -1;
    }
    return ext->ctx_iface->cpudefs_if.write_reg_location(frame, reg_def, cmds, cmds_cnt);
}
#endif

ContextIf * context_get_interface(Context * ctx) {
    return EXT(ctx)->ctx_iface;
}

int context_set_interface(Context * ctx, ContextIf * ctx_iface) {
    EXT(ctx)->ctx_iface = ctx_iface;
    return 0;
}

void ini_context_dispatcher(void) {
    context_extension_offset = context_extension(sizeof(struct ContextExtensionMux));
}
#endif /* ENABLE_ContextMux */
