::requires 'ooSQLite.cls'
::requires "rxunixsys" LIBRARY
::requires 'ncurses.cls'
::requires 'app/utils.rex'

::class AddressBookUI public

    ::method init
      expose win mainMenu db
      use arg db
      return

    ::method initialize
      expose win mainMenu db

      win = self~DrawMainPanel(0, .true) /* default colour, draw border box */
      self~setupMainMenu(win)
      return

    ::METHOD DrawMainPanel
        use arg clrset, with_box
        win = .window~new()                    /* The first invocation must be with no arguments */
        win~curs_set(0)                        /* Hide the cursor by default                     */
        win~keypad(1)                          /* Enable function keys and arrow keys            */
        win~raw
        win~noecho
        win~clear
        win~cbreak

        self~SetPanelColor(win, clrset)

        if with_box = .true then DO
            win~box(0, 0) /* Draw box with default ACS characters */
            win~refresh
        END
    return win

    ::METHOD DrawSubPanel
        use arg height, width, starty, startx, clrset, title, with_box
        new_win = .window~new(height, width, starty, startx)
        new_win~curs_set(0)

        /* Set the panel color */
        self~SetPanelColor(new_win, clrset)

        /* Display title */
        new_win~attron(new_win~A_BOLD)
        new_win~mvaddstr(starty-(starty-2), (width - title~length) % 2, title)
        new_win~attroff(new_win~A_BOLD)
        new_win~refresh
        if with_box = .true then DO
            new_win~box(0,0)
            new_win~refresh
        END
    return new_win

    ::METHOD SetPanelColor
        use arg panel, clrset

        panel~start_color()
        SELECT
            /* white text on blue background */
            when clrset = 1 then panel~init_pair(2, panel~COLOR_WHITE, panel~COLOR_BLUE)
            /* white text on grey background */
            when clrset = 2 then panel~init_pair(3, panel~COLOR_WHITE, panel~COLOR_GREEN)
            /* yellow text on red background */
            when clrset = 3 then panel~init_pair(4, panel~COLOR_YELLOW, panel~COLOR_RED)
            /* black text on white background */
            when clrset = 4 then panel~init_pair(5, panel~COLOR_BLACK, panel~COLOR_WHITE)
            /* black text on green background */
            when clrset = 5 then panel~init_pair(6, panel~COLOR_BLACK, panel~COLOR_GREEN)
            /* black text on cyan background */
            when clrset = 6 then panel~init_pair(7, panel~COLOR_BLACK, panel~COLOR_CYAN)
            /* Yellow text on blue background */
            when clrset = 7 then panel~init_pair(8, panel~COLOR_YELLOW, panel~COLOR_BLUE)
            /* Cyan text on black background */
            when clrset = 8 then panel~init_pair(9, panel~COLOR_CYAN, panel~COLOR_BLACK)
            /* blacken everything to give the appearance of disappearance */
            when clrset = 9 then panel~init_pair(10, panel~COLOR_BLACK, panel~COLOR_BLACK)
            /* Default to white text on black background */
            OTHERWISE DO
                clrset=0
                panel~init_pair(1, panel~COLOR_WHITE, panel~COLOR_BLACK)
            END
        END
        panel~bkgd(panel~color_pair(clrset+1))
        panel~refresh
    RETURN

    /***********************
     * SETUP MAIN MENU     *
     ***********************/
    ::method setupMainMenu
        expose mainMenu menuwin menu_items menu_keys selected
        use arg win

        max_y = win~lines
        max_x = win~cols

        menu_height = 21
        menu_width = 40
        start_y = (max_y - menu_height) % 2
        start_x = (max_x - menu_width) % 2
        clrset=1
        title = "Rexx Address Book"
        with_box = .true

        menuwin = self~DrawSubPanel(menu_height, menu_width, start_y, start_x, clrset, title, with_box)
        menu_items = .array~of("[A]dd Contact", "[D]elete Contact", "[E]dit Contact", "[S]earch", "[L]ist All", "[Q]uit")
        menu_keys = .array~of("a", "d", "e", "s", "l", "q")

        .environment~selected = 1
        self~DrawMenu(menuwin, menu_items, .environment~selected, win)
        menuwin~refresh
    RETURN

    /***********************
     * DRAW MENU           *
     ***********************/
    ::method DrawMenu
      expose win menu_items menu_keys selected mainwin
      use arg menuwin, items, selected, mainwin

      do i = 1 to items~items
        if i = .environment~selected then do
          menuwin~attron(menuwin~attron(menuwin~A_REVERSE))
          menuwin~attron(menuwin~A_BOLD)
          menuwin~mvaddstr(i+3, 2, items[i])
          menuwin~attroff(menuwin~A_REVERSE)
          menuwin~attroff(menuwin~A_BOLD)
        end
        else do
          menuwin~mvaddstr(i+3, 2, items[i])
        end
      end
      menuwin~refresh
      return

    /***********************
     * DROP WINDOW         *
     ***********************/
    ::method dropWindow
        expose win menuwin
        use arg window
        self~SetPanelColor(window, 9)
        window~erase()
        window~delwin()
        win~refresh
        self~setupMainMenu(win)
        menuwin~refresh()
        RETURN

     /**********************
     * Add new contact     *
     **********************/
    ::method addContactPanel
        expose win db menuwin formwin

        /* Create a form panel */
        max_y = win~lines
        max_x = win~cols
        form_height = 15
        form_width = 60
        start_y = (max_y - form_height) % 2
        start_x = (max_x - form_width) % 2
        formwin = self~DrawSubPanel(form_height, form_width, start_y, start_x, 0, "Add New Contact", .true)

        /* Create form fields */
        formwin~mvaddstr(3, 2, "First Name: ")
        formwin~mvaddstr(4, 2, "Last Name: ")
        formwin~mvaddstr(5, 2, "Phone: "); formwin~mvaddstr(5, 42, "Type: ");
        formwin~mvaddstr(6, 2, "Email: "); formwin~mvaddstr(6, 42, "Type: ");
        formwin~mvaddstr(7, 2, "Street: ")
        formwin~mvaddstr(8, 2, "City: "); formwin~mvaddstr(8, 28, "State: "); formwin~mvaddstr(8, 40, "PostCode: ")
        formwin~mvaddstr(form_height-2, 2, "[Enter] to save, [Esc] to cancel")
        formwin~refresh()

        /* Input fields */
        firstName = self~getInputField(formwin, 3, 14, 30)
        if firstName = .nil then do
            self~dropWindow(formwin)
            RETURN
            END
        lastName = self~getInputField(formwin, 4, 14, 30)
        if lastName = .nil then do
            self~dropWindow(formwin)
            RETURN
            END
        phone = self~getInputField(formwin, 5, 10, 15)
        if phone = .nil then do
            self~dropWindow(formwin)
            RETURN
            END
        phone_type = self~getInputField(formwin, 5, 48, 15)
        if phone_type = .nil then do
            self~dropWindow(formwin)
            RETURN
            END
        email = self~getInputField(formwin, 6, 10, 30)
        if email = .nil then do
            self~dropWindow(formwin)
            RETURN
            END
        email_type = self~getInputField(formwin, 6, 48, 15)
        if email_type = .nil then do
            self~dropWindow(formwin)
            RETURN
            END

        street = self~getInputField(formwin, 7, 10, 25)
        if street = .nil then do
            self~dropWindow(formwin)
            RETURN
            END
        city = self~getInputField(formwin, 8, 10, 15)
        if city = .nil then do
            self~dropWindow(formwin)
            RETURN
            END
        state = self~getInputField(formwin, 8, 35, 2)
        if state = .nil then do
            self~dropWindow(formwin)
            RETURN
            END
        postCode = self~getInputField(formwin, 8, 50, 10)
        if postCode = .nil then do
            self~dropWindow(formwin)
            RETURN
            END

        if firstname = "" | lastname = "" then do
            formwin~mvaddstr(8, 2, "First and Last names are required.")
            formwin~refresh()
            call SysWait 0.5
        END /* don't add contact */
        else DO
            /* Add to database */
            contactDict = .Directory~new()
            contactDict["FIRST_NAME"] = firstName
            contactDict["LAST_NAME"] = lastName
            contactDict["PHONE_NUMBER"] = phone
            contactDict["PHONE_TYPE"] = phone_type
            contactDict["EMAIL_ADDRESS"] = email
            contactDict["EMAIL_TYPE"] = email_type
            contactDict["STREET"] = street
            contactDict["CITY"] = city
            contactDict["STATE"] = state
            contactDict["POSTCODE"] = postCode
            result = db~addContact(contactDict)

            /* Display result message */
            if result > 0 then do /* should be a result id number */
                formwin~mvaddstr(10, 2, "Contact ID ["result"] added successfully!")
                formwin~refresh()
                call SysWait 0.5
            end
            else do
                formwin~mvaddstr(10, 2, "Failed to add contact.")
                formwin~refresh()
                call SysWait 0.5
            end
        end /* add contact */
        self~dropWindow(formwin)
    return

    /****************************
     * Delete Contact Panel     *
     ****************************/
    ::METHOD deleteContactPanel
        expose win db menuwin delwin

        /* Create a form panel */
        max_y = win~lines
        max_x = win~cols
        form_height = 14
        form_width = 35
        start_y = (max_y - form_height) % 2
        start_x = (max_x - form_width) % 2
        delwin = self~DrawSubPanel(form_height, form_width, start_y, start_x, 0, "Delete A Contact", .true)

        delwin~mvaddstr(3, 2, "Contact ID: ")
        delwin~mvaddstr(form_height-2, 2, "[Enter] to save, [Esc] to cancel")
        delwin~refresh()

        contactId = self~getInputField(delwin, 3, 14, 10)
        if contactId = .nil then do
            self~dropWindow(delwin)
            RETURN
            END

        result = db~deleteContact(contactId)

        if result = 0 then do /* should be a result id number */
            delwin~mvaddstr(8, 2, "Contact deleted successfully!")
            delwin~refresh()
            call SysWait 0.5
        end
        else do
            delwin~mvaddstr(8, 2, "Failed to add contact.")
            delwin~refresh()
            call SysWait 0.5
        end
        self~dropWindow(delwin)
    RETURN

    /****************************
     * List all contacts        *
     ****************************/
    ::method listAllContactsPanel
        expose win db menuwin

        /* Create a list panel */
        max_y = win~lines
        max_x = win~cols
        list_height = max_y - 5
        list_width = max_x - 23
        start_y = (max_y - list_height) % 2
        start_x = (max_x - list_width) % 2

        listwin = self~DrawSubPanel(list_height, list_width, start_y, start_x, 1, "All Contacts", .true)
        listwin~scrollok(.true)
        listwin~Setscrreg(4,18)

        /* Display column headers */
        listwin~attron(listwin~A_BOLD)
        listwin~mvaddstr(4, 2, "ID")
        listwin~mvaddstr(4, 6, "First Name")
        listwin~mvaddstr(4, 18, "Last Name")
        listwin~mvaddstr(4, 30, "Phone")
        listwin~mvaddstr(4, 50, "Email")
        listwin~mvaddstr(5, 2, "--  ----------  ---------   ---------------     -------------------------")
        listwin~attroff(listwin~A_BOLD)

        contacts = db~getAllContacts()

        /* Display contacts */
        if contacts~items > 0 then do
            do i = 1 to contacts~items
                contact = contacts[i]
                listwin~mvaddstr(i+5, 2, contact['ID'])
                listwin~mvaddstr(i+5, 6, contact['FIRST_NAME'])
                listwin~mvaddstr(i+5, 18, contact['LAST_NAME'])
                listwin~mvaddstr(i+5, 30, contact['PHONE_NUMBER'])
                listwin~mvaddstr(i+5, 50, contact['EMAIL_ADDRESS'])
                /* Break if we run out of screen space */
                if i > list_height-7 then LEAVE
            end
        end
        else do
            listwin~mvaddstr(6, 5, "No contacts found.")
        end

        listwin~getch()
        self~dropWindow(listwin)
        RETURN
    Return

    /****************************
     * Search for contact       *
     ****************************/
    ::method searchContactPanel
        expose win db menuwin menu_items

        /* Create a search panel */
        max_y = win~lines
        max_x = win~cols
        search_height = 6
        search_width = 50
        start_y = (max_y - search_height) % 2
        start_x = (max_x - search_width) % 2
        searchwin = self~DrawSubPanel(search_height, search_width, start_y, start_x, 7, "Search Contacts (First, Last)", .true)

        searchwin~mvaddstr(3, 2, "Search term: ")
        searchwin~refresh()

        /* Get search term */
        term = self~getInputField(searchwin, 3, 15, 30)

        /* If canceled, return to main menu */
        if term = .nil then do
            self~DropWindow(searchwin)
            return
        end

        /* Perform search */
        searchwin~erase()
        searchwin~refresh()

        /* Display results in a new window */
        self~displaySearchResults(term)
        self~dropWindow(searchwin)
    return

    /**************************
     * DISPLAY SEARCH RESULTS *
     **************************/
    ::METHOD displaySearchResults
        expose win menuwin db
        use arg term

        /* Create a list panel */
        max_y = win~lines
        max_x = win~cols
        list_height = max_y - 5
        list_width = max_x - 23
        start_y = (max_y - list_height) % 2
        start_x = (max_x - list_width) % 2

        searchoutwin = self~DrawSubPanel(list_height, list_width, start_y, start_x, 1, "All Contacts", .true)
        searchoutwin~scrollok(.true)
        searchoutwin~Setscrreg(4,18)

        /* Display column headers */
        searchoutwin~attron(searchoutwin~A_BOLD)
        searchoutwin~mvaddstr(4, 2, "ID")
        searchoutwin~mvaddstr(4, 6, "First Name")
        searchoutwin~mvaddstr(4, 18, "Last Name")
        searchoutwin~mvaddstr(4, 30, "Phone")
        searchoutwin~mvaddstr(4, 50, "Email")
        searchoutwin~mvaddstr(5, 2, "--  ----------  ---------   ---------------     -------------------------")
        searchoutwin~attroff(searchoutwin~A_BOLD)

        contacts = db~searchContacts(term)

        /* Display contacts */
        if contacts~items > 0 then do
            do i = 1 to contacts~items
                contact = contacts[i]
                searchoutwin~mvaddstr(i+5, 2, contact['ID'])
                searchoutwin~mvaddstr(i+5, 6, contact['FIRST_NAME'])
                searchoutwin~mvaddstr(i+5, 18, contact['LAST_NAME'])
                searchoutwin~mvaddstr(i+5, 30, contact['PHONE_NUMBER'])
                searchoutwin~mvaddstr(i+5, 50, contact['EMAIL_ADDRESS'])
                /* Break if we run out of screen space */
                if i > list_height-7 then LEAVE
            end
        end
        else do
            searchoutwin~mvaddstr(6, 5, "No contacts found.")
        end

        searchoutwin~getch()
        self~dropWindow(searchoutwin)

    RETURN


    /************************
     * Get Input From Field *
     ************************/
    ::method getInputField
        use arg win, y, x, maxlen

        win~move(y, x)
        win~curs_set(1)  /* Show cursor */
        win~keypad(1)    /* Enable function keys and arrow keys            */
        win~echo()       /* Show typed characters */
        win~raw

        buffer = ""
        blank_line = copies(" ", maxlen)  /* String of spaces for clearing the line */

        do forever
            key = win~getch()

           decimalKey = C2D(key)
            /* Debug: Display key decimal value */
            /* win~mvaddstr(12, 2, "Key pressed (decimal): " || decimalKey || "   ") */


            select
                when key = D2C(27) then do  /* ESC key */
                    win~curs_set(0)    /* Hide cursor */
                    win~noecho()       /* Stop showing typed characters */
                    return .nil        /* Return nil to indicate cancellation */
                end
                when key = D2C(10) | key = D2C(13) then do  /* Enter key */
                    win~curs_set(0)    /* Hide cursor */
                    win~noecho()       /* Stop showing typed characters */
                    return buffer      /* Return the entered text */
                end
                when decimalKey = 3290675 then do
                /* when key = D2C(8) | key = D2C(26) | key = D2C(127)    */
                /* I don't know why the standard ASCII codes don't work! */
                    if buffer~length > 0 then do
                        buffer = buffer~left(buffer~length - 1)
                        win~move(y, x)
                        win~addstr(buffer || " ")
                        win~move(y, x + buffer~length)
                    end
                end
                otherwise do
                    if buffer~length < maxlen then do
                        ch = key
                        buffer = buffer || ch
                    end
                end
            end
        end
    return buffer

    /***************/
    /** MAIN LOOP **/
    /***************/
    ::method mainLoop
      expose win mainMenu menuwin selected menu_items menu_keys
      menuwin~refresh
      running = .true

      do while running
        key = win~getch

        old_selected = .environment~selected

        select
          when key = menuwin~KEY_UP then do
            if .environment~selected > 1 then .environment~selected = .environment~selected - 1
            self~DrawMenu(menuwin, menu_items, .environment~selected, win)
            menuwin~refresh
          end
          when key = menuwin~KEY_DOWN then do
            if .environment~selected < menu_items~items then .environment~selected = .environment~selected + 1
            self~DrawMenu(menuwin, menu_items, .environment~selected, win)
            menuwin~refresh
          end
          when key = D2C(81) | key = D2C(113) then do /* Q for quit */
            .environment~selected = self~findInArray(menu_keys, key)
            self~ProcessSelection(menuwin, menu_keys[.environment~selected])
            RETURN
          END
          when key = D2C(10) | key = D2C(13) then do /* Enter key - numeric codes only */
            menuwin~mvaddstr(19 - 1, 18, "Letter selection ["||menu_keys[.environment~selected]||"]")
            menuwin~refresh
            self~ProcessSelection(menuwin, menu_keys[.environment~selected])
            return
          end
          otherwise do
              key = lower(key)
              poz = self~findInArray(menu_keys, key)
              if poz > 0 then do
                .environment~selected = poz
                self~DrawMenu(menuwin, menu_items, .environment~selected, win)
                menuwin~mvaddstr(19 - 1, 18, "Letter selection ["||key||"]")
                menuwin~refresh
                self~ProcessSelection(menuwin, key)
              end /* if pos > 0 */
          end /* otherwise */
        end /* select */
        /* Only redraw if selection changed */
        if old_selected \= .environment~selected then do
            self~DrawMenu(menuwin, menu_items, .environment~selected, win)
        end
      end /* do while running */
    return

     /***********************
      * PROCESS SELECTION   *
      ***********************/
    ::METHOD  ProcessSelection
        expose menu_items menu_keys
        use arg menuwin, key_char
          select
            when key_char = 'a' then do
                self~addContactPanel()
                END
            when key_char = 'd' then do
                self~deleteContactPanel()
                END
            when key_char = 'e' then do
                menuwin~mvaddstr(19 - 3, 5, "TODO: Create an Edit Panel    ");
                menuwin~refresh
                END
            when key_char = 's' then do
                self~searchContactPanel()
                END
            when key_char = 'l' then do
                self~listAllContactsPanel()
                END
            when key_char = 'q' then do
                menuwin~mvaddstr(19 - 3, 5, "Exiting the application...        ")
                menuwin~refresh
                menuwin~endwin
                .environment['STOPNOW'] = 1
                END
            otherwise nop
          end
    return

    ::METHOD findInArray
      use arg array, item
      do i = 1 to array~items
        if array[i] = item then return i
      end
      return 0  /* Not found */

    ::METHOD cleanup
      expose win menuwin
      /* Clean up ncurses */
      menuwin~endwin
      win~endwin
      exit 0
    return


