#ifdef _MSC_VER
#   define snprintf _snprintf
#endif

namespace ImGui
{
//    const char* title;
//    if (Begin(title, &Open))
//    {
//        End();
//    }

    void MemoryEditor::Draw(void* mem_data_void, int mem_size, int base_display_addr)
    {
        PushFont(Font::Mono);

        unsigned char* mem_data = (unsigned char*)mem_data_void;

        BeginChild("##scrolling", ImVec2(0, -GetItemsLineHeightWithSpacing()));

        if (ImGui::BeginPopupContextWindow() )
        {
            ImGui::Checkbox("HexII", &HexII);
            ImGui::EndPopup();
        }

        PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f) );
        PushStyleVar(ImGuiStyleVar_ItemSpacing,  ImVec2(0.0f, 0.0f) );

        int addr_digits_count = 0;
        for (int n = base_display_addr + mem_size - 1; n > 0; n >>= 4)
        {
            addr_digits_count++;
        }

        float glyph_width = CalcTextSize("F").x;
        float cell_width = glyph_width * 3; // "FF " we include trailing space in the width to easily catch clicks everywhere

        float line_height = GetTextLineHeight();
        int line_total_count = (int)((mem_size + Rows-1) / Rows);
        ImGuiListClipper clipper(line_total_count, line_height);
        int visible_start_addr = clipper.DisplayStart * Rows;
        int visible_end_addr = clipper.DisplayEnd * Rows;

        bool data_next = false;

        if (!AllowEdits || DataEditingAddr >= mem_size)
        {
            DataEditingAddr = -1;
        }

        int data_editing_addr_backup = DataEditingAddr;
        if (DataEditingAddr != -1)
        {
            if (IsKeyPressed(GetKeyIndex(ImGuiKey_UpArrow)) && DataEditingAddr >= Rows)                    { DataEditingAddr -= Rows; DataEditingTakeFocus = true; }
            else if (IsKeyPressed(GetKeyIndex(ImGuiKey_DownArrow))  && DataEditingAddr < mem_size - Rows)  { DataEditingAddr += Rows; DataEditingTakeFocus = true; }
            else if (IsKeyPressed(GetKeyIndex(ImGuiKey_LeftArrow))  && DataEditingAddr > 0)                { DataEditingAddr -= 1;    DataEditingTakeFocus = true; }
            else if (IsKeyPressed(GetKeyIndex(ImGuiKey_RightArrow)) && DataEditingAddr < mem_size - 1)     { DataEditingAddr += 1;    DataEditingTakeFocus = true; }
        }

        if ((DataEditingAddr / Rows) != (data_editing_addr_backup / Rows))
        {
            // Track cursor movements
            float scroll_offset = ((DataEditingAddr / Rows) - (data_editing_addr_backup / Rows)) * line_height;
            bool scroll_desired = (scroll_offset < 0.0f && DataEditingAddr < visible_start_addr + Rows*2) || (scroll_offset > 0.0f && DataEditingAddr > visible_end_addr - Rows*2);
            if (scroll_desired)
            {
                SetScrollY(GetScrollY() + scroll_offset);
            }
        }

        bool draw_separator = true;
        for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible items
        {
            int addr = line_i * Rows;
            Text("%0*x: ", addr_digits_count, base_display_addr+addr);
            SameLine();

            // Draw Hexadecimal
            float line_start_x = GetCursorPosX();
            for (int n = 0; n < Rows && addr < mem_size; n++, addr++)
            {
                SameLine(line_start_x + cell_width * n);

                if (DataEditingAddr == addr)
                {
                    // Display text input on current byte
                    PushID(addr);
                    struct FuncHolder
                    {
                        // FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious.
                        static int Callback(ImGuiTextEditCallbackData* data)
                        {
                            int* p_cursor_pos = (int*)data->UserData;
                            if (!data->HasSelection())
                            {
                                *p_cursor_pos = data->CursorPos;
                            }
                            return 0;
                        }
                    };
                    int cursor_pos = -1;
                    bool data_write = false;
                    if (DataEditingTakeFocus)
                    {
                        SetKeyboardFocusHere();
                        snprintf(AddrInput, sizeof(AddrInput), "%0*x", addr_digits_count, base_display_addr+addr);
                        snprintf(DataInput, sizeof(DataInput), "%02x", mem_data[addr]);
                    }

                    PushItemWidth(CalcTextSize("FF").x);
                    ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal|ImGuiInputTextFlags_EnterReturnsTrue|ImGuiInputTextFlags_AutoSelectAll|ImGuiInputTextFlags_NoHorizontalScroll|ImGuiInputTextFlags_AlwaysInsertMode|ImGuiInputTextFlags_CallbackAlways;
                    if (InputText("##data", DataInput, 32, flags, FuncHolder::Callback, &cursor_pos))
                    {
                        data_write = data_next = true;
                    }
                    else if (!DataEditingTakeFocus && !IsItemActive())
                    {
                        DataEditingAddr = -1;
                    }

                    DataEditingTakeFocus = false;
                    PopItemWidth();
                    if (cursor_pos >= 2)
                    {
                        data_write = data_next = true;
                    }

                    if (data_write)
                    {
                        int data;
                        if (sscanf(DataInput, "%X", &data) == 1)
                        {
                            mem_data[addr] = (unsigned char)data;
                        }
                    }
                    PopID();
                }
                else
                {
                    if (HexII)
                    {
                        unsigned char byte = mem_data[addr];
                        if (isprint(byte) )
                        {
                            Text(".%c ", byte);
                        }
                        else if (0x00 == byte)
                        {
                            Text("   ");
                        }
                        else if (0xff == byte)
                        {
                            Text("## ");
                        }
                        else
                        {
                            Text("%02x ", byte);
                        }
                    }
                    else
                    {
                        Text("%02x ", mem_data[addr]);
                    }

                    if (AllowEdits && IsItemHovered() && IsMouseClicked(0))
                    {
                        DataEditingTakeFocus = true;
                        DataEditingAddr = addr;
                    }
                }
            }

            SameLine(line_start_x + cell_width * Rows + glyph_width * 2);

            if (draw_separator)
            {
                ImVec2 screen_pos = GetCursorScreenPos();
                GetWindowDrawList()->AddLine(ImVec2(screen_pos.x - glyph_width, screen_pos.y - 9999), ImVec2(screen_pos.x - glyph_width, screen_pos.y + 9999), ImColor(GetStyle().Colors[ImGuiCol_Border]));
                draw_separator = false;
            }

            // Draw ASCII values
            addr = line_i * Rows;
            for (int n = 0; n < Rows && addr < mem_size; n++, addr++)
            {
                if (n > 0) { SameLine(); }
                int c = mem_data[addr];
                Text("%c", (c >= 32 && c < 128) ? c : '.');
            }
        }
        clipper.End();
        PopStyleVar(2);

        EndChild();

        if (data_next && DataEditingAddr < mem_size)
        {
            DataEditingAddr = DataEditingAddr + 1;
            DataEditingTakeFocus = true;
        }

        Separator();

        AlignFirstTextHeightToWidgets();
        PushItemWidth(50);
        PushAllowKeyboardFocus(false);
        int rows_backup = Rows;
        if (DragInt("##rows", &Rows, 0.2f, 4, 32, "%.0f rows"))
        {
            ImVec2 new_window_size = GetWindowSize();
            new_window_size.x += (Rows - rows_backup) * (cell_width + glyph_width);
            SetWindowSize(new_window_size);
        }

        PopAllowKeyboardFocus();
        PopItemWidth();
        SameLine();
        Text("Range %0*x..%0*x", addr_digits_count, (int)base_display_addr, addr_digits_count, (int)base_display_addr+mem_size-1);
        SameLine();
        PushItemWidth(70);
        if (InputText("##addr", AddrInput, 32, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue))
        {
            int goto_addr;
            if (sscanf(AddrInput, "%X", &goto_addr) == 1)
            {
                goto_addr -= base_display_addr;
                if (goto_addr >= 0 && goto_addr < mem_size)
                {
                    BeginChild("##scrolling");
                    SetScrollFromPosY(GetCursorStartPos().y + (goto_addr / Rows) * GetTextLineHeight());
                    EndChild();
                    DataEditingAddr = goto_addr;
                    DataEditingTakeFocus = true;
                }
            }
        }

        PopItemWidth();

        PopFont();
    }

    void MemoryEditor::Draw(const void* mem_data, int mem_size, int base_display_addr)
    {
        Draw(const_cast<void*>(mem_data), mem_size, base_display_addr);
    }

} // namespace ImGui
