#!/usr/local/bin/bash

# ncid - Network Caller-ID client

# Copyright (c) 2001-2020
#  John L. Chmielewski <jlc@users.sourceforge.net>
#  Steve Limkemann
#  Todd Andrews <tandrews@users.sourceforge.net>

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Lines beginning with # and ending in backslash are a trick to allow sh
# to execute the lines and tclsh/wish to ignore them.

# START OF LOCAL MODIFICATION SECTION
# set PATH to include /usr/local/bin \
  PATH=$PATH:/usr/local/bin; export PATH
# END OF LOCAL MODIFICATION SECTION

# eliminate duplicate PATHS https://unix.stackexchange.com/questions/40749/remove-duplicate-path-entries-with-awk-command \
  PATH=$(printf %s "$PATH" | awk -v RS=: '!a[$0]++' | paste -s -d: -)

# determine full path to tclsh and wish binaries
# set TCLSH variable, FreeBSD will call it something like tclsh8.4 \
  TCLSH=`type tclsh | sed 's,.* \/,/,'`
# set WISH variable, FreeBSD will call it something like wish8.4 \
  WISH=`type wish | sed 's,.* \/,/,'`

# configuration file \
  CF=/usr/local/etc/ncid/ncid.conf

# check for config file and set GUI if not found \
  [ -f $CF ] || GUI=1

# if $GUI not set, set GUI based on configuration file \
  [ "$GUI" == "" ] && GUI=`awk 'BEGIN {GUI = 1} /^ *#/ {next} \
      /^ *set  *NoGUI/ {if ($3 == 0) {GUI = 0}} \
      END {print GUI}' $CF`

# if GUI == 1, look for the --no-gui option, if found set GUI="0" \
  [ "$GUI" == "1" ] && for i in $*; do if [ "$i" = "--no-gui" ]; then  GUI="0"; fi; done

# if DISPLAY is not in the environment, set GUI="0" \
  [ -z "$DISPLAY" ] && GUI="0"

# if wish found and $GUI == 1, look for wish and exec it \
  [ -n "$WISH" ] && [ "$GUI"  == "1" ] && exec $WISH -f "$0" -- "$@"

# if tclsh found and $GUI == 0, look for tclsh and exec it \
  [ -n "$TCLSH" ] && [ "$GUI" == "0" ] && exec $TCLSH "$0" "$@"

# if tclsh found and wish not found, exec tclsh with the --no-gui option \
  [ -n "$TCLSH" ] && exec $TCLSH "$0" --no-gui "$@"

# tcl or tk not found \
  echo "wish or tclsh not found in your \$PATH"; exit -1

set DefaultHost         127.0.0.1
set DefaultPort         3333
set ConfigFile         /usr/local/usr/local/etc/ncid/ncid.conf
set ImageDir            /usr/local/share/ncid/images
set Logo                $ImageDir/ncid.gif
set ThemeDir            /usr/local/share/ncid/themes

### global variables that can be changed by
### command line options, the configuration file, the rcfile
set AltDate             0

### global variables that can be changed by
### command line options, the configuration file
set Hosts               [list]
set Delay               15
set PIDfile             ""
set PopupTime           1
set Verbose             0
set NoGUI               0
set CallOnRing          0
set HostnameFlag        0
set Ring                999
set NoExit              0
set WakeUp              0
set ExitOn              exit
set CallLog             0
set Country             "US"
set LogEnable           0
set UnixLogDir          $::env(HOME)/NCID/client
set WinLogDir           "logs"
set UnixRCfile          "$::env(HOME)/.ncid"
set WinRCfile           ".ncid"
set UnixASfile          "$::env(HOME)/.config/autostart/net.sourceforge.ncid.desktop"

### global variables that can be changed by
### command line options, the rcfile
set Host                ""
set Port                ""

### global variables that can be changed by
### the configuration file, the rcfile
set DateSepar           "/"

###  global variables that can only be changed by
#### command line options
set Module              ""

###  global variables that can only be changed by
#### the configuration file
set ModDir              /usr/local/share/ncid/modules
set ModName             ""
set NoOne               0
set YearDot             0
set WrapLines           "word"
set DialPrefix          ""
set preClient_1_0       0
set nameWidth           30
set ClipboardPopup      1
set ClipboardPopupTime  3

### global variables that can only be changed by
### the rcfile
set clock               24
set autoSave            "off"
set autoStart           "off"
set fontList            ""
set wmGeometry          ""

### global variables that are used as static variables
set NightMode           0
set ThemeName           "day"
set fgColor             ""
set fg2Color            ""
set bckColor            ""
set LogFile             ""
set LogChan             ""
set LogStatus           "Log File:      disabled"
set LogDirLocation      ""
set oldHost             $Host
set oldPort             $Port
set oldDateSepar        $DateSepar
set oldAltDate          $AltDate
set oldAutoSave         $autoSave
set oldAutoStart        $autoStart
set oldClock            $clock
set Leading1            "Leave"
set oldLeading1         $Leading1
set DialLineID          ""
set display_line_num    0
set awakened            0
set Begin               0
set End                 0
set DoingCallLog        0
set waitMsg             0
set mod_menu            0
set multi               0
set menuDisabled        0
set labelWidth          4
set dateWidth           10
set timeWidth           5
set lineIDWidth         16
set nmbrWidth           20
set mtypeWidth          5
set fieldseparators     6
# the alias width must be in the same position as the alias type
set aliasTypes          "NAMEDEP NAMEONLY NMBRDEP NMBRONLY NMBRNAME LINEONLY"
set aliasWidths         "$nameWidth $nameWidth $nmbrWidth $nmbrWidth $nmbrWidth $lineIDWidth"
set aliasList           ""
set dtfile              "/usr/share/applications/net.sourceforge.ncid.desktop"
set countryCodes        "DE FR HR SE UK US NONE"
array set hup           {}
set HostIndex           -1
set SelAliasType        ""
set ChangeHostFlag      0
set hupColor            ""
set ltColor             ""
set dateColor           ""
set timeColor           ""
set lineColor           ""
set nmbrColor           ""
set nameColor           ""
set mtColor             ""
set tvFgColor           ""
set tvBckColor          ""
set vhFgColor           ""
set vhBckColor          ""
set vhInsColor          ""

set LEVEL1              1
set LEVEL2              2
set LEVEL3              3
set LEVEL4              4
set LEVEL5              5
set LEVEL6              6
set LEVEL7              7
set LEVEL8              8
set LEVEL9              9

set TypeGroups          [list]
set oldTypeGroups       [list]

set SelectedTypes       {BLK 0 CID 0 HUP 0 MWI 0 OUT 0 PID 0\
                         PUT 0 RID 0 WID 0 MSG 0 NOT 0}
set oldSelectedTypes    [list]
set SelectedAllTypes    {BLK 1 CID 1 HUP 1 MWI 1 OUT 1 PID 1\
                         PUT 1 RID 1 WID 1 MSG 1 NOT 1}
set SelectedCalls       {BLK 1 CID 1 HUP 1 MWI 1 OUT 1 PID 1\
                         PUT 1 RID 1 WID 1 MSG 0 NOT 0}
set SelectedMessages    {BLK 0 CID 0 HUP 0 MWI 0 OUT 0 PID 0\
                         PUT 0 RID 0 WID 0 MSG 1 NOT 1}
set SelectedSmartPhone  {BLK 0 CID 0 HUP 0 MWI 0 OUT 0 PID 1\
                         PUT 1 RID 0 WID 0 MSG 0 NOT 1}

set LineIDGroups         0
set oldLineIDGroups      0
set DiscoveredLineIDs    [list]
set SelectedLineIDs      [list]
set oldSelectedLineIDs   [list]

# Global Variables
set Interpreter         [info nameofexecutable]
set ServerOptions       ""
set Dialed              0
set ServerOptLineIDS    ""
set svrLID              ""
set OptPmsg             ""
set delayedMsgs         ""

set ConfigFileHost      ""
set ConfigFilePort      ""
set ConfigFileoldHost   ""
set ConfigFileoldPort   ""
set ConfigFileHosts     ""
set ConfigFileHostIndex ""

set ArgHost             ""
set ArgPort             ""
set ArgoldHost          ""
set ArgoldPort          ""
set ArgHosts            ""
set ArgHostIndex        ""

set RCfileHost          ""
set RCfilePort          ""
set RCfileoldHost       ""
set RCfileoldPort       ""
set RCfileHosts         ""
set RCfileHostIndex     ""
set RCfileThemeName     ""

set PortableDir         ""

set ScriptDir [file normalize [file dirname [info script]]]
if {[file exists [file join $ScriptDir [file tail $ConfigFile]]]} {
  set PortableDir $ScriptDir
  set ConfigFile [file join $PortableDir [file tail $ConfigFile]]
}

if {$::tcl_platform(platform) == "unix"} {set LogDir $UnixLogDir
} elseif {$::tcl_platform(platform) == "windows"} {set LogDir $WinLogDir}

if {[file exists $ConfigFile]} {
  # note: debug log has not been opened yet so logMsg is unavailable
  source $ConfigFile
  set delayedMsgs "Processed config file: $ConfigFile"
  #deprecated warning will be given once the log file has been opened
  if {$Hosts == ""} {
    if {$Host != ""} {
      set delayedMsgs \
      "$delayedMsgs\n***** Host: $Host\n***** WARNING: config file option 'Host' is deprecated, use 'Hosts'"
    }
    if {$Port != ""} {
      set delayedMsgs \
      "$delayedMsgs\n***** Port: $Port\n***** WARNING: config file option 'Port' is deprecated, use 'Hosts'"
    }
    if {$Host != "" || $Port != ""} {
      if {$Host == ""} {
        set newhost $DefaultHost
      } else {set newhost $Host}
      if {$Port == ""} {
        set newport $DefaultPort
      } else {set newport $Port}
      set Hosts "$newhost:$newport"
      set delayedMsgs \
        "$delayedMsgs\n***** Hosts: $newhost:$newport"
    }
  } else {
    if {$Host != ""} {
      set delayedMsgs \
      "$delayedMsgs\n***** Host: $Host\n***** WARNING: using config file 'Hosts option, remove deprecated 'Host' option."
      set Host ""
    }
    if {$Port != ""} {
      set delayedMsgs \
      "$delayedMsgs\n***** Port: $Port\n***** WARNING: using config file 'Hosts' option, remove deprecated, 'Port' option."
      set Port ""
    }
  }
} else {set delayedMsgs "*** Config file Missing: $ConfigFile"}

# if we're running in tclsh, force non-graphical
if {[regexp {tclsh} $Interpreter]} {set NoGUI 1}

set ConfigFileHost $Host
set ConfigFilePort $Port
set ConfigFileoldHost $ConfigFileHost
set ConfigFileoldPort $ConfigFilePort
set ConfigFileHosts $Hosts
set ConfigFileHostIndex $HostIndex

### Constants

set CygwinBat     /cygwin.bat

set viewTextWidth 80

# historyTextWidth must be increased by 1 character
# $timeWidth will be included after config file sets $clock which can
# cause it to change its value from 5 to 8
set historyTextWidth [ expr $labelWidth + $dateWidth + $lineIDWidth + \
                            $nmbrWidth + $nameWidth +  $mtypeWidth + \
                            $fieldseparators + 1 ]

set linelabel [format "%-${lineIDWidth}.${lineIDWidth}s" "LINE ID"]
set nmbrlabel [format "%-${nmbrWidth}.${nmbrWidth}s" "NUMBER"]
set namelabel [format "%-${nameWidth}.${nameWidth}s" "NAME"]
set mtypelabel [format "%-${mtypeWidth}.${mtypeWidth}s" "MTYPE"]

# DATE field width is either 10 or 11 characters
# TIME field width is either 5 or 8 characters
# clock 24 & DateSepar .
set lbl1 "TYPE |DATE      |TIME |$linelabel|$nmbrlabel|$namelabel|$mtypelabel|"
# clock 24 & DateSepar -
set lbl2 "TYPE |DATE      |TIME |$linelabel|$nmbrlabel|$namelabel|$mtypelabel|"
# clock 24 & DateSepar /
set lbl3 "TYPE |DATE      |TIME |$linelabel|$nmbrlabel|$namelabel|$mtypelabel|"
# clock 12 & DateSepar .
set lbl4 "TYPE |DATE      |TIME    |$linelabel|$nmbrlabel|$namelabel|$mtypelabel|"
# clock 12 & DateSepar -
set lbl5 "TYPE |DATE      |TIME    |$linelabel|$nmbrlabel|$namelabel|$mtypelabel|"
# clock 12 & DateSepar /
set lbl6 "TYPE |DATE      |TIME    |$linelabel|$nmbrlabel|$namelabel|$mtypelabel|"
# clock 24 & DateSepar . & YearDot 1
set lbl7 "TYPE |DATE       |TIME |$linelabel|$nmbrlabel|$namelabel|$mtypelabel|"
# clock 12 & DateSepar . & YearDot 1
set lbl8 "TYPE |DATE       |TIME    |$linelabel|$nmbrlabel|$namelabel|$mtypelabel|"

if {$ModName != ""} {
    if {[regexp {^.*/} $ModName]} { set Module "$ModName"
    } else {set Module "$ModDir/$ModName"}
} else {set Module ""}

### global variables that are fixed
set Count       0
set ExecSh      0
set Socket      0
set Try         0
set Version     "1.11"
#during development the version# is embedded in the file name
#'tolower' and 'repeat' thwarts sed in Makefile
if {[string tolower $Version] == [string repeat "x" 5]} {set Version [file tail [info script]]}

set VersionIDENT "client ncid (NCID) $Version"
set Usage       {Usage:   ncid  [OPTS] [ARGS]
         OPTS: [--no-gui]
               [--alt-date                  | -A]
               [--call-log                  | -c]
               [--country-code <code>       | -C <country code>]
               [--delay <seconds>           | -D <seconds>]
               [--help                      | -h]
               [--hostname-flag             | -H]
               [--log-enable <0-2>          | -l <0-2>]
               [--log-dir <dirname>         | -L <dirname>]
               [--module <module name>      | -m <module name>]
               [--noexit                    | -X]
               [--pidfile <file>            | -p <file>]
               [--PopupTime <0-5>           | -t <0-5>]
               [--ring <0-9|-1|-2|-9>       | -r <0-9|-1|-2|-9>]
               [--verbose <1-9>             | -v <1-9>]
               [--version                   | -V]
               [--wakeup                    | -W]

         ARGS: [<IP_ADDRESS>                | <HOSTNAME>]
               [<PORT_NUMBER>]}

set hostname [info hostname]
regsub {([^.]*).*} $hostname {\1/ncid} LineName
set VersionInfo "Client: ncid \[$hostname\] NCID $Version"

set Author \
"
Copyright © 2001-2020
John L. Chmielewski
"

set Website "http://ncid.sourceforge.net"

set labBLK "BLK:  Blocked           - blacklisted call blocked"
set labCID "CID:  Caller ID         - incoming call"
set labHUP "HUP:  Hangup            - blacklisted call hangup"
set labMSG "MSG:  Message           - text message from a user or the server"
set labMWI "MWI:  Voicemail         - one or more voicemail messages"
set labNOT "NOT:  Notice            - a smartphone message notice"
set labOUT "OUT:  Out               - outgoing call"
set labPID "PID:  Phone ID          - Caller ID from a smartphone"
set labPUT "PUT:  Phone out call    - outgoing Caller ID from a smartphone"
set labRID "RID:  Ring Back         - rings back when called number is available"
set labWID "WID:  Call Waiting ID   - Caller ID from call waiting"

set labList \
"
$labBLK
$labCID
$labHUP
$labMSG
$labMWI
$labNOT
$labOUT
$labPID
$labPUT
$labRID
$labWID
"

set fieldList \
"
TYPE       - call or message label
DATE       - date of call or message
TIME       - time of call or message
LINE ID    - telephone line label
NUMBER     - caller telephone number
NAME       - caller name
MTYPE      - message type
"

set viewHelp \
"
Types:   Shows all NCID line types.  Those
         not crossed out are those that may
         or may not show up in the history
         window.

LineIDs: Shows all line identifications.
         Those that are not crossed out
         are those that show up in the
         history window.
"

set serverHelp \
"
\"Reload alias, blacklist and whitelist files\" menu entry:
    Server reloads its Alias, Blacklist and Whitelist files.

\"Update current call log\" menu entry:
    Server replaces items in its cidcall.log file with
    aliases in its ncidd.alias file.

\"Update all call logs\" menu entry:
    Server replaces items in its current cidcall.log file
    and previous ones with aliases in its ncidd.alias file.

\"Reread call log\" menu entry:
    Server resends the cidcall.log file.

Selecting a line in the history window will enable the alias,
blacklist, whitelist and clipboard menu entries.  If a modem
is active, \"Dial Number Manually \" is enabled.  If the line
selected is all digits, \"Dial Number From History\" will also
be enabled.

You can remove a history window line selection by clicking
on \"Send Message:\" or on a text line below it

Once you modify the alias file you must:
    * Reload alias, blacklist and whitelist files
    * Update the current call log or all call logs
    * Reread call log

Once you modify the blacklist or whitelist file, you must:
    * Reload alias, blacklist and whitelist files
"

########################################################################
#                       PROCEDURE DEFINITIONS                          #
########################################################################

# display error message and exit
proc exitMsg {code msg} {
    global NoGUI LogChan

    if {$LogChan != ""} {
        set systemTime [clock seconds]
        puts $LogChan "\[[clock format $systemTime -format "%m/%d %H:%M"]\] $msg"
    }

    if $NoGUI {
        puts stderr $msg
    } else {
        wm withdraw .
        option add *Dialog.msg.wrapLength 9i
        option add *Dialog.msg.font "courier 12"
        tk_messageBox -message $msg -icon error -type ok
    }
    exit $code
}

# platform, OS, etc.
proc machine {which} {
  # Observed values      platform       os             osgui
  # Windows 10:          windows        Windows NT     win32
  # Mac (native GUI):    unix           Darwin         aqua
  # Mac (XQuartz):       unix           Darwin         x11
  # AndroWish:           unix           Linux          x11
  # Fedora 25 cinnamon:  unix           Linux          x11
  # FreeBSD              unix           FreeBSD        x11

  switch $which {
    platform {
      if {$::sdltk_present && [expr [sdltk android]]} {return "android"}
      if {$::sdltk_present && [expr [sdltk ischromebook]]} {return "chromebook"}
      return $::tcl_platform(platform)
    }
    os {
      if {$::sdltk_present && [expr [sdltk ischromebook]]} {return "Chrome OS"}
      if {$::sdltk_present && [expr [sdltk android]]} {return "Linux"}
      return $::tcl_platform(os)
    }
    osgui {
      return [tk windowingsystem]
    }
    osname {
      set osname [machine os]
      if {$osname eq "Darwin"} {return "OS X"}
      if {$osname eq "Chrome OS"} {return "Chromebook"}
      if {$::sdltk_present && [expr [sdltk android]]} {return "Android"}
      return $osname
    }
    model {
      if {$::borg_present} {
        array set temparray [borg osbuildinfo]
        return $temparray(model)
      } else {return "not available"}
    }
  }
}

# display the $Try attempt number to connect to ncidd
proc tryCount {msg} {
    global Count
    global Delay
    global Try
    global Txt
    global NoGUI

    # If $Delay == 0, do not try to reconnect
    if (!$Delay) {exit -1}

    if $NoGUI {
        set Once 0
        puts -nonewline stderr $msg
        after [expr $Delay*1000] set Once 1
        vwait Once
    } else {
        set Count $Delay
        while {$Count > 0} {
            if {$Count == 1} {
                set Txt "$msg Try $Try in $Count second."
            } else {
                set Txt "$msg Try $Try in $Count seconds."
            }
            set Once 0
            set Count [expr $Count - 1]
            after [expr 1000] set Once 1
            vwait Once
        }
    }
}

# close connection to NCID server if open, then reconnect
proc Reconnect {} {
    global Socket
    global Count

    if $Count {
        # already waiting to reconnect, force a retry
        set Count 0
        return
    }

    if {$Socket > 0} {
        # close connection to server
        flush $Socket
        fileevent $Socket readable ""
        close $Socket
        set Socket 0
    }

    connectCID
}

# This catches a lot of errors!
proc bgerror {mess} {
    global errorInfo
    global errorCode

    exitMsg 1 "BGError: $mess\n$errorInfo\n$errorCode\n"
}

proc ncidInfo {} {

set sysInfo1 \
"
Windowing System: [string totitle [machine osgui]]
Operating System: [machine osname]
Platform        : [string totitle [machine platform]]
Theme           : [string totitle $::ThemeName]"

if {[file isdirectory $::ThemeDir]} {
    set sysInfo2 "\nAddon Themes Dir: $::ThemeDir/"
} else {set sysInfo2 ""}

set sysInfo3 \
"
Config File     : $::ConfigFile
Preference File : $::rcfile
Wish Executable : $::Interpreter
                  Version [info patchlevel]
[regsub {:} $::LogDirLocation {   :}]
[regsub {:     } [regsub {          } $::LogStatus {             }] {        :}]
"
    if {$sysInfo2 == ""} {
        set sysinfo "$sysInfo1$sysInfo3"
    } else {
        set sysinfo "$sysInfo1$sysInfo2$sysInfo3"
    }
}

proc getWidgetProps {verboseLevel verboseMsg widgetPath whichOptions} {

        #if whichOptions is "all" then all properties are dumped
        #otherwise, only options related to color, relief and text are dumped

        logMsg $verboseLevel "$verboseMsg Current theme reported by ttk::style is: [ttk::style theme use]"

        lmap c [$widgetPath configure ] {
             if {[llength $c] == 2} continue;
             set testvarname [lindex $c 0]
             set doDump 1
             if {$whichOptions != "all"} {
                 set doDump 0
                 if {[string match -nocase "*ground*" $testvarname] || [string match -nocase "*color*" $testvarname] |\
                      [string match -nocase "*relief*" $testvarname] | [string match -nocase "*text*" $testvarname]} {
                      set doDump 1
                 }
            }
            if {$doDump} {
                 set val [$widgetPath cget $testvarname]
                 logMsg $verboseLevel "$verboseMsg $widgetPath [format "%-25.25s %s" "$testvarname" "$val"]"
            }
        }

}

# performs crude checks on host and port
# https://www.appypie.com/faqs/what-characters-are-allowed-in-a-domain-name
#   The characters allowed in a domain name include letters (abc), numbers
#   (123), and dashes/hyphens (---). No spaces are allowed and the domain
#   name can't begin or end with dash/hyphen.
proc checkHosts {} {
    global Hosts

    # Hosts = host:port [host:port] [...]
    foreach hostport $Hosts {
        lassign [split $hostport ":"] host port
        if {[string length $host] < 4} {
            exitMsg 8 "Network address too short: $host"
        } elseif {![regexp {^[\w][\w.-]+$} $host]} {
            exitMsg 8 "Network address has characters not allowed: $host"
        }
        if {[regexp {^.*-$} $host]} {
            exitMsg 8 "Network address must not end in a dash: $host"
        }
        if {![regexp {^\d{4,5}$} $port]} {
            exitMsg 9 "Network port must be 4 or 5 digits: $port"
        }
    }
}

proc getWindowProps {verboseLevel verboseMsg windowPath} {

    foreach i {geometry manager name parent rootx rooty width height screenwidth screenheight vrootwidth vrootheight  \
               vrootx vrooty x y} {
        set val [winfo $i $windowPath]
        logMsg $verboseLevel "$verboseMsg $windowPath [format "%-25.25s %s" "$i" "$val"] "
    }

}

# Get data from CID server
proc getCID {} {
    global Module Host Port Socket NoGUI Try Verbose VersionInfo
    global Ring CallOnRing
    global cid label display_line_num DoingCallLog
    global call Dialed lineID ServerOptLineIDS
    global WakeUp wakened targetTime Begin End ClientJobResult waitMsg
    global mod_menu argument menuDisabled
    global CIDaliasType LineAliasType
    global ServerOptions nmbrREQ
    global hup bckColor
    global TypeGroups SelectedTypes ChangeHostFlag
    global LineIDGroups SelectedLineIDs DiscoveredLineIDs

    # convert list to array
    array set t_array $SelectedTypes

    set msg {server connection closed}
    set cnt 0
    while {$cnt != -1} {
        if {[eof $Socket] || [catch {set cnt [gets $Socket dataBlock]} msg]} {
            # remove event handler
            fileevent $Socket readable ""
            close $Socket
            set ServerOptions ""
            if !$NoGUI {
                if {$menuDisabled == 0} {
                  set menu .menubar.server
                  $menu entryconfigure Reload* -state disabled
                  $menu entryconfigure Update*current* -state disabled
                  $menu entryconfigure Update*all*call* -state disabled
                  $menu entryconfigure Reread* -state disabled
                  $menu entryconfigure Add/Modify* -state disabled
                  $menu entryconfigure Add*to*Blacklist* -state disabled
                  $menu entryconfigure Remove*from*Blacklist* -state disabled
                  $menu entryconfigure Add*to*Whitelist* -state disabled
                  $menu entryconfigure Remove*from*Whitelist* -state disabled
                  $menu entryconfigure Dial*Number* -state disabled
                  $menu entryconfigure Copy*to*Clipboard* -state disabled
                  set menuDisabled 1
                }
            }
            set Try [expr $Try + 1]
            tryCount "$Host:$Port - $msg\n"
            connectCID
            return
        }
        set Try 0

        # get rid of non-printable characters at start/end of string
        set dataBlock [string trim $dataBlock]

        if {[string match 200* $dataBlock]} {
            # output NCID server connect message
            logMsg $::LEVEL1 $dataBlock
            regsub {200 (.*)} $dataBlock {\1} dataBlock
            if $NoGUI {
               logMsg $::LEVEL1 "$VersionInfo\n$dataBlock"
               set targetTime 0
            } else {
                set targetTime [expr [clock clicks -milliseconds] + 500]
                displayCID "$VersionInfo\n$dataBlock" 1
                }
        } elseif {[string match 254* $dataBlock]} {
            # NCID server sent start of call log message
            if {!$NoGUI} {
                set DoingCallLog 1
                .vh configure -state normal
                .vh insert 1.0 "\n\n\t\tReading the call log\n\n"
                set Begin [clock clicks -milliseconds]
            }
        } elseif {[string match {25[0-3]*} $dataBlock]} {
            # NCID server sent call log message
            if !$NoGUI {
                .vh delete 1.0 6.0
                .vh yview moveto 1.0
                .vh configure -state disabled
                if {[lindex [.vh yview] 0] + [lindex [.vh yview] 1] == 1.0} {
                    grid remove .ys
                } else {
                    grid .ys
                }
            }
            set DoingCallLog 0
            if {[regexp {250} $dataBlock]} {
                # NCID server sent end of call log message
                if {!$NoGUI} {
                    set DiscoveredLineIDs [lsort -dictionary $DiscoveredLineIDs]
                    if {$ChangeHostFlag == 1} {
                        logMsg $::LEVEL1 "$dataBlock - $display_line_num lines"
                        set SelectedLineIDs $DiscoveredLineIDs
                        write_rc_file "set SelectedLineIDs" "set SelectedLineIDs \"$SelectedLineIDs\""
                        set ChangeHostFlag 0
                    }
                }
            } else {logMsg $::LEVEL1 "$dataBlock"}
            set End [clock clicks -milliseconds]
            set elapsed [expr $End - $Begin]
            logMsg $::LEVEL2 "$display_line_num call history entries in $elapsed milliseconds"
        } elseif {[string match 300* $dataBlock]} {
            # NCID server sent end of startup message
            if {$ServerOptions == ""} {set ServerOptions "\nnone"}
            logMsg $::LEVEL1 $dataBlock
            logMsg $::LEVEL2 "ServerOptions: [split [regsub -all {^\n|  +} $ServerOptions {}] "\n"]"
            if {!$NoGUI} {logMsg $::LEVEL3 "DiscoveredLineIDs: $DiscoveredLineIDs"}
            continue
        } elseif {[string match 400* $dataBlock]} {
            # NCID server has sent text to be displayed
            logMsg $::LEVEL1 $dataBlock
            toplevel .reply -background $bckColor
            wm title .reply "Server's Response"
            grid [text .reply.text -yscrollcommand ".reply.ys set" -setgrid 1 \
                     -font FixedFontP -height 8 -width 70] \
                     -pady 1 -padx 1 -sticky nesw
            grid [ttk::scrollbar .reply.ys -command ".reply.text yview"] \
                    -column 1 -row 0 -sticky ns -pady 1 -padx 1
            grid [ttk::button .reply.btn -text "OK" -command {destroy .reply}] \
                    -pady 10 -columnspan 2
            grid columnconfigure .reply 0 -weight 1
            grid rowconfigure .reply 0 -weight 1
            wm minsize .reply 25 4
            bind .reply <Configure> {
                if {[lindex [.reply.text yview] 0] + [lindex [.reply.text yview] 1] == 1.0} {
                    grid remove .reply.ys
                } else {
                    grid .reply.ys
                }
            }
            modal {.reply}
            continue;
        } elseif {[string match 401* $dataBlock]} {
            # NCID server has sent text to be displayed, must ACCEPT or REJECT
            logMsg $::LEVEL1 $dataBlock
            toplevel .reply -background $bckColor
            wm title .reply "Server's Response"
            grid [text .reply.text -yscrollcommand ".reply.ys set" -setgrid 1 \
                    -font FixedFontP -height 8 -width 70] \
                    -pady 10 -padx 10 -sticky nesw
            .reply.text insert 1.0 "\n\n\tUpdating call logs"
            .reply.text configure -state disabled
            grid [ttk::scrollbar .reply.ys -command ".reply.text yview"] \
                    -column 1 -row 0 -sticky ns -pady 10 -padx 5
            grid [ttk::frame .reply.fr]  -pady 10 -padx 10 -columnspan 2 -row 1
            ttk::button .reply.accept_btn -text "Accept" -state disabled -command {
                    global multi

                    if {$multi} {
                        set temp "S"
                    } else {
                        set temp ""
                    }
                    puts $Socket "WRK: ACCEPT LOG$temp"
                    flush $Socket
                    destroy .reply
                    }
            ttk::button .reply.reject_btn -text "Reject" -state disabled -command {
                    global multi

                    if {$multi} {
                        set temp "S"
                    } else {
                        set temp ""
                    }
                    puts $Socket "WRK: REJECT LOG$temp"
                    flush $Socket
                    destroy .reply
                    }
            grid .reply.accept_btn .reply.reject_btn -in .reply.fr -padx 25
            grid columnconfigure .reply 0 -weight 1
            grid rowconfigure .reply 0 -weight 1
            wm minsize .reply 40 5
            bind .reply <Configure> {
                if {[lindex [.reply.text yview] 0] + [lindex [.reply.text yview] 1] == 1.0} {
                    grid remove .reply.ys
                } else {
                    grid .reply.ys
                }
            }
            showBusy "." .reply.text
            modal {.reply}
            continue;
        } elseif {[string match 402* $dataBlock]} {
            logMsg $::LEVEL1 $dataBlock
             set ClientJobResult ""
        } elseif {[string match 403* $dataBlock]} {
            logMsg $::LEVEL1 $dataBlock
            set mod_menu 1
        } elseif {[string match 410* $dataBlock]} {
            logMsg $::LEVEL1 $dataBlock
            .reply.text configure -state normal
            .reply.text delete end-1chars
            .reply.text configure -state disabled
            .reply.text see end
            catch {
                if {[lindex [.reply.text yview] 0] + [lindex [.reply.text yview] 1] == 1.0} {
                    grid remove .reply.ys
                } else {
                    grid .reply.ys
                }
            }
            catch {
                .reply.accept_btn configure -state normal
                .reply.reject_btn configure -state normal
            }
            continue
        } elseif {[string match 411* $dataBlock]} {
            logMsg $::LEVEL1 $dataBlock
            if {$mod_menu} {
                set mod_menu 0
                continue
            }
            if {[string length $ClientJobResult] < 4} {
                set ClientJobResult "Done."
            }
            if {$Dialed} {
                # $Dialed == 2 when dial aborted
                if {$Dialed == 1} {
                    .dial.close configure -state active
                    .dial.abort configure -state active
                }
                set Dialed 0
            } else {.confirm.close configure -state active}
        } elseif {[string match INFO:* $dataBlock]} {
            logMsg $::LEVEL1 $dataBlock
            if {$mod_menu} {
                set menu .menubar.server
                $menu entryconfigure Copy*to*Clipboard* -state normal
                set temp [split $dataBlock " "]
                set fileType [lindex $temp 1]
                if {$fileType == "dial"} {
                    set dialarg [lindex $temp 2]
                } else {set argument [lindex $temp 2]}
                switch $fileType {
                    alias {
                        set CIDaliasType [lindex $temp 2]
                        set LineAliasType [lindex $temp 3]
                        $menu entryconfigure Add*Alias* -state normal
                    }
                    black {
                        $menu entryconfigure Add*Black* -state disabled
                        $menu entryconfigure Add*White* -state normal
                        $menu entryconfigure Remove*Black* -state normal
                        $menu entryconfigure Remove*White* -state disabled
                    }
                    white {
                        $menu entryconfigure Add*Black* -state disabled
                        $menu entryconfigure Add*White* -state disabled
                        $menu entryconfigure Remove*Black* -state disabled
                        $menu entryconfigure Remove*White* -state normal
                    }
                    neither {
                        $menu entryconfigure Add*Black* -state normal
                        $menu entryconfigure Add*White* -state normal
                        $menu entryconfigure Remove*Black* -state disabled
                        $menu entryconfigure Remove*White* -state disabled
                    }
                    dial {
                        if {$dialarg == "NODIAL"} {
                            $menu.dial entryconfigure From*History* -state disabled
                        } else {
                            $menu.dial entryconfigure From*History* -state normal
                        }
                    }
                }
                continue;
            }
            .reply.text configure -state normal
            if {$waitMsg} {
                set waitMsg 0
                .reply.text delete 1.0 end
            }
            .reply.text insert end [string range [append dataBlock " \n"] 6 end]
            .reply.text configure -state disabled
            continue
        } elseif {[string match RESP:* $dataBlock]} {
            logMsg $::LEVEL1 $dataBlock
            if ($Dialed) {
                if {[string first "Pickup phone" $dataBlock 0] != -1} {
                    .dial.abort configure -state active
                    .dial.close configure -state active
                } else {
                    regsub {.*Server modem ([\w\d\s]+) dialed.*$} $dataBlock {\1} svrLID
                }
            }
            append ClientJobResult [string range $dataBlock 6 end]
            append ClientJobResult "\n"
            continue
        } elseif {[string match RPLY:* $dataBlock]} {
            logMsg $::LEVEL1 $dataBlock
            set ClientJobResult [string range $dataBlock 6 end]
            append ClientJobResult "\n"
            destroy .dial
            doRPLY
        } elseif {[string match OPT:* $dataBlock]} {
            set ServerOpt [string trim [string range $dataBlock 5 end]]
            if {[string first "LineIDS:" $ServerOpt 0] != -1} {
                regsub {^.*LineIDS: (.*)$} $ServerOpt {\1} ServerOptLineIDS
                if {!$NoGUI} {
                    .menubar.server entryconfigure Dial*Number* -state normal
                }
            }
            logMsg $::LEVEL3 "Received Server Option: $ServerOpt"
            set ServerOptions "$ServerOptions\n$ServerOpt"
        }
        if {[set label [checkType $dataBlock]]} {
            if {$label == 3} {
                # CIDINFO (ring) line
                set ringinfo [getField RING $dataBlock]
                # must use $call($lineinfo) instead of $cid
                set lineinfo [getField LINE $dataBlock]
                if {!$NoGUI} {
                    set status [processLineID "$lineinfo"]
                    if {$LineIDGroups == 1 && $status != 1} {continue}
                }
                if {[array get call $lineinfo] != {}} {
                  set CIDtype [lindex $call($lineinfo) 5]
                  if {$ringinfo == -4} {
                    if {!$NoGUI } {
                      # get line from hup array and restore HUP color in GUI
                      if {[catch {set displine $hup($lineinfo)} huperr]} {
                        logMsg $::LEVEL1 "huperr"
                      } else {
                        # restore theme color to $CIDtype
                        .vh configure -state normal
                        .vh replace $displine.0 $displine.0+4c "$CIDtype:" lttag
                        .vh configure -state disabled
                      }
                    }
                  } elseif {$CallOnRing && $CIDtype == "CID"} {
                    if {$Module != "" && ($Ring == $ringinfo ||
                        ($Ring == -9 && $ringinfo > 1))} {
                      sendCID $call($lineinfo)
                      logMsg $::LEVEL1 "$dataBlock"
                    } else { logMsg $::LEVEL6 "$dataBlock" }
                  }
                } else {
                    logMsg $::LEVEL1 "Phone line label \"$lineinfo\" not found"
                }
                if {$WakeUp && $ringinfo == 1} {
                    doWakeup
                    set wakened 1
                }
            } elseif {$label == 4 || $label == 5} {
                # MSG (4), NOT (4)
                # MSGLOG (5), NOTLOG (5)
                set msg [formatMSG $dataBlock]
                if {!$NoGUI} {
                    set status [processLineID "[lindex $msg 4]"]
                    set thisTYPE [lindex $msg 5]
                    if {$LineIDGroups == 1 && $status != 1} {continue}
                    if {$TypeGroups == 1} {continue}
                    if {$TypeGroups == 3 && !$t_array($thisTYPE)} {continue}
                    if {$TypeGroups == 4 && !$t_array($thisTYPE)} {continue}
                }
                displayLog $msg 1
                if {$label == 4} {
                    if {!$NoGUI} {
                        displayCID "[lindex $msg 7]\n" 1
                        doPopup
                    }
                    if {$Module != ""} {
                        sendMSG $msg
                    }
                }
            } elseif {$label == 1 || $label == 2} {
                # CID (1), HUP (1) OUT (1), RID (1)
                # BLK (2), MWI (2), PID (2), PUT(2), WID (2)
                if {$WakeUp} {
                    if {!$wakened} {
                        doWakeup
                    } else {set wakened 0}
                }
                set cid [formatCID $dataBlock]
                if {!$NoGUI} {
                    set status [processLineID "[lindex $cid 4]"]
                    set thisTYPE [lindex $cid 5]
                    if {$LineIDGroups == 1 && $status != 1} {continue}
                    if {$TypeGroups == 2} {continue}
                    if {$TypeGroups == 3 && !$t_array($thisTYPE)} {continue}
                    if {$TypeGroups == 4 && !$t_array($thisTYPE)} {continue}
                }
                if {$label == 1} {array set call "{$lineID} [list $cid]"}
                # display log
                displayLog $cid 0
                # display CID
                if {!$NoGUI} {
                    displayCID $cid 0
                    doPopup
                }
                set CIDtype [lindex $cid 5]
                if {(!$CallOnRing  || $CIDtype == "CID" || $Ring == -9) && $Module != ""} {
                    sendCID $cid
                }
            } elseif {$label == 6} {
                # BLKLOG, CIDLOG, HUPLOG, MWILOG, OUTLOG, PIDLOG, PUTLOG, RIDLOG, WIDLOG
                set cid [formatCID $dataBlock]
                if {!$NoGUI} {
                    set status [processLineID "[lindex $cid 4]"]
                    set thisTYPE [lindex $cid 5]
                    if {$LineIDGroups == 1 && $status != 1} {continue}
                    if {$TypeGroups == 2} {continue}
                    if {$TypeGroups == 3 && !$t_array($thisTYPE)} {continue}
                    if {$TypeGroups == 4 && !$t_array($thisTYPE)} {continue}
                }
                # display log
                displayLog $cid 0
                if {!$NoGUI && $targetTime && [clock clicks -milliseconds] >= $targetTime} {
                    set targetTime [expr [clock clicks -milliseconds] + 500]
                    .vh insert 3.end "."
                    update idletasks
                }
            }
        }
    }
    if {!$NoGUI && $DiscoveredLineIDs != ""} {updateViewDisplay}
}

proc showBusy {text widget} {
    global waitMsg

    $widget configure -state normal
    $widget insert end $text
    $widget configure -state disabled
    set waitMsg 1
}

proc doWakeup {} {
    global ExecSh
    global ModDir

    if $ExecSh {
        catch {exec sh -c $ModDir/ncid-wakeup} oops
    } else {
        catch {exec $ModDir/ncid-wakeup} oops
    }
}

#   PopupTime = 0:   doPopup is disabled
#   PopupTime = 1-5: Time in seconds window is forced to remain
#                    on top before user is allowed to remove it.
proc doPopup {} {
    global PopupTime

    if {! $PopupTime} { return }

    wm deiconify .
    raise .
    wm attributes . -topmost true
    after [expr $PopupTime*1000] wm attributes . -topmost false
}

proc checkType {dataBlock} {

    set rtn 0
    # Determine label type
    # General classifications:
    #  1 = real time: calls that can trigger WakeUp - CID, HUP, OUT, RID
    #  2 = real time: other calls - BLK, MWI, PID, PUT, WID
    #  3 = real time: ring detected - CIDINFO
    #  4 = real time: messages (non-calls)
    #  5 = log file : messages (non-calls)
    #  6 = log file : calls classified as 1 and 2 with suffix LOG
    #  7 = log file : unrecognized line type
    #  8 = real time: relay job - RLY
    #  9 = log file : relay job - RLYLOG
    # 10 = real time: call accounting - END
    # 11 = log file : call accounting - ENDLOG
          if [string match CID:* $dataBlock] {set rtn 1
    } elseif [string match HUP:* $dataBlock] {set rtn 1
    } elseif [string match OUT:* $dataBlock] {set rtn 1
    } elseif [string match RID:* $dataBlock] {set rtn 1

    } elseif [string match BLK:* $dataBlock] {set rtn 2
    } elseif [string match MWI:* $dataBlock] {set rtn 2
    } elseif [string match PID:* $dataBlock] {set rtn 2
    } elseif [string match PUT:* $dataBlock] {set rtn 2
    } elseif [string match WID:* $dataBlock] {set rtn 2

    } elseif [string match CIDINFO:* $dataBlock] {set rtn 3

    } elseif [string match MSG:* $dataBlock] {set rtn 4
    } elseif [string match NOT:* $dataBlock] {set rtn 4

    } elseif [string match MSGLOG:* $dataBlock] {set rtn 5
    } elseif [string match NOTLOG:* $dataBlock] {set rtn 5

    } elseif [string match BLKLOG:* $dataBlock] {set rtn 6
    } elseif [string match CIDLOG:* $dataBlock] {set rtn 6
    } elseif [string match HUPLOG:* $dataBlock] {set rtn 6
    } elseif [string match MWILOG:* $dataBlock] {set rtn 6
    } elseif [string match OUTLOG:* $dataBlock] {set rtn 6
    } elseif [string match PIDLOG:* $dataBlock] {set rtn 6
    } elseif [string match PUTLOG:* $dataBlock] {set rtn 6
    } elseif [string match RIDLOG:* $dataBlock] {set rtn 6
    } elseif [string match WIDLOG:* $dataBlock] {set rtn 6

    } elseif [string match LOG:* $dataBlock] {set rtn 7

    } elseif [string match RLY:* $dataBlock] {set rtn 8

    } elseif [string match RLYLOG:* $dataBlock] {set rtn 9

    } elseif [string match END:* $dataBlock] {set rtn 10

    } elseif [string match ENDLOG:* $dataBlock] {set rtn 11}
    logMsg $::LEVEL6 "Assigned $rtn for $dataBlock"
    return $rtn
}

# must be sure the line passed checkType
# returns: $ciddate $cidtime $cidnumber $cidname $cidline $linetype "" ""
proc formatCID {dataBlock} {
    global lineID lineIDWidth

    set cidname [formatNAME $dataBlock]
    set cidnumber [formatNMBR $dataBlock]
    set ciddate [formatDATE $dataBlock]
    set cidtime [formatTIME $dataBlock]
    set cidline ""
    if [string match {*\*LINE\**} $dataBlock] {
        set cidline [formatLINE $dataBlock]
    }
    # set default line indicator, should not be needed anymore
    if {$cidline == ""} {
        set cidline "-"
        for {set x 0} {$x < $lineIDWidth} {incr x} {
            set cidline "$cidline "
        }
    }
    # create call line label
    regsub { *$} $cidline {} lineID
    # set type of call
    if {![regsub {(\w+)LOG:.*} $dataBlock {\1} linetype]} {
        regsub {(\w+):.*} $dataBlock {\1} linetype
    }

    return [list $ciddate $cidtime $cidnumber $cidname $cidline $linetype "" ""]
}

# returns: $msgdate $msgtime $msgnumber $msgname $msgline $linetype $mesgtype $message
proc formatMSG {dataBlock} {

    if {![regsub {(\w+)LOG:.*} $dataBlock {\1} linetype]} {
        regsub {(\w+):.*} $dataBlock {\1} linetype
    }

    if {[regexp {\*\*\*DATE} $dataBlock]} {
        set msgdate [formatDATE $dataBlock]
        set msgtime [formatTIME $dataBlock]
        set msgname [formatNAME $dataBlock]
        set msgnmbr [formatNMBR $dataBlock]
        set msgline [formatLINE $dataBlock]
        set msgtype [formatMTYPE $dataBlock]
        regsub {\w+:\s+(.*) \*\*\*DATE.*} $dataBlock {\1\2} mesg
        set message [list $msgdate $msgtime $msgnmbr $msgname $msgline $linetype $msgtype $mesg]
    } else {
        regsub {\w+:\s+(.*)} $dataBlock {\1} mesg
        set message [list {} {} {} {} {} $linetype {} $mesg]
    }

    return $message
}

proc formatMTYPE {dataBlock} {
    if {[regexp {\*\*\*DATE.*MTYPE} $dataBlock]} {
        set msgtype [getField MTYPE $dataBlock]
    } else {
        set msgtype "-"
    }
    return $msgtype
}

proc formatLINE {dataBlock} {
    set cidline [getField LINE $dataBlock]
    return $cidline
}

proc formatDATE {dataBlock} {
    global AltDate DateSepar YearDot

    set ciddate [getField DATE $dataBlock]
    # slash (/) is the default date separator
    if {$AltDate} {
        # Date format: DDMMYY or DDMM
        if {![regsub {([0-9][0-9])([0-9][0-9])([0-9][0-9][0-9][0-9])} \
            $ciddate {\2/\1/\3} ciddate]} {
            regsub {([0-9][0-9])([0-9][0-9].*)} $ciddate {\2/\1} ciddate
        }
    } else {
        # Date format: MMDDYY or MMDD
        if {![regsub {([0-9][0-9])([0-9][0-9])([0-9][0-9][0-9][0-9])} \
            $ciddate {\1/\2/\3} ciddate]} {
            regsub {([0-9][0-9])([0-9][0-9].*)} $ciddate {\1/\2} ciddate
        }
    }
    if {$DateSepar == "-"} {
        # set hyphen (-) as date separator
        regsub -all {/} $ciddate - ciddate
    } elseif {$DateSepar == "."} {
        if $YearDot {
          # set period (.) as date separator and append it to year (ordinal numbers)
          regsub -all {/} $ciddate. . ciddate
        } else {
          # set period (.) as date separator
          regsub -all {/} $ciddate . ciddate
        }
    }
    return $ciddate
}

proc formatTIME {dataBlock} {
    global clock

    set cidtime [getField TIME $dataBlock]
    if ([regexp {(\d{2})(\d{2})} $cidtime time hours minutes]) {
        if {$clock == 24} {
            set cidtime "$hours:$minutes"
        } else {
        set cidtime [convertTo12 $hours $minutes]
        }
    }
    return $cidtime
}

proc formatNAME {dataBlock} {
    set cidname [getField NAME $dataBlock]
    if {$cidname == "-"} {set cidname "NO NAME"}
    return $cidname
}

proc checkCountry {code} {
    global countryCodes NoGUI

    if {![regexp "$code" $countryCodes]} {
      if {!$NoGUI} {
        logMsg $::LEVEL1 "Country Code \"$code\" is not supported.\nShould be one of \"$countryCodes\"."
     }
     exitMsg 7 "Country Code \"$code\" is not supported.\nShould be one of \"$countryCodes\"."
    }
}

proc formatNMBR {dataBlock} {
    global Country NoOne
    # https://en.wikipedia.org/wiki/National_conventions_for_writing_telephone_numbers

    set cidnumber [getField NMBR $dataBlock]
    if {$cidnumber == "-"} {set cidnumber "NO-NUMBER"}
    switch $Country {
      DE {set cidnumber [formatDE $cidnumber]}
      FR {set cidnumber [formatFR $cidnumber]}
      HR {set cidnumber [formatHR $cidnumber]}
      SE {set cidnumber [formatSE $cidnumber]}
      UK {set cidnumber [formatUK $cidnumber]}
      US {set cidnumber [formatUS $cidnumber]}
      NONE {}
      DEFAULT {
        exitMsg 7 "Country Code \"$Country\" is not supported. Please change it."
      }
    }
    return $cidnumber
}

proc formatUS {cidnumber} {
    global NoOne
    # https://en.wikipedia.org/wiki/North_American_Numbering_Plan
    # US-PBX examples: 919715559679 -> 9,1-971-555-9679
    #                  99715559679 -> 9,971-555-9679
    if {![regsub {(9)(1)([0-9]{3})([0-9]{3})([0-9]{4})} \
      $cidnumber {\1,\2-\3-\4-\5} cidnumber]} {
      if {![regsub {(9)([0-9]{3})([0-9]{3})([0-9]{4})} \
      $cidnumber {\1,\2-\3-\4} cidnumber]} {
        if {![regsub {(1)([0-9]{3})([0-9]{3})([0-9]{4})} \
          $cidnumber {\1-\2-\3-\4} cidnumber]} {
          if {![regsub {(ob)([0-9]{3})([0-9]{3})([0-9]{3})} \
            $cidnumber {\1-\2-\3-\4} cidnumber]} {
            if {![regsub {^([0-9]{3})([0-9]{3})([0-9]{3})$} \
              $cidnumber {\1-\2-\3} cidnumber]} {
              if {![regsub {([0-9]{3})([0-9]{3})([0-9]{4})} \
                $cidnumber {\1-\2-\3} cidnumber]} {
                regsub {([0-9]{3})([0-9]{4})} \
                $cidnumber {\1-\2} cidnumber
              }
            }
          }
        }
      }
    } elseif {$NoOne} {
      regsub {^1-?(.*)} $cidnumber {\1} cidnumber
    }
    return $cidnumber
}

proc formatUK {cidnumber} {
    # https://en.wikipedia.org/wiki/United_Kingdom_area_codes
    if {![regsub {^(011[0-9])([0-9]{3})([0-9]+)} \
      $cidnumber {\1-\2-\3} cidnumber]} {
      if {![regsub {^(01[0-9]1)([0-9]{3})([0-9]+)} \
        $cidnumber {\1-\2-\3} cidnumber]} {
        if {![regsub {^(13873|15242|19467)([0-9]{4,5})} \
          $cidnumber {\1-\2} cidnumber]} {
          if {![regsub {^(153)(94|95|96)([0-9]{4,5})} \
            $cidnumber {\1\2-\3} cidnumber]} {
            if {![regsub {^(169)(73|74|77)([0-9]{4,5})} \
              $cidnumber {\1\2-\3} cidnumber]} {
              if {![regsub {^(176)(83|84|87)([0-9]{4,5})} \
                $cidnumber {\1\2-\3} cidnumber]} {
                if {![regsub {^(01[0-9]{3})([0-9]+)} \
                  $cidnumber {\1-\2} cidnumber]} {
                  if {![regsub {^(02[0-9])([0-9]{4})([0-9]+)} \
                    $cidnumber {\1-\2-\3} cidnumber]} {
                    if {![regsub {^(0[389][0-9]{2})([0-9]{3})([0-9]+)} \
                      $cidnumber {\1-\2-\3} cidnumber]} {
                      if {![regsub {^(07[0-9]{3})([0-9]+)} \
                        $cidnumber {\1-\2} cidnumber]} {
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    return $cidnumber
}

proc formatSE {cidnumber} {
    # https://en.wikipedia.org/wiki/Telephone_numbers_in_Sweden#Area_codes
    if {![regsub {^(07[0-9])([0-9]+)} \
        $cidnumber {\1-\2} cidnumber]} {
     if {![regsub {^(08)([0-9]+)} \
         $cidnumber {\1-\2} cidnumber]} {
      if {![regsub {^(01[013689])([0-9]+)} \
          $cidnumber {\1-\2} cidnumber]} {
       if {![regsub {^(0[23][[136])([0-9]+)} \
           $cidnumber {\1-\2} cidnumber]} {
        if {![regsub {^(04[0246])([0-9]+)} \
            $cidnumber {\1-\2} cidnumber]} {
         if {![regsub {^(054)([0-9]+)} \
             $cidnumber {\1-\2} cidnumber]} {
          if {![regsub {^(06[02])([0-9]+)} \
              $cidnumber {\1-\2} cidnumber]} {
           if {![regsub {^(090)([0-9]+)} \
               $cidnumber {\1-\2} cidnumber]} {
            regsub {^([0-9]{4})([0-9]+)} \
                    $cidnumber {\1-\2} cidnumber
           }
          }
         }
        }
       }
      }
     }
    }
    return $cidnumber
}

proc formatDE {cidnumber} {
  # https://en.wikipedia.org/wiki/Area_codes_in_Germany

  # format for numbers was broken, removed until it can be done correctly
  return $cidnumber
}

proc formatHR {cidnumber} {
    # https://en.wikipedia.org/wiki/Telephone_numbers_in_Croatia
    if {![regsub {^(01)([0-9]+)} \
      $cidnumber {\1-\2} cidnumber]} {
      if {![regsub {^(02[0123])([0-9]+)} \
        $cidnumber {\1-\2} cidnumber]} {
        if {![regsub {^(03[12345])([0-9]+)} \
          $cidnumber {\1-\2} cidnumber]} {
          if {![regsub {^(04[0234789])([0-9]+)} \
            $cidnumber {\1-\2} cidnumber]} {
            if {![regsub {^(05[123])([0-9]+)} \
              $cidnumber {\1-\2} cidnumber]} {
              if {![regsub {^(09[125789])([0-9]+)} \
                $cidnumber {\1-\2} cidnumber]} {
              }
            }
          }
        }
      }
    }
    return $cidnumber
}

proc formatFR {cidnumber} {
    # http://en.wikipedia.org/wiki/Telephone_numbers_in_France
    set nmbrWidth 20
    #French national calls
    if {![regsub {^(0[1-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])} \
      $cidnumber {\1 \2 \3 \4 \5} cidnumber]} {
	#international calls (prefix 1* ,2 )
	#formats prefix but doesn't format local number
      if {![regsub {^(00)(1)([1-9]+)} \
        $cidnumber {(+1) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(2[078])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(2[1234569][0-9])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(3[012469])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(3[578][0-9])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
	#telemarketing calls with France international prefix
	#formats prefix and formats local number to french standard
      if {![regsub {^(00)(33)([1-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9]+)} \
        $cidnumber {(+33) \3 \4 \5 \6 \7} cidnumber]} {
      }
	#other international calls (prefix 3*,4*,5*,6*,7*,8*,9*)
	#formats prefix but doesn't format local number
      if {![regsub {^(00)(4[013456789])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(4[2][0-9])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(5[1345678])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(5[09][0-9])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(6[013456])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(6[789][0-9])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(7)([1-9]+)} \
        $cidnumber {(+7) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(8[123469])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(8[0578][0-9])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(9[0123458])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
      if {![regsub {^(00)(9[679][0-9])([0-9]+)} \
        $cidnumber {(+\2) \3 } cidnumber]} {
      }
    }
    return $cidnumber
}

proc convertTo12 {hours minutes} {
    set AmPm "am"
    if {$hours > 12} {
        set hours [expr $hours - 12]
        set AmPm "pm"
    } elseif {$hours == 12} {
        set AmPm "pm"
    } elseif {$hours == 0} {
        set hours 12
    }
    regsub {^(0|\s|)?(\d)$} $hours { \2} hours
    return "$hours:$minutes $AmPm"
}

proc convertTo24 {hours minutes AmPm} {
    if {$hours == 12 && $AmPm eq "am"} {
        set hours 0
    } elseif {$hours != 12 && $AmPm eq "pm"} {
        set hours [expr $hours + 12]
    }
    regsub {^(0|\s|)?(\d)$} $hours {0\2} hours
    return "$hours:$minutes"
}

# extract field pair where 'dataString' is the field label (NAME, NMBR, etc.)
# and $result is the field data
proc getField {dataString dataBlock} {
  regsub ".*\\*$dataString\\*" $dataBlock {} result

  switch $dataString {
    RING -
    DATE -
    TIME {
      regsub {(\d+).*} $result {\1} result
    }
    LINE {
      regsub {([\w\s@!-]+)\*.*} $result {\1} result
    }
    NMBR -
    NAME {
      regsub {\*DATE\*.*|\*TIME\*.*$|\*LINE\*.*|\*NMBR\*.*|\*MESG\*.*|\*NAME\*.*|\*MTYPE\*.*|\*$} $result "" result
    }
    MTYPE -
    default {
      regsub {([\w-]+)\*} $result {\1} result
    }
  }
  return $result
}

# send the CID information to an external program
# Input: $ciddate $cidtime $cidnumber $cidname $cidline $cidtype "" ""
proc sendCID {cid} {
  global Module ExecSh ModDir WakeUp

  set modcid "$cid"
  # send DATE\nTIME\nNUMBER\nNAME\nLINE\nTYPE\nMESG\nMTYPE\n
  set modtype "using a module"
  set modin "[lindex $cid 0]\n[lindex $cid 1]\n[lindex $cid 2]\n[lindex $cid 3]\n[lindex $cid 4]\n[lindex $cid 5]\n[lindex $cid 6]\n[lindex $cid 7]"
  if $ExecSh {
    catch {exec sh -c $Module << "$modin" >@stdout &} oops
  } else {
    catch {exec $Module << "$modin" >@stdout &} oops
  }
  logMsg $::LEVEL1 "$modtype\nSent $Module $modcid"
}

# pass the message to an external program
# input: $msgdate $msgtime $msgnumber $msgname $msgline $msgtype $mtype $msg
proc sendMSG {msg} {
  global Module ExecSh preClient_1_0

  set mesg "$msg"
  if $preClient_1_0 {
    # send "\n\n\nMESG\n\nTYPE\n"
    set modtype "using a preClient 1.0 module for a message"
    set modin "[lindex $msg 0]\n[lindex $msg 1]\n[lindex $msg 2]\n[lindex $msg 6]\n[lindex $msg 4]\n[lindex $msg 5]\n[lindex $msg 3]\n"
    set mesg [lreplace $mesg 3 3 [lindex $msg 7]]
    set mesg [lreplace $mesg 7 7 [lindex $msg 3]]
    if $ExecSh {
      catch {exec sh -c $Module << "$modin" >@stdout &} oops
    } else {
      catch {exec $Module << "$modin" >@stdout &} oops
    }
  } else {
    # send "DATE\nTIME\nNMBR\nNAME\nLINE\nTYPE\n\MESG\nMTYPE\n"
    set modtype "using a Client 1.0 type module for a message"
    set modin "[lindex $msg 0]\n[lindex $msg 1]\n[lindex $msg 2]\n[lindex $msg 3]\n[lindex $msg 4]\n[lindex $msg 5]\n[lindex $msg 7]\n[lindex $msg 6]\n"
    set mesg "[list [lindex $msg 0]\n[lindex $msg 1]\n[lindex $msg 2]\n[lindex $msg 3]\n[lindex $msg 4]\n[lindex $msg 5]\n[lindex $msg 7]\n[lindex $msg 6]]\n"
    if $ExecSh {
      catch {exec sh -c $Module << "$modin" >@stdout &} oops
    } else {
      catch {exec $Module << "$modin" >@stdout &} oops
    }
  }
  logMsg $::LEVEL1 "$modtype\nSent $Module $mesg"
}

# display CID information or message
# Input: $ciddate $cidtime $cidnumber $cidname $cidline $type "" ""
#        $msgdate $msgtime $msgnumber $msgname $msgline $type message msgio
# ismsg = 0 for CID and 1 for message
proc displayCID {input ismsg} {
    global Txt historyTextWidth

    if {$ismsg} {
       set maxwidth [expr $historyTextWidth - 30]
       set firstNewline [string first "\n" $input]
       if {($firstNewline == [expr [string length $input] - 1]) && ([string length $input] > $maxwidth)} {
          logMsg $::LEVEL2 "Truncating long message of length [string length $input] to fit within width $maxwidth"
          set Txt "[string range [string trim $input] 0 $maxwidth](truncated)"
       } else {
        # string is not too long OR string has embedded new lines already
        set Txt $input
       }
    } else {
        set Txt "[lindex $input 3]\n[lindex $input 2]"
    }
}

# display Call Log
# Input: $ciddate $cidtime $cidnumber $cidname $cidline $linetype "" ""
# Input: $msgdate $msgtime $msgnumber $msgname $msgline $linetype $msgtype message
proc displayLog {input ismsg} {
    global Module NoGUI
    global display_line_num DoingCallLog
    global nmbrWidth nameWidth lineIDWidth mtypeWidth
    global hup label
    global hupColor ltColor dateColor timeColor lineColor nmbrColor nameColor

    if $NoGUI {
        if {$Module == ""} {
            if $ismsg {
                if {[lindex $input 1] eq {}} {
                    logMsg $::LEVEL1 "[lindex $input 6]: [lindex $input 7]"
                } else {
                    logMsg $::LEVEL1 "[lindex $input 5]: [lindex $input 0]  [lindex $input 1] [lindex $input 4] [lindex $input 2] [lindex $input 3] [lindex $input 6] [lindex $input 7]"
                }
            } else {
                logMsg $::LEVEL1 "[lindex $input 5]: [lindex $input 0]  [lindex $input 1] [lindex $input 4] [lindex $input 2] [lindex $input 3]"
            }
        }
        incr display_line_num
    } else {
        # GUI
        incr display_line_num
        if {! $DoingCallLog} {.vh configure -state normal}
        if {[lindex $input 1] eq {}} {
        .vh insert end "\n[lindex $input 5]: " lttag [lindex $input 7] msgtag
        } else {
            set ciddate [lindex $input 0]
            set cidtime [lindex $input 1]
            set cidnmbr [format "%${nmbrWidth}.${nmbrWidth}s" [lindex $input 2]]
            set cidname [format "%-${nameWidth}.${nameWidth}s" [lindex $input 3]]
            set cidline [format "%-${lineIDWidth}.${lineIDWidth}s" \
                         [lindex $input 4]]
            set linetype [lindex $input 5]

            if {$label != 6 && $linetype == "HUP"} {

                # hup array used to restore normal color to $linetype
                set lineid [string trimright $cidline]
                array set hup "{$lineid} $display_line_num"

                # set HUP active color to $linetype
                .vh insert end "\n$linetype: " huptag
            } else {
                # set theme color to $linetype
                .vh insert end "\n$linetype: " lttag
            }

            .vh insert end "$ciddate "   datetag \
                           "$cidtime "   timetag \
                           "$cidline "   linetag \
                           "$cidnmbr "   nmbrtag \
                           "$cidname "   nametag

            if $ismsg {
                set msgtype [format "%-${mtypeWidth}.${mtypeWidth}s" \
                             [lindex $input 6]]
                set message [lindex $input 7]
                .vh insert end "$msgtype " mttag $message msgtag
            }
        }
        if {! $DoingCallLog} {
            if {$display_line_num == 1} {
                .vh delete 1.0 2.0
            }
            .vh yview moveto 1.0
            .vh configure -state disabled
            if {[lindex [.vh yview] 0] + [lindex [.vh yview] 1] == 1.0} {
                grid .ys
            }
        }
    }
}

#https://www.rosettacode.org/wiki/Word_wrap#Tcl
#label widgets don't have a -wrap option so use this
proc wrapParagraph {width text} {
    regsub -all {\s+} [string trim $text] " " text
    set RE "^(.{1,$width})(?:\\s+(.*))?$"
    for {set result ""} {[regexp $RE $text -> line text]} {} {
	append result $line "\n"
    }
    return [string trimright $result "\n"]
}

# Open a connection to the NCID server
proc connectCID {} {
    global Host Port
    global Try Delay
    global Socket menuDisabled
    global NoGUI
    global VersionInfo VersionIDENT HostnameFlag hostname
    global Module dtfile
    global CallLog

    set ServerOptions ""
    set Socket 0

    if {!$NoGUI} {
      if {$::tcl_platform(platform) == "unix"} {
        set menu .menubar.file

        # enable or disable autostart menu
        if ![file isfile $dtfile] {
          $menu entryconfigure Auto*Start -state disabled
        } else {
          $menu entryconfigure Auto*Start -state normal
        }
      }
      set menu .menubar.server
    }

    logServerAddress
    logMsg $::LEVEL3 "Attempting to connect"

    while (1) {
        # open socket to server
        if {[catch {set Socket [socket $Host $Port]} msg]} {
            if {!$NoGUI} {
                if {$menuDisabled == 0} {
                    $menu entryconfigure Reload* -state disabled
                    $menu entryconfigure Update*current* -state disabled
                    $menu entryconfigure Update*all*call* -state disabled
                    $menu entryconfigure Reread* -state disabled
                    $menu entryconfigure Dial*Number* -state disabled
                    set menuDisabled 1

                  # a delay of 1 second causes Reconnect to break ncid
                  if {$Delay == 1} {
                    .menubar.file entryconfigure Reconnect* -state disabled
                  }
                }
            }
            set Try [expr $Try + 1]
            tryCount "$Host:$Port - $msg\n"
        } else {
            # set socket to non-blocking
            fconfigure $Socket -blocking 0
            # get response from server as an event
            fileevent $Socket readable getCID

            if {!$NoGUI && $menuDisabled} {
                $menu entryconfigure Reload* -state normal
                $menu entryconfigure Update*current* -state normal
                $menu entryconfigure Update*all*call* -state normal
                $menu entryconfigure Reread* -state normal
                set menuDisabled 0

              if {$Delay == 1} {
                .menubar.file entryconfigure Reconnect* -state normal
              }
            }

            puts $Socket "HELLO: IDENT: $VersionIDENT"
            flush $Socket
            logMsg $::LEVEL1 "HELLO: IDENT: $VersionIDENT"
            if $NoGUI {
                logMsg $::LEVEL1 "Connected to $Host:$Port"
                if $CallLog {
                    # tell server to send call log
                    puts $Socket "HELLO: CMD: log"
                    flush $Socket
                    logMsg $::LEVEL1 "Sent: HELLO: CMD: log"
                } else {
                    # tell server to not send call log
                    puts $Socket "HELLO: CMD: no_log"
                    flush $Socket
                    logMsg $::LEVEL1 "Sent: HELLO: CMD: no_log"
                }
            } else {
                clearLog
                displayCID "Connected to\n$Host:$Port" 1
                logMsg $::LEVEL1 "Connected to $Host:$Port"
            }
        break
        }
    }
}

# valid option with argument examples: -v 1 | -v1 | --verbose 1 | --verbose=1
# combined options are not handled, for example: -AcHXW
proc handleOptArg {} {
    global Usage Opt OptArg OptCnt

    set gotarg 0
    if {[regexp {^--} $Opt]} {
        set len [string length $Opt]
        set pos [string first = $Opt]
        if {$pos != -1 && $len > $pos} {
            set OptArg [string range $Opt [expr $pos + 1] $len]
            set Opt [string range $Opt 0 [expr $pos - 1]]
        } else {incr OptCnt}
    } else {
        if {[string length $Opt] > 2} {
            # single letter option combined with argument
            set OptArg [string range $Opt 2 [string length $Opt]]
            set Opt [string range $Opt 0 1]
        } else {incr OptCnt}
   }

   if {$OptArg == ""} {exitMsg 6 "Missing $Opt argument\n$Usage\n"}
}

proc getArg {} {
    # note: debug log has not been opened yet so logMsg is unavailable
    global Opt OptArg OptCnt OptPmsg delayedMsgs
    global Host Port Hosts HostIndex oldHost oldPort SelectedLineIDs
    global Delay
    global Usage
    global NoGUI
    global Verbose
    global LogEnable LogDir
    global Module ModDir Ring CallOnRing
    global HostnameFlag
    global PIDfile
    global PopupTime
    global NoExit
    global AltDate
    global WakeUp
    global Version
    global WrapLines
    global CallLog
    global NightMode Country

    set showUsage 0
    set hostport 0

    for {set OptCnt 0} {$OptCnt < $::argc} {incr OptCnt} {
        set OptArg [lindex $::argv [expr $OptCnt + 1]]
        switch -regexp -- [set Opt [lindex $::argv $OptCnt]] {
            {^-r} -
            {^--ring$|^--ring=} {
                handleOptArg
                if {[regexp {^-[129]$} $OptArg]
                    || [regexp {^[0123456789]$} $OptArg]} {
                    set Ring $OptArg
                    set CallOnRing 1
                } else {exitMsg 4 "Invalid $Opt argument: $OptArg\n$Usage\n"}
            }
            {^--no-gui$} {set NoGUI 1}
            {^--night-mode$} {set NightMode 1}
            {^-A$} -
            {^--alt-date$} {set AltDate 1}
            {^-c$} -
            {^--call-log$} {set CallLog 1}
            {^-C} -
            {^--country-code$|^--country-code=} {
                handleOptArg
                checkCountry $OptArg
                set Country $OptArg
            }
            {^-D} -
            {^--delay$|^--delay=} {
                handleOptArg
                if {[regexp {^[0-9]+$} $OptArg]} {
                    set Delay $OptArg
                } else {exitMsg 4 "Invalid $Opt argument: $OptArg\n$Usage\n"}
            }
            {^-h$} -
            {^--help$} {set showUsage 1; set NoGUI 1}
            {^-H$} -
            {^--hostname-flag$} {set HostnameFlag 1}
            {^-l} -
            {^--log-enable$|^--log-enable=} {
                handleOptArg
                if {[regexp {^[0-2]+$} $OptArg]} {
                    set LogEnable $OptArg
                } else {exitMsg 4 "Invalid $Opt argument: $OptArg\n$Usage\n"}
            }
            {^-L} -
            {^--log-dir$|^--log-dir=} {
                handleOptArg
                if {$OptArg == ""} {
                    exitMsg 4 "Invalid $Opt argument: $OptArg\n$Usage\n"}
                set LogDir $OptArg
            }
            {^-m} -
            {^--module$|^--module=} -
            {^-P$} -
            {^--program$} {
                if {$Opt == "-P" || $Opt == "--program"} {
                    set delayedMsgs \
                    "$delayedMsgs\n***** WARNING: option -P|--program is deprecated, use option -m|--module"
                }
                handleOptArg
                if {[regexp {^.*/} $OptArg]} {
                    set Module [list $OptArg]
                } else {set Module [list $ModDir/$OptArg]}
            }
            {^-p} -
            {^--pidfile$|^--pidfile=} {
                handleOptArg
                if {[regexp {^[\w./]+} $OptArg]} {
                    set PIDfile $OptArg
                } else {exitMsg 4 "Invalid $Opt argument: $OptArg\n$Usage\n"}
            }
            {^-t} -
            {^--PopupTime$^--PopupTime=} {
                handleOptArg
                if {[regexp {^[0-5]$} $OptArg]} {
                    set PopupTime $OptArg
                } else {exitMsg 4 "Invalid $Opt argument: $OptArg\n$Usage\n"}
            }
            {^-v} -
            {^--verbose$|--verbose=} {
                handleOptArg
                if {[regexp {^[1-9]+$} $OptArg]} {
                    set Verbose $OptArg
                } else {exitMsg 4 "Invalid $Opt argument: $OptArg\n$Usage\n"}
            }
            {^-V$} -
            {^--version$} {set NoGUI 1; puts "ncid (NCID) $Version"; exit 0}
            {^-X$} -
            {^--noexit} {set NoExit 1}
            {^-W$} -
            {^--wakeup$} {set WakeUp 1}
            {^-.*$} {set NoGUI 1; exitMsg 5 "Unknown option: $Opt\n$Usage\n"}
            {^\d+$} {set Port $Opt; set hostport 1}
            {^.+$} {set Host $Opt; set hostport 1}
            default {set NoGUI 1; exitMsg 5 "Unknown option: $Opt\n$Usage\n"}
        }
    }

    if {$showUsage} {exitMsg 1 "$Usage\n"}

    if {$hostport} {
        set SelectedLineIDs ""
        if {[regexp {:} $Host]} {
            set Hosts $Host
            lassign [split $Hosts ":"] Host Port
        } else {
            set Hosts "$Host:$Port"
        }
        set delayedMsgs \
            "$delayedMsgs\nncidd address temporarily changed to $Host:$Port"
    }
    set oldHost ""
    set oldPort ""
}

# ttk Widgets: http://wiki.tcl-lang.org/14796
# https://tkdocs.com/tutorial/styles.html
# Styles and themes: http://www.tkdocs.com/tutorial/styles.html#using
# Changing ttk Widget Colors: https://wiki.tcl.tk/37973
#   A collection of all the information on setting the
#   colors of modern widgets in one place.
#
# sets styles for Day, Night, and themes (if needed)
proc setStyles {} {
  global ThemeName fgColor bckColor mtColor bckSelColor fgSelColor
  global hupColor dateColor timeColor lineColor nmbrColor nameColor
  global tvFgColor  tvBckColor \
  global vhFgColor  vhBckColor vhInsColor

  set enabletheme 0
  set m .menubar
  set items "file file.auto file.startup server server.hosts server.dial \
             server.copy view view.types view.lines prefs theme help"

  switch $ThemeName {
    night {
      set fgColor "yellow"
      set fg2Color "white"
      set fg3Color "cyan"
      set fgActColor "white"
      set fgSelColor "red"
      set bckColor "#262626"
      set bckActColor "#215d9c"
      set bckSelColor "#262626"
      set bck2SelColor "blue"
      set bckFldColor "#0f0f00"
      set bckFldActColor "#215d9c"
      set roColor "#0f0f00"
      set arrowColor "white"
      set arrowActColor "black"
      set indColor "black"
      set indSelColor "white"
      set indPressColor "white"
      set truColor "#3c3c3c"
      set selColor "white"
      set insColor "white"

      # text colors
      set tvFgColor  "yellow"
      set tvBckColor "#262626"
      set vhFgColor  "white"
      set vhBckColor "#262626"
      set vhInsColor "white"

      # history window column colors
      .vh tag configure huptag  -foreground white   -selectbackground blue
      .vh tag configure lttag   -foreground #7fff7f -selectbackground blue
      .vh tag configure datetag -foreground yellow  -selectbackground blue
      .vh tag configure timetag -foreground cyan    -selectbackground blue
      .vh tag configure linetag -foreground #7fff7f -selectbackground blue
      .vh tag configure nmbrtag -foreground yellow -selectbackground blue
      .vh tag configure nametag -foreground cyan   -selectbackground blue
      .vh tag configure mttag   -foreground yellow -selectbackground blue
      .vh tag configure msgtag  -foreground white  -selectbackground blue
    }
    day {
      set fgColor "blue"
      set fg2Color "black"
      set fg3Color "green"
      set fgActColor "white"
      set fgSelColor "red"
      set bckColor "#d9d9d9"
      set bckActColor "#4a90d9"
      set bckSelColor "#d9d9d9"
      set bck2SelColor "blue"
      set bckFldColor "#f0f0ff"
      set bckFldActColor "#4a90d9"
      set roColor "#f0f0ff"
      set arrowColor "black"
      set arrowActColor "white"
      set indColor "#d9d9d9"
      set indSelColor "black"
      set indPressColor "black"
      set truColor "#c9c9c9"
      set selColor "black"
      set insColor "black"

      #text colors
      set tvBckColor "white"
      set tvFgColor  "blue"
      set vhBckColor "white"
      set vhFgColor  "black"
      set vhInsColor "white"

      # history window column colors
      .vh tag configure huptag  -foreground black   -selectbackground lightgreen
      .vh tag configure lttag   -foreground purple  -selectbackground lightgreen
      .vh tag configure datetag -foreground blue    -selectbackground lightgreen
      .vh tag configure timetag -foreground red     -selectbackground lightgreen
      .vh tag configure linetag -foreground purple  -selectbackground lightgreen
      .vh tag configure nmbrtag -foreground blue    -selectbackground lightgreen
      .vh tag configure nametag -foreground red     -selectbackground lightgreen
      .vh tag configure mttag   -foreground blue    -selectbackground lightgreen
      .vh tag configure msgtag  -foreground black   -selectbackground lightgreen
    }
    default {
      set enabletheme 1

      set fgColor "black"
      set fg2Color "black"
      set fg3Color "black"
      set fgActColor "black"
      set fgSelColor "black"
      set bckColor "#d9d9d9"
      set bckActColor "#d9d9d9"
      set bckSelColor "#d9d9d9"
      set bck2SelColor "blue"
      set bckFldColor "#f0f0ff"
      set bckFldActColor "#d9d9d9"
      set roColor "#f0f0ff"
      set arrowColor "black"
      set arrowActColor "black"
      set indColor "#d9d9d9"
      set indSelColor "black"
      set indPressColor "black"
      set truColor "#c9c9c9"
      set selColor "black"
      set insColor "black"

      #text colors
      set tvBckColor "white"
      set tvFgColor  "black"
      set vhBckColor "white"
      set vhFgColor  "black"
      set vhInsColor "white"

      # history window column colors
      .vh tag configure huptag  -foreground black -selectbackground #d9d9d9
      .vh tag configure lttag   -foreground black -selectbackground #d9d9d9
      .vh tag configure datetag -foreground black -selectbackground #d9d9d9
      .vh tag configure timetag -foreground black -selectbackground #d9d9d9
      .vh tag configure linetag -foreground black -selectbackground #d9d9d9
      .vh tag configure nmbrtag -foreground black -selectbackground #d9d9d9
      .vh tag configure nametag -foreground black -selectbackground #d9d9d9
      .vh tag configure mttag   -foreground black -selectbackground #d9d9d9
      .vh tag configure msgtag  -foreground black -selectbackground #d9d9d9
    }
  }
  .tv configure -background $tvBckColor -foreground $tvFgColor \
                -selectbackground $tvBckColor -selectforeground $tvFgColor
  .vh configure -background $vhBckColor -foreground $vhFgColor \
                -insertbackground $vhInsColor
  $m configure -background $bckColor -foreground $fg2Color
  foreach item $items {
    $m.$item configure -background $bckColor -foreground $fg2Color \
      -selectcolor $selColor
  }
if {$::tcl_platform(platform) == "unix"} {
    ttk::style configure TButton \
      -background $bckColor \
      -foreground $fg2Color
    ttk::style map TButton \
      -background [list active $bckActColor] \
      -foreground [list active $fgActColor]
  }
  ttk::style configure TEntry \
    -foreground $fg3Color \
    -fieldbackground $bckFldColor \
    -selectbackground $bckSelColor \
    -selectforeground $fgSelColor \
    -insertcolor $insColor
  ttk::style configure TFrame \
    -background $bckColor
  ttk::style configure TLabel \
    -background $bckColor \
    -foreground $fgColor
  ttk::style configure TLabelframe \
    -background $bckColor \
    -foreground $fg2Color
  ttk::style configure TSpinbox \
    -arrowsize 15 \
    -arrowcolor $arrowColor \
    -background $bckColor \
    -foreground $fg2Color \
    -selectbackground $bck2SelColor
  ttk::style map TSpinbox \
    -background [list active $bckActColor] \
    -fieldbackground [list active $bckFldColor readonly $roColor] \
    -relief [list {pressed !disabled} sunken]
  ttk::style configure TRadiobutton \
    -indicatorcolor $indColor \
    -background $bckColor \
    -foreground $fgColor
  ttk::style map TRadiobutton \
    -indicatorcolor [list selected $indSelColor pressed $indPressColor] \
    -background [list active $bckActColor] \
    -foreground [list active $fgActColor]
  ttk::style configure TCheckbutton \
    -indicatorcolor $indColor \
    -background $bckColor \
    -foreground $fgColor
  ttk::style map TCheckbutton \
    -indicatorcolor [list selected $indSelColor pressed $indPressColor] \
    -background [list active $bckActColor]
  ttk::style configure TScrollbar \
    -arrowsize 15 \
    -arrowcolor $arrowColor \
    -background $bckColor \
    -troughcolor $truColor
  ttk::style map TScrollbar \
    -arrowcolor [list active $arrowActColor] \
    -background [list active $bckActColor] \
    -relief [list {pressed !disabled} sunken]
  ttk::style configure TCombobox \
    -arrowsize 15 \
    -arrowcolor $arrowColor \
    -background $bckColor \
    -foreground $fgColor \
    -fieldbackground $bckColor \
    -selectbackground $bckColor \
    -selectforeground $fgColor
  ttk::style map TCombobox \
    -arrowcolor [list active $arrowActColor] \
    -background [list active $bckActColor] \
    -relief [list {pressed !disabled} sunken]
  . configure -background $bckColor

  if $enabletheme {
    #ttk::style theme use $ThemeName
    ttk::setTheme $ThemeName
  } else {
    # Day and Night styles modify the default theme
    ttk::setTheme "default"
  }
}

proc processRCfile {} {
    global rcfile ExitOn fontList PortableDir wmGeometry
    global autoSave oldAutoSave autoStart oldAutoStart
    global clock oldClock AltDate oldAltDate DateSepar oldDateSepar
    global ThemeName oldThemeName delayedMsgs
    global TypeGroups oldTypeGroups SelectedTypes oldSelectedTypes
    global LineIDGroups oldLineIDGroups SelectedLineIDs oldSelectedLineIDs
    global Host Port Hosts HostIndex Leading1 oldLeading1
    global DefaultHost DefaultPort ConfigFileHost ConfigFilePort

    if [expr [file exists $rcfile] && [file isfile $rcfile]] {
        set id [open $rcfile]
        set data [read $id]
        close $id
    } else {
        set data "no data"
    }

    set lines [split $data "\n"]

    foreach line $lines {
        if [regexp {geometry\s+\S+\s+[0-9x]+} $line] {
            eval $line
            set wmGeometry [regsub {\w+\s+\w+\s+\S+\s+([\dx+]+)} $line {\1}]
        } elseif [regexp {font\s+create} $line] {
            eval $line
        } elseif [regexp {(:?fontList|clock|AltDate|DateSepar|NightMode|\
                             autoSave|autoStart|TypeGroups|SelectedTypes|\
                             LineIDGroups|SelectedLineIDs|ThemeName|\
                             Leading1|Host|Port|\
                             )\s+} $line] {
            eval $line
        }
    }

    # initial values, not final values
    set oldHost $Host
    set oldPort $Port

    # determine Host, Port and Hosts values
    if {$ConfigFileHost == ""} {
        if {$Host == ""} {set Host $DefaultHost}
    } else {
        set Host $ConfigFileHost
    }
    if {$ConfigFilePort == ""} {
        if {$Port == ""} {set Port $DefaultPort}
    } else {
        set Port $ConfigFilePort
    }

    # host and port variables may have been replaced by the .ncid RC file
    # make sure there is a match against the list of hosts
    if {$Hosts == ""} {
        set Hosts "$DefaultHost:$DefaultPort"
        set delayedMsgs \
            "$delayedMsgs\nempty Hosts list set to $DefaultHost:$DefaultPort"
        set HostIndex 0
        lassign [split [lindex $Hosts $HostIndex] ":"] Host Port
        write_rc_file "set Host" "set Host $Host"
        write_rc_file "set Port" "set Port $Port"
    } else {
        set HostIndex [lsearch -regexp $Hosts $Host:$Port]
        if {$HostIndex == -1} {
            # the config file changed since the rcfile was last updated
            set delayedMsgs \
                "$delayedMsgs\n$Host:$Port not in list of Hosts: \"$Hosts\""
            set HostIndex 0
            lassign [split [lindex $Hosts $HostIndex] ":"] Host Port
            set delayedMsgs \
                "$delayedMsgs\nUsing first entry in list of hosts: $Host:$Port"
        }    
        write_rc_file "set Host" "set Host $Host"
        write_rc_file "set Port" "set Port $Port"
    }

    if {$Host != $oldHost} {
        # the RC file does not contain "set Host"
        write_rc_file "set Host" "set Host $Host"
    }

    if {$Port != $oldPort} {
        # the RC file does not contain "set Port"
        write_rc_file "set Port" "set Port $Port"
    }

    # remove NightMode from rcfile and add ThemeName to rcfile
    if {[info exists NightMode]} {
        if {$NightMode == 1} {set ThemeName "night"} else {set ThemeName "day"}
        write_rc_file "set ThemeName" "set ThemeName \"$ThemeName\""
        write_rc_file "" "set NightMode"
        set delayedMsgs \
            "$delayedMsgs\nNightMode \"$NightMode\" in rc file converted to ThemeName \"$ThemeName\""
    }

    set oldClock $clock
    set oldAltDate $AltDate
    set oldDateSepar $DateSepar
    set oldLeading1 $Leading1
    set oldThemeName $ThemeName
    set oldAutoSave $autoSave
    set oldAutoStart $autoStart
    set oldTypeGroups $TypeGroups
    set oldSelectedTypes $SelectedTypes
    set oldLineIDGroups $LineIDGroups
    set oldSelectedLineIDs $SelectedLineIDs
}

proc logRCfileOldNewVarChange {verboseLevel oldVarName newVarName} {

     # prints "rcfile and $newVarName..."
     upvar $oldVarName oldVarValue
     upvar $newVarName newVarValue
     if {$oldVarValue != $newVarValue} {
        if {[string length $oldVarValue] >30 || [string length $newVarValue] >30} {
           logMsg $verboseLevel "rcfile and $newVarName have been changed"
           logMsg $verboseLevel "    from: $oldVarValue"
           logMsg $verboseLevel "      to: $newVarValue"
        } else {
           logMsg $verboseLevel "rcfile and $newVarName have been changed from: $oldVarValue to: $newVarValue"
        }
     }
}

proc do_nothing {} {
}

proc do_goodbye {} {
    global Socket

    if {$Socket > 0} {puts $Socket "GOODBYE"}
}

proc makeWindow {} {
    global ExitOn
    global Verbose WrapLines viewTextWidth
    global fontList m currentFont
    global clock autoSave autoStart
    global DateSepar YearDot
    global Hosts HostIndex
    global nameREQ nmbrREQ lineREQ historyTextWidth timeWidth
    global lbl1 lbl2 lbl3 lbl4 lbl5 lbl6 lbl7 lbl8
    global wmGeometry ThemeName
    global TypeGroups LineIDGroups DiscoveredLineIDs

    wm title . "Network Caller ID"
    wm protocol . WM_DELETE_WINDOW $ExitOn

    set auto [expr \"$autoSave\" eq \"off\" ? \"normal\" : \"disabled\"]

    if {$fontList == ""} {scanFonts}

    if {[catch {font configure FixedFontH}]} {
        font create FixedFontH -family "$currentFont" -size 12
        write_rc_file "FixedFontH" \
                "font create FixedFontH [font configure FixedFontH]"
    }
    if {[catch {font configure FixedFontM}]} {
        font create FixedFontM -family "$currentFont" -size 12
        write_rc_file "FixedFontM" \
                "font create FixedFontM [font configure FixedFontM]"
    }
    if {[catch {font configure FixedFontP}]} {
        font create FixedFontP -family "$currentFont" -size 12
                write_rc_file "FixedFontP" \
                "font create FixedFontP [font configure FixedFontP]"
    }

    ttk::style configure TButton -font FixedFontP
    ttk::style configure TCheckbutton -font FixedFontP
    ttk::style configure TRadiobutton -font FixedFontP

    # menu options: no tearoff and help menu on far right
    option add *tearOff 0
    option add *Menu.useMotifHelp 1
    option add *Text.relief sunken
    option add *Text.borderWidth 2
    option add *highlightThickness 1
#
    # create menubar
    menu .menubar
    . configure -menu .menubar

    # create and place: column labels
    if {$clock == 24 && $DateSepar == "."} {set lbl $lbl1}
    if {$clock == 24 && $DateSepar == "-"} {set lbl $lbl2}
    if {$clock == 24 && $DateSepar == "/"} {set lbl $lbl3}
    if {$clock == 12 && $DateSepar == "."} {set lbl $lbl4}
    if {$clock == 12 && $DateSepar == "-"} {set lbl $lbl5}
    if {$clock == 12 && $DateSepar == "/"} {set lbl $lbl6}
    if {$clock == 24 && $DateSepar == "." && $YearDot == 1} {set lbl $lbl7}
    if {$clock == 12 && $DateSepar == "." && $YearDot == 1} {set lbl $lbl8}
    ttk::label .la -text $lbl -justify left -font {FixedFontH}
    grid .la -row 1 -sticky w -columnspan 2

    # create File, Server, Preferences, View, Theme and Help menus
    set m .menubar
    menu $m.file
    menu $m.file.auto
    menu $m.file.startup
    menu $m.server
    menu $m.server.hosts
    menu $m.server.dial
    menu $m.server.copy
    menu $m.prefs
    menu $m.view
    menu $m.view.types
    menu $m.view.lines
    menu $m.theme
    menu $m.help
    $m add cascade -menu $m.file -label File -underline 0 -font FixedFontM
    $m add cascade -menu $m.server -label Server -underline 0 -font FixedFontM
    $m add cascade -menu $m.prefs -label Preferences -underline 0 -font FixedFontM
    $m add cascade -menu $m.view -label View -underline 0 -font FixedFontM
    $m add cascade -menu $m.theme -label Themes -underline 0 -font FixedFontM
    $m add cascade -menu $m.help -label Help -underline 0 -font FixedFontM

    # create File menu items
    $m.file add command -label "Clear Log" -command clearLog -font FixedFontM
    $m.file add command -label "Reconnect" -command Reconnect -font FixedFontM
    $m.file add separator
    if {$::tcl_platform(platform) == "unix"} {
    $m.file add cascade -menu $m.file.startup -label "Auto Start" -font FixedFontM
    $m.file add separator
  }
    $m.file add cascade -menu $m.file.auto -label "Auto Save" -font FixedFontM
    $m.file add command -label "Save Size" -state $auto -command {saveSize 0} -font FixedFontM
    $m.file add command -label "Save Size and Position" -state $auto -command {saveSize 1} -font FixedFontM
    $m.file add separator
    $m.file add command -label Quit -command {do_goodbye; exit} -font FixedFontM

    $m.file.auto add radiobutton -label "Size" -variable autoSave -value "size" -command {logAuto $m.file} -font FixedFontM
    $m.file.auto add radiobutton -label "Size and Position" -variable autoSave -value "both" -command {logAuto $m.file} -font FixedFontM
    $m.file.auto add radiobutton -label "Off" -variable autoSave -value "off" -command {logAuto $m.file} -font FixedFontM

    $m.file.startup add radiobutton -label "On" -variable autoStart -value "on" -command {logStart} -font FixedFontM
    $m.file.startup add radiobutton -label "On with desktop notifications" -variable autoStart -value "on+alert" -command {logStart} -font FixedFontM
    $m.file.startup add radiobutton -label "On only desktop notifications" -variable autoStart -value "alert" -command {logStart} -font FixedFontM
    $m.file.startup add radiobutton -label "Off" -variable autoStart -value "off" -command {logStart} -font FixedFontM

    # create Server menu items
    $m.server add cascade -menu $m.server.hosts -label "Select NCID Server" -state normal   -font FixedFontM
    set x 0
    foreach i $Hosts {
        $m.server.hosts add radiobutton -label "$i" -variable HostIndex \
            -value $x -command {logHosts} -font FixedFontM
        incr x
    }
    $m.server add separator
    $m.server add command -label "Reload alias, blacklist and whitelist files" -command {
                Disable $m
                puts $Socket "REQ: RELOAD"
                flush $Socket
            } -font FixedFontM
    $m.server add command -label "Update current call log" -command {
                global multi

                Disable $m
                puts $Socket "REQ: UPDATE"
                flush $Socket
                set multi 0
            } -font FixedFontM
    $m.server add command -label "Update all call logs" -command {
                global multi

                Disable $m
                puts $Socket "REQ: UPDATES"
                flush $Socket
                set multi 1
            } -font FixedFontM
    $m.server add command -label "Reread call log" -command {
                global display_line_num

                set display_line_num 0
                clearLog
                puts $Socket "REQ: REREAD"
                flush $Socket
            } -font FixedFontM
    $m.server add separator
    $m.server add cascade -menu $m.server.dial -label "Dial Number" -state disabled -font FixedFontM
    $m.server.dial add command -label "Manually" -command { doDial "any" } -font FixedFontM
    $m.server.dial add command -label "From History" -state disabled -command { doDial "history" } -font FixedFontM
    $m.server add separator
    $m.server add command -label "Add/Modify/Remove Alias in Alias File" -state disabled -command { doList alias "" "" } -font FixedFontM
    $m.server add separator
    $m.server add command -label "Add to Blacklist File" -state disabled -command {
                doList black add ""
            } -font FixedFontM
    $m.server add command -label "Remove from Blacklist File" -state disabled -command {
                global argument
                doList black remove $argument
            } -font FixedFontM
    $m.server add separator
    $m.server add command -label "Add to Whitelist File" -state disabled -command {
                doList white add ""
            } -font FixedFontM
    $m.server add command -label "Remove from Whitelist File" -state disabled -command {
                global argument
                doList white remove $argument
            } -font FixedFontM
    $m.server add separator
    $m.server add cascade -menu $m.server.copy -label "Copy to Clipboard" -state disabled -font FixedFontM
    $m.server.copy add command -label "Name" -command { doClipboard 4 [lindex $dataDump 16] } -font FixedFontM
    $m.server.copy add command -label "Number Formatted" -command { doClipboard 1 [lindex $dataDump 13] } -font FixedFontM
    $m.server.copy add command -label "Number Digits" -command { doClipboard 2 [lindex $dataDump 13] } -font FixedFontM
    $m.server.copy add command -label "Entire Line" -command { doClipboard 3 $dataDump } -font FixedFontM

    # create Preferences menu items
    $m.prefs add command -label "Font..." -command {changeFont} -font FixedFontM
    $m.prefs add separator
    $m.prefs add command -label "Date and Time..." -command {formatDT} -font FixedFontM

    # create View menu items
    $m.view add cascade -menu $m.view.types -label "Line Types" -font FixedFontM
    $m.view add cascade -menu $m.view.lines -label "LineIDs" -font FixedFontM

    $m.view.types add radiobutton -label "All" \
        -variable TypeGroups -value 0 \
        -command {logTypeGroups} -font FixedFontM
    $m.view.types add separator
    $m.view.types add radiobutton -label "Calls" \
        -variable TypeGroups -value 1 \
        -command {logTypeGroups} -font FixedFontM
    $m.view.types add radiobutton -label "Messages" \
        -variable TypeGroups -value 2 \
        -command {logTypeGroups} -font FixedFontM
    $m.view.types add radiobutton -label "Smart Phone" \
        -variable TypeGroups -value 3 \
        -command {logTypeGroups} -font FixedFontM
    $m.view.types add separator
    $m.view.types add radiobutton -label "Select..." \
        -variable TypeGroups -value 4 \
        -command {doSelectedTypes} -font FixedFontM

    $m.view.lines add radiobutton -label "All" \
        -variable LineIDGroups -value 0 \
              -command {logViewLineIDs} -font FixedFontM
    $m.view.lines add separator
    $m.view.lines add radiobutton -label "Select..." \
        -variable LineIDGroups -value 1 \
        -command {doLineIDs} -font FixedFontM

    # create themes menu items
    $m.theme add radiobutton -label "Day" \
        -variable ThemeName -value "day" \
        -command {logTheme} -font FixedFontP
    $m.theme add radiobutton -label "Night" \
        -variable ThemeName -value "night" \
        -command {logTheme} -font FixedFontP
    $m.theme add radiobutton -label "Default" \
        -variable ThemeName -value "default" \
        -command {logTheme} -font FixedFontP
    if {$::DistThemes ne "default"} {$m.theme add separator}

    #Observed values
    #Windows 10:         alt clam classic default    vista winnative xpnative
    #Mac:                alt clam classic default    aqua
    #AndroWish:          alt clam classic default    droid
    #Fedora 25 cinnamon: alt clam classic default
    foreach t $::DistThemes {
        if {$t eq "default"} {continue}
        $m.theme add radiobutton -label [join [list [string totitle $t]]] \
        -variable ThemeName -value $t \
        -command {logTheme} -font FixedFontP
    }

    if {$::AddonThemes != ""} {
        $m.theme add separator
        foreach t $::AddonThemes {
            $m.theme add radiobutton -label [join [list [string totitle $t]]] \
            -variable ThemeName -value $t \
            -command {logTheme} -font FixedFontP
        }
    }

    # create Help menu item
    $m.help add command -label About -command {helpItem "About" $About [ncidInfo]} -font FixedFontM
    $m.help add command -label "Online Docs" -command {helpItem "Online Documentation" "" ""} -font FixedFontM
    $m.help add command -label "Field Labels" -command {helpItem "Field Labels" $fieldList ""} -font FixedFontM
    $m.help add command -label "Line Types" -command {helpItem "Line Types" $labList ""} -font FixedFontM
    $m.help add command -label "Views Window" -command {helpItem "Views Window" $viewHelp ""} -font FixedFontM
    $m.help add command -label "Server Menu" -command {helpItem "Server Menu Help" $serverHelp ""} -font FixedFontM
    $m.help add command -label "Server Options" -command serverOPT -font FixedFontM

    # create and place: CID history scroll window
    if {$clock == 12} {set timeWidth 8}
    set historyTextWidth [expr $historyTextWidth + $timeWidth]
    text .vh -width $historyTextWidth -height 4 -yscrollcommand ".ys set" \
        -state disabled -font {FixedFontH} -setgrid 1 -wrap $WrapLines
    grid [ttk::scrollbar .ys -command ".vh yview"] \
          -column 1 -row 0 -sticky ns -pady 10 -padx 5
    grid .vh -row 2 -sticky nsew -padx 2 -pady 2
    grid .ys -row 2 -column 1 -sticky ns -pady 2

    # create and place: window for view, user message
    ttk::frame .fr
    grid .fr -row 4 -columnspan 2
    text .tv -font {FixedFontM} -height 2 -insertwidth 0 -wrap word \
        -width $viewTextWidth -yscrollcommand {.vsb set}
    .tv tag add strike 1.end
    .tv tag add blank 1.end
    .tv tag configure strike -overstrike 1
    grid .tv -row 3 -columnspan 2
    grid [ttk::scrollbar .vsb -command ".tv yview"] \
          -column 1 -row 0 -sticky ns -pady 10 -padx 5
    grid .vsb -row 3 -column 1
    grid remove .vsb
    bind .tv <KeyPress> break
    if {$DiscoveredLineIDs != ""} {updateViewDisplay}

    ttk::label .ml -text "Send Message: " -font {FixedFontM}
    ttk::entry .im -width 40 -font {FixedFontM}
    grid .ml .im -in .fr

    # create and place: call and server message display
    ttk::label .md -textvariable Txt -font {FixedFontM}
    grid .md -row 5 -columnspan 2

    grid columnconfigure . 0 -weight 1
    grid rowconfigure . 2 -weight 1

    set geometry [wm grid .]
    wm minsize . [lindex $geometry 0] [lindex $geometry 1]
    update

    switch $autoSave {
        "size" {
            $m.file entryconfigure Quit -command {do_goodbye; saveSize 0; exit}
            wm protocol . WM_DELETE_WINDOW {saveSize 0; $ExitOn}
        }
        "both" {
            $m.file entryconfigure Quit -command {do_goodbye; saveSize 1; exit}
            wm protocol . WM_DELETE_WINDOW {saveSize 1; $ExitOn}
        }
    }
    setStyles

    wm geometry . $wmGeometry

    logMsg $::LEVEL4 "History window font: \
        [regsub {\s+\-slant.+$} [font configure FixedFontH] {}]"

    logMsg $::LEVEL4 "Message window and display font: \
        [regsub {\s+\-slant.+$} [font configure FixedFontM] {}]"

    logMsg $::LEVEL4 "Help text: \
        [regsub {\s+\-slant.+$} [font configure FixedFontP] {}]"

    logMsg $::LEVEL4 "Window geometry set to: \
        [regsub {(\d+x\d+)\+(\d+)\+(\d+)} [wm geometry .] {\1 at x=\2 y=\3}]"

    bind . <Configure> {
            if {[lindex [.vh yview] 0] + [lindex [.vh yview] 1] == 1.0} {
                grid remove .ys
            } else {grid .ys}
    }
    bind .fr  <Button-1> {
        Disable $m
    }
    bind .ml  <Button-1> {
        Disable $m
    }
    bind .md  <Button-1> {
        Disable $m
    }
    bind .vh <ButtonRelease-1> {
        .vh tag remove sel 1.0 end
        set first [.vh index @%x,%ylinestart]
        set last [.vh index @%x,%ylineend]
        .vh tag add sel $first $last
        .vh mark unset anchor
        .vh mark unset tk::anchor1
        .vh mark set insert 1.0
        .vh mark set current 1.0
        set dataDump [.vh dump -text $first $last]
        set select_label [string trimright [lindex $dataDump 1]]
        set lineREQ [string trimright [lindex $dataDump 10]]
        set nmbrREQ [string trimright [lindex $dataDump 13]]
        set nmbrREQ [string trimleft $nmbrREQ]
        set nameREQ [string trimright [lindex $dataDump 16]]
        set selected [.vh get $first $last]
        set nmbrREQ [regsub -all -- {[-,]} $nmbrREQ ""]
        if {$nameREQ eq ""} {
            logMsg $::LEVEL1 "$select_label nameREQ is null"
            set menu .menubar.server
            $menu entryconfigure Add*Alias* -state disabled
            if {!$NoGUI} {
                $menu entryconfigure *Blacklist* -state disabled
                $menu entryconfigure *Whitelist* -state disabled
            }
        } else {
            if {!$Try} {
                puts $Socket "REQ: INFO $nmbrREQ&&$nameREQ&&$lineREQ"
                flush $Socket
                logMsg $::LEVEL1 "REQ: INFO $nmbrREQ&&$nameREQ&&$lineREQ"
            } else { logMsg "Server not connected for a REQ: INFO" $::LEVEL1}
        }
        break
    }
}

proc Disable {menu} {
    .vh tag remove sel 1.0 end
    set last [$menu.server index last]
    set found 0
    for {set index 0} {$index <= $last} {incr index} {
        set type [$menu.server type $index]
        # do not disable menu items before the 3rd separator
        if {$found == 3} {
            if {$type ne "separator"} {
                $menu.server entryconfigure $index -state disabled
            }
        } elseif {$type eq "separator"} { set found [expr $found + 1] }
    }
    # need to disable this submenu from not disabled menu entry "Dial Number"
    .menubar.server.dial entryconfigure From*History* -state disabled
}

proc remove {menu block} {
    set last [$menu index last]
    set found [expr $block == 0 ? 1 : 0]
    for {set index 0} {$index <= $last} {incr index} {
        set type [$menu type $index]
        if {$found} {
            $menu delete $index
            set index [expr $index - 1]
            if {$type eq "separator"} {
                break
            }
        } elseif {$type eq "separator"} {
            set block [expr $block - 1]
            set found [expr $block == 0 ? 1 : 0]
            continue
        }
    }
}

proc doRPLY {} {
    global ClientJobResult bckColor

    toplevel .rply -background $bckColor
    wm title .rply "Dial Reply"
    wm resizable .rply 0 0

    grid [ttk::label .rply.mesg -font FixedFontP -justify left -textvariable ClientJobResult ] -columnspan 2 -padx 12

    grid [ttk::button .rply.ok -text "Close" -command { destroy .rply}] \
          -columnspan 2 -pady 10

    modal {.rply}
}

proc doDial {dialtype} {
  global nmbrREQ nameREQ lineREQ DialPrefix DialLineID ClientJobResult
  global bckColor fgColor bckSelColor fgSelColor
  global Country nmbrWidth wantdial

  toplevel .dial -background $bckColor
  wm title .dial "Dial"
  wm resizable .dial 0 0
  if {$DialPrefix == ""} {set dprefix "no"} else {set dprefix $DialPrefix}
  if {$dialtype == "history"} {
    set wantdial 1
    set ht "Request the server dial:\n\nDial: $dprefix prefix\nNmbr: $nmbrREQ\nName: $nameREQ\nLine: $lineREQ"
  } else {
    set wantdial 2
    set nameREQ "No Name"
    set ht "Request the server dial:\n\nDial: $dprefix prefix\nName: $nameREQ"
  }
  set row 1
  grid [ttk::label .dial.rd -justify left -font FixedFontP \
        -text $ht ] \
        -columnspan 2 -padx 12 -pady 10
  grid [ttk::label .dial.cl -justify left -font FixedFontP \
        -text "Server Dial Line:"] -columnspan 2 -padx 12 -column 0
  set lineid_list [list $::ServerOptLineIDS]
  set DialLineID [lindex $lineid_list 0]
  grid [ttk::combobox .dial.lb -font FixedFontP -values $lineid_list \
        -height 2 -textvariable DialLineID] -columnspan 2 -column 0
  .dial.lb set $DialLineID
  set row [expr $row + 2]
  if {$dialtype == "history"} {
    ttk::label .dial.mml -font FixedFontM -text "Leading 1 in number"
    grid [ttk::labelframe .dial.lf -labelwidget .dial.mml -labelanchor "n"] \
          -columnspan 2 -column 0 -pady 5 -padx 40
    if {$Country == "US"} {
      grid [ttk::radiobutton .dial.lf.one -text "Add" \
            -variable Leading1 -value "Add" -command logOne] \
            -row 0 -column 0 -padx 8
      grid [ttk::radiobutton .dial.lf.noone -text "Remove" \
            -variable Leading1 -value "Remove" -command logOne] \
            -row 0 -column 1 -padx 8
      grid [ttk::radiobutton .dial.lf.leave -text "Leave" \
            -variable Leading1 -value "Leave" -command logOne] \
            -row 0 -column 2 -padx 8
    }
  } else {
    grid [ttk::label .dial.num -justify left -font FixedFontP \
          -text "Dial Number:"] -columnspan 2 -padx 12 -column 0
    ttk::entry .dial.number -width $nmbrWidth -font FixedFontP
    grid .dial.number -sticky ew -columnspan 2 -padx 8
    focus .dial.number
  }
  set row [expr $row + 1]
  grid [ttk::frame .dial.fr] -pady 10 -columnspan 2
  incr row
  grid [ttk::label .dial.fr.lab1 -font {FixedFontM} -text "Status:"] -padx 3
  incr row
  grid [ttk::label .dial.fr.lab2 -font {FixedFontM} \
        -textvariable ClientJobResult] -column 0 -padx 3
  incr row
  grid [ttk::button .dial.cancel -text "Cancel" \
        -command {destroy .dial}] -row $row -pady 12
  grid [ttk::button .dial.call -text "Call" \
        -command {DoIt "" "DIAL" "$DialPrefix[logOne]" "$nameREQ" $wantdial}] \
        -row $row -column 1
  grid [ttk::button .dial.abort -text "ABORT" \
      -command {DoIt "" "DIAL_ABORT" "$DialPrefix[logOne]" "$nameREQ" $wantdial} \
      -state disabled] \
      -pady 10 -row $row -column 1
  grid [ttk::button .dial.close -text "Close" -command {
        Disable .menubar
        destroy .dial} \
        -state disabled] -columnspan 2 -row $row -pady 10
  grid remove .dial.close .dial.abort
  set ClientJobResult "Waiting for user action..."
  modal {.dial}
}

proc doAliasType {} {
    global nameREQ nmbrREQ lineREQ AliasText SelAliasType
    global action_ replace_ alias_action line_action

    switch $SelAliasType {
        NAMEDEP {
            set action_ $alias_action
            set replace_ $nameREQ
            set AliasText "Replace NAME: $nameREQ\nif      NMBR: $nmbrREQ\nwith ALIAS entered below"
            if {$action_ eq "modify" } {
                append AliasText ",\n or clear it to remove it"
                incr row
            }
        }
        NMBRDEP {
            set action_ $alias_action
            set replace_ $nmbrREQ
            set AliasText "Replace NMBR: $nmbrREQ\nif      NAME: $nameREQ\nwith ALIAS entered below"
            if {$action_ eq "modify" } {
                append AliasText ",\n or clear it to remove it"
                incr row
            }
        }
        NAMEONLY {
            set action_ $alias_action
            set replace_ $nameREQ
            if {$action_ eq "modify" } {
                set AliasText "Replace NAME alias: $nameREQ\nwith ALIAS entered below"
                append AliasText ",\n or clear it to remove it"
                incr row
            } else {
                set AliasText "Replace NAME: $nameREQ\nwith ALIAS entered below"
            }
        }
        NMBRONLY {
            set action_ $alias_action
            set replace_ $nmbrREQ
            if {$action_ eq "modify" } {
                set AliasText "Replace NMBR alias: $nmbrREQ\nwith ALIAS entered below"
                append AliasText ",\n or clear it to remove it"
                incr row
            } else {
                set AliasText "Replace NMBR: $nmbrREQ\nwith ALIAS entered below"
            }
        }
        NMBRNAME {
            set action_ $alias_action
            set replace_ $nmbrREQ
            if {$action_ eq "modify" } {
                set AliasText "Replace NMBRNAME alias: $nameREQ\nand         NMBR: $nmbrREQ\nwith ALIAS entered below"
                append AliasText ",\n or clear it to remove it"
                incr row
            } else {
                set AliasText "Replace NAME: $nameREQ\nand     NMBR: $nmbrREQ\nwith ALIAS entered below"
            }
        }
        LINEONLY {
            set action_ $line_action
            set replace_ $lineREQ
            if {$action_ eq "modify" } {
                set AliasText "Replace LINE alias: $lineREQ\nwith ALIAS entered below"
                append AliasText ",\n or clear it to remove it"
                incr row
            } else {
                set AliasText "Replace LINE: $lineREQ\nwith ALIAS entered below"
            }
        }
    }
    append AliasText ".\n"
    .confirm.lab configure -text $AliasText
}

proc doList {list action which} {
    global entry_ action_ list_ ClientJobResult replace_ comment_ from_
    global aliasList aliasTypes CIDaliasType LineAliasType SelAliasType
    global nameREQ nmbrREQ lineREQ AliasText alias_action line_action
    global nameWidth bckColor fgColor bckSelColor fgSelColor

    toplevel .confirm -background $bckColor
    wm title .confirm "Confirmation"
    wm resizable .confirm 0 0
    set action_ $action
    set list_ $list
    set comment_ ""
    set from_ $nameREQ

    if {$list eq "black" || $list eq "white"} {
        set entry [list "$nmbrREQ" "$nameREQ"]
        set entry_ [lindex $entry 0]
        if {[lindex $entry 1] eq "NO NAME" || $nameREQ eq $nmbrREQ} {
            set entry [lreplace $entry 1 1]
        }

        if {$action eq "add"} {
            set _entry [join $entry "\" or \""]
        } elseif {$which eq "name"} {
            set entry_ [set _entry [lindex $entry 1]]
            set entry ""
        } else {
            set entry_ [set _entry [lindex $entry 0]]
            set entry ""
        }
        set _action [string toupper $action 0 0]
        set prep [expr {$action} eq "{add}" ? "{to}" : "{from}"]
        grid [ttk::label .confirm.lab -font FixedFontP -text "$_action \"$_entry\"\n$prep the server's ${list}list"] \
                -columnspan 2 -padx 12 -pady 10
        if {[llength $entry] == 2} {
            grid [ttk::radiobutton .confirm.rb1 -text [lindex $entry 0] \
                  -variable entry_ \
                  -value [lindex $entry 0]] -pady 5 -columnspan 2
            grid [ttk::radiobutton .confirm.rb2 -text [lindex $entry 1] \
                  -variable entry_ \
                  -value [lindex $entry 1]] -pady 5 -columnspan 2
            set row 3
        } else {
            set row 1
        }
        if {$action eq "add"} {
            grid [ttk::label .confirm.lab1 -font FixedFontP -text "Comment:"] -padx 12 -sticky w
            ttk::entry .confirm.entry -width $nameWidth -font FixedFontP
            grid .confirm.entry -sticky ew -columnspan 2 -padx 8
            focus .confirm.entry
            set row [expr $row + 2]
        }
    } else {
        grid [ttk::label .confirm.list -justify left -font FixedFontP -text "NAME: $nameREQ\nNMBR: $nmbrREQ\nLINE: $lineREQ\n\nChoose the alias type:"] -columnspan 2 -padx 12 -pady 10
        grid [listbox .confirm.lb -font FixedFontP -listvariable aliasList \
            -selectmode browse -height 0 -width 0 \
            -background $bckColor -foreground $fgColor \
            -selectbackground $bckSelColor -selectforeground $fgSelColor] \
            -columnspan 2
        if {$CIDaliasType eq "NOALIAS"} {
            set aliasList $aliasTypes
            set alias_action "add"
            set replace_ ""
        } else {
            set aliasList "$CIDaliasType LINEONLY"
            set alias_action "modify"
        }
        if {$LineAliasType eq "NOALIAS"} {
            set line_action "add"
            set replace_ $lineREQ
        } else {
            set line_action "modify"
            set replace_ $lineREQ
        }
        .confirm.lb selection set 0
        set SelAliasType [.confirm.lb get [.confirm.lb curselection]]
        if {$SelAliasType == "LINEONLY"} {set from_ $lineREQ}
        set AliasText ""
        focus .confirm.lb
        grid [ttk::label .confirm.lab -justify left -font FixedFontP -text $AliasText] \
         -columnspan 2 -padx 12 -pady 10
        ttk::entry .confirm.entry -exportselection 0 -font FixedFontP
        set aliaswidth [lindex $::aliasWidths \
                       [lsearch $aliasTypes $SelAliasType]]
        .confirm.entry configure -width $aliaswidth
        grid .confirm.entry -columnspan 2 -padx 12 -pady 10
        set row 4
        doAliasType
        append AliasText ".\n"
        if {$action_ eq "modify"} {
            .confirm.entry insert 0 "$replace_"
        }
    }

    bind all <<ListboxSelect>> {
        global SelAliasType

        if {[.confirm.lb curselection] eq ""} {break}
        set SelAliasType [.confirm.lb get [.confirm.lb curselection]]
        .confirm.entry configure -width [lindex $::aliasWidths \
            [lsearch $::aliasTypes $SelAliasType]]
        focus .confirm.entry
        doAliasType
        append AliasText ".\n"
        .confirm.entry delete 0 end
        if {$action_ eq "modify"} {
            .confirm.entry insert end "$replace_"
        }
    }
    grid [ttk::frame .confirm.fr] -pady 10 -columnspan 2 -row $row
    incr row
    grid [ttk::label .confirm.fr.lab1 -font FixedFontP -text "Status:"] -padx 3
    grid [ttk::label .confirm.fr.lab2 -font FixedFontP -textvariable ClientJobResult] -column 0 -row 1 -padx 3
    grid [ttk::button .confirm.cancel -text "Cancel" -command {destroy .confirm}] -pady 10

    if {$list eq "alias"} {
        grid [ttk::button .confirm.ok -text "Apply" -command {
           global ClientJobResult
           set ClientJobResult ""
           set replace_ [.confirm.entry get]
           set replace_ [string trim $replace_]
           if {$replace_ eq ""} {
              set ClientJobResult "You must enter something\nwhen adding a new alias."
              continue
           }
           set aliaswidth [lindex $::aliasWidths \
                          [lsearch $::aliasTypes $::SelAliasType]]
           .confirm.entry configure -width $aliaswidth
           if {[string length $replace_] > $aliaswidth} {
              .confirm.entry delete $::nameWidth end
              set replace_ [.confirm.entry get]
              set replace_ [string trim $replace_]
              set ClientJobResult "Alias length truncated to fit\nwindow:  edit Cancel or Apply"
              continue
           }
           DoIt $action_ $list_ $nmbrREQ&&$replace_ "$SelAliasType&&$from_" 0}] \
          -pady 10 -row $row -column 1
    } else {
        grid [ttk::button .confirm.ok -text "Apply" -command {
                if {$action_ eq "add"} {
                    set comment_ [.confirm.entry get]; \
                    set comment_ [string trim $comment_]; \
                }; \
                DoIt $action_ $list_ $entry_ $comment_ 0}] \
                -pady 10 -row $row -column 1
    }

    incr row
    grid [ttk::button .confirm.close -text "Close"  -command {
            Disable .menubar
            destroy .confirm} \
             -state disabled ] -columnspan 2 -row $row -pady 10
    grid remove .confirm.close
    set ClientJobResult "Waiting for user action..."
    modal .confirm
}

proc DoIt {action list entry extra wantdial} {
    global Socket  ClientJobResult Try Dialed DialLineID

    set Dialed $wantdial
    if {$Try} {
        set ClientJobResult "Server not connected ..."
        logMsg $::LEVEL1 "Server not connected for a REQ:"
        return
    }
    if {$Dialed > 0} {
        if {$Dialed == 2} {set entry [regsub -all {[^\d]} [.dial.number get] ""]}
        if {$list == "DIAL_ABORT"} {set Dialed 2}
        grid forget .dial.cancel .dial.call
        grid configure .dial.close -columnspan 1
        grid .dial.abort .dial.close
        puts $Socket "REQ: $list $entry&&$extra&&$DialLineID"
        flush $Socket
        logMsg $::LEVEL1 "REQ: $list $entry&&$extra&&$DialLineID"
    } else {
        set ClientJobResult "Working ..."
        grid forget .confirm.cancel .confirm.ok
        grid .confirm.close
        puts $Socket "REQ: $list $action \"$entry\" \"$extra\""
        flush $Socket
        logMsg $::LEVEL1 "REQ: $list $action \"$entry\" \"$extra\""
    }
}

proc doClipboardPopup {title text} {

  global ClipboardPopupCount ClipboardPopupButton ClipboardPopupTime
  global historyTextWidth bckColor

  toplevel .cbp -background $bckColor
  wm withdraw .cbp

  wm title .cbp $title
  wm resizable .cbp 0 0

  set dismissBegin [clock clicks -milliseconds]
  set ClipboardPopupCount $ClipboardPopupTime

  if {$ClipboardPopupTime > 0} {after 1000 {ClipboardPopupLoop}}
  set wrapwidth [expr $historyTextWidth - 30]
  if {[string length $text] > $wrapwidth} {
    logMsg $::LEVEL2 "Wrapping text of length [string length $text] to width $wrapwidth"
    set text [wrapParagraph $wrapwidth $text]
  }

  updateClipboardPopupButton

  set row 3
  grid [ttk::label .cbp.icon -image ::tk::icons::information ] \
       -column 1 -padx 12 -pady 10 -row $row
  grid [ttk::label .cbp.text -font FixedFontP  -text $text ] \
       -column 2 -padx 12 -pady 10 -row $row

  incr row
  incr row

  grid [ttk::button .cbp.ok -textvariable ClipboardPopupButton -command {
        destroy .cbp}] \
       -column 2 -pady 10 -pady 10 -row $row

    if {$::tcl_platform(platform) != "unix"} {
      if {[catch {tk::PlaceWindow .cbp widget .} msg]} { logMsg $::LEVEL1 $msg}
    }

  wm deiconify .cbp
  modal {.cbp}

}

proc doClipboard {flag passedData} {

    # flag = 1 = copy phone number as shown
    #        2 = copy phone number digits only
    #        3 = copy entire line
    #        4 = copy name as shown

    if {$flag != 3} {set passedData [string trim $passedData]}

    clipboard clear
    switch $flag {
        1 -
        4 {
            clipboard append $passedData
        }
        2 {
            regsub -all -line {[^\d]} $passedData {} passedData
            clipboard append $passedData
        }
        3 {
            set templine ""
            for {set x 1} {$x < [ llength $passedData]} {incr x 3} {
                set tempfield [ lindex $passedData $x ]
                set templine "$templine$tempfield"
            }
            set passedData [string trim $templine]
            regsub -all -line {  +} $passedData { } passedData
            clipboard append $passedData
        }
    }
    logMsg $::LEVEL2 "Copied to clipboard: $passedData"
    doClipboardPopup "Selected History Line" "Copied to clipboard: $passedData"
}

proc updateClipboardPopupButton {} {
    global ClipboardPopupCount ClipboardPopupTime ClipboardPopupButton

    if {$ClipboardPopupTime == 0} {
       set ClipboardPopupButton "OK"
    } else {
      set ClipboardPopupButton "Dismiss or wait $ClipboardPopupCount second"
      if {$ClipboardPopupCount != 1} {append ClipboardPopupButton "s" }
    }

}

proc ClipboardPopupLoop {} {
     global ClipboardPopupCount ClipboardPopupButton
     incr ClipboardPopupCount -1
     updateClipboardPopupButton

     if {[winfo exists .cbp]} {
        if {$ClipboardPopupCount <= 0 } {
           .cbp.ok invoke
        } else {
          after 1000 {ClipboardPopupLoop}
        }
     }

}

proc doSelectedTypes {} {
    global labBLK labCID labHUP labMSG labMWI labNOT labOUT labPID labPUT labRID labWID
    global SelectedTypes oldSelectedTypes TypeGroups oldTypeGroups
    global t_array msgToUser bckColor

    # convert lists to arrays
    array set t_array $SelectedTypes

    set maxwidth [expr [string length $labRID] + 2]

    set msgToUser ""

    toplevel .ctypes -background $bckColor
    wm title .ctypes "Select Call and Message Types to View"
    wm resizable .ctypes 0 0

    set row 3

    grid [ttk::label .ctypes.calls -font FixedFontP \
          -text "Call Types"] \
         -columnspan 2 -padx 12 -pady 10 -row $row
    incr row
    incr row

    grid [ttk::frame .ctypes.fr1] -pady 10 -columnspan 1
    grid [ttk::checkbutton .ctypes.fr1.blk \
              -text                $labBLK \
              -variable        t_array(BLK) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr1.cid \
              -text                $labCID \
              -variable        t_array(CID) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr1.hup \
              -text                $labHUP \
              -variable        t_array(HUP) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr1.mwi \
              -text                $labMWI \
              -variable        t_array(MWI) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr1.out \
              -text                $labOUT \
              -variable        t_array(OUT) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr1.pid \
              -text                $labPID \
              -variable        t_array(PID) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr1.put \
              -text                $labPUT \
              -variable        t_array(PUT) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr1.rid \
              -text                $labRID \
              -variable        t_array(RID) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr1.wid \
              -text                $labWID \
              -variable        t_array(WID) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row
    incr row

    grid [ttk::label .ctypes.msgs -font FixedFontP \
          -text "Message Types"] \
         -columnspan 2 -padx 12 -pady 10 -row $row
    incr row
    incr row

    grid [ttk::frame .ctypes.fr2] -pady 10 -columnspan 1
    grid [ttk::checkbutton .ctypes.fr2.msg \
              -text                $labMSG \
              -variable        t_array(MSG) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row

    grid [ttk::checkbutton .ctypes.fr2.not \
              -text                $labNOT \
              -variable        t_array(NOT) -width $maxwidth] -sticky w -pady 5 -column 1 -row $row
    incr row
    incr row
    incr row
    incr row

    grid [ttk::label .ctypes.msgtouser -font FixedFontP \
          -textvariable msgToUser] \
         -columnspan 2 -padx 12 -pady 10 -row $row

    grid [ttk::frame .ctypes.fr3]  -column 0 -pady 8
    grid [ttk::button .ctypes.fr3.selall -text "Select All" \
          -command {array set t_array {BLK 1 CID 1 HUP 1 MWI 1 OUT 1 \
                          PID 1 PUT 1 RID 1 WID 1 MSG 1 NOT 1}
               }] -padx 12 -pady 10 -row $row

    grid [ttk::button .ctypes.fr3.clrall -text "Clear All" -command {
               array set t_array {BLK 0 CID 0 HUP 0 MWI 0 OUT 0 PID 0 PUT 0 RID 0 WID 0 \
                                  MSG 0 NOT 0}
               }] -column 1 -padx 12 -pady 10 -row $row

    grid [ttk::button .ctypes.fr3.cancel -text "Cancel" -command {
               set TypeGroups $oldTypeGroups
               destroy .ctypes}] \
          -column 2 -padx 12 -pady 10 -row $row

    grid [ttk::button .ctypes.fr3.apply -text "Apply" -command {
               global t_array msgToUser
               set sum 0
               foreach {name value} [array get t_array] {set sum [expr $sum + $value]}
               if {!$sum} {
                  set msgToUser "You must select at least one type"
                  logMsg $::LEVEL1 $msgToUser
                  continue
               }
               # then convert array to list
               set SelectedTypes [lsort -stride 2 [array get t_array]]
               destroy .ctypes
               #logTypeGroups causes call log to be re-read
               logTypeGroups}] \
          -column 3 -padx 12 -pady 10 -row $row

    modal {.ctypes}
}

proc doLineIDs {} {
  global DiscoveredLineIDs LineIDGroups oldLineIDGroups
  global SelectedLineIDs oldSelectedLineIDs
  global lineREQ
  global SelLINE msgToUser SelHistoryLINE SelHistoryLINEIndex
  global bckColor fgColor bckSelColor fgSelColor

  set msgToUser ""
  set SelLINE   [list]

  toplevel .clines -background $bckColor
  wm title .clines "Select Line Identifiers to View"
  wm resizable .clines 0 0

  set oldSelectedLineIDs $SelectedLineIDs

  set row 3

  set viewselected $SelectedLineIDs
  set SelectedLineIDs ""
  set SelectedLineIDsIndex ""

  foreach vs $viewselected {
    set index [lsearch -exact "$DiscoveredLineIDs" $vs]
    if {$index != -1} {
      lappend SelectedLineIDs $vs
      lappend SelectedLineIDsIndex $index
    }
  }

  if {$SelectedLineIDsIndex == ""} {set SelectedLineIDsIndex -1}

  # see if user selected a line in call history window
  set SelHistoryLINE ""
  set SelHistoryLINEIndex 0
  if {[info exists lineREQ]} {
    if {$lineREQ != "" } {
      set SelHistoryLINEIndex [lsearch -exact $DiscoveredLineIDs $lineREQ]
      if {$SelHistoryLINEIndex == -1} {
        set SelHistoryLINEIndex 0
      } else {set SelHistoryLINE $lineREQ}
    }
  }

  if {[llength $DiscoveredLineIDs] > 0} {
    grid [listbox .clines.lb -font FixedFontP -height 4 -width 0 \
          -listvariable DiscoveredLineIDs -selectmode extended \
          -yscrollcommand {.clines.ys set} \
          -background $bckColor -foreground $fgColor \
          -selectbackground $bckSelColor -selectforeground $fgSelColor] \
          -columnspan 2
    grid [ttk::scrollbar .clines.ys -command {.clines.lb yview}] \
          -column 1 -row 0 -sticky nse -pady 1 -padx 1
    if {[.clines.lb index end] <= [lindex [.clines.lb configure -height] 4]} {
      grid remove .clines.ys
    } else {
      grid .clines.ys
    }
    if {$SelectedLineIDsIndex != -1} {
      foreach x $SelectedLineIDsIndex {
        .clines.lb selection set $x
      }
      set SelLINE {}
      foreach x [.clines.lb curselection] {
        lappend SelLINE [.clines.lb get $x]
      }
    }

    bind all <<ListboxSelect>> {
      if {[info commands .clines.lb] ne ""} {
        set SelLINE {}
        foreach x [.clines.lb curselection] {
          lappend SelLINE [.clines.lb get $x]
        }
        if {[lindex [.clines.lb yview] 0] + \
            [lindex [.clines.lb yview] 1] == 1.0} {
          grid remove .clines.ys
        } else {
          grid .clines.ys
        }
      }
    }
  }
  set row [expr [llength $DiscoveredLineIDs] + 2]

  if {[llength $DiscoveredLineIDs] > 1} {
    grid [ttk::label .clines.help -justify left -font FixedFontP \
          -text "Multiple selections are allowed."] \
          -columnspan 2 -padx 12 -pady 10 -row $row
    incr row
  }

  if {$SelHistoryLINE != ""} {
    incr row
    incr row
    grid [ttk::label .clines.call -justify left -font FixedFontP \
          -text "Last selected call in history window:"] \
          -columnspan 2 -padx 12 -pady 10 -row $row
    incr row
    grid [ttk::button .clines.history -text "LineID: $SelHistoryLINE" -command {
          global SelHistoryLINE SelHistoryLINEIndex SelLINE
          .clines.lb selection clear 0 end
          .clines.lb selection set $SelHistoryLINEIndex
          .clines.lb curselection
          set SelLINE $SelHistoryLINE
          logMsg $::LEVEL4 "Selected index $SelHistoryLINEIndex for SelHistoryLINE=$SelHistoryLINE"
          }] \
          -columnspan 2 -pady 10 -row $row
    incr row
    incr row
  }

  grid [ttk::label .clines.msgtouser -font FixedFontP \
        -textvariable msgToUser] \
        -columnspan 2 -padx 12 -pady 10 -row $row
  incr row
  incr row

  grid [ttk::button .clines.cancel -text "Cancel" -command {
        global DiscoveredLineIDs LineIDGroups oldLineIDGroups
        set LineIDGroups $oldLineIDGroups
        logMsg $::LEVEL4 "Cancel: DiscoveredLineIDs contains $DiscoveredLineIDs"
        destroy .clines}] \
        -padx 12 -row $row

  grid [ttk::button .clines.apply -text "Apply" -command {
        global DiscoveredLineIDs LineIDGroups oldLineIDGroups
        global SelectedLineIDs oldSelectedLineIDs
        global SelLINE msgToUser
        set msgToUser "You must select at least one lineid"
        if {$SelLINE == ""} {
          logMsg $::LEVEL1 $msgToUser
          continue
        }
        set msgToUser ""
        set SelectedLineIDs $SelLINE
        destroy .clines
        #logViewLineIDs causes call log to be re-read
        logViewLineIDs}] \
        -column 1 -padx 12 -row $row

  modal {.clines}

}

proc helpItem {title passedMesg1 passedMesg2} {
    global Logo command Website
    global mesg1 mesg2 bckColor

    set mesg1 [encoding convertfrom utf-8 $passedMesg1]
    set mesg2 [encoding convertfrom utf-8 $passedMesg2]

    toplevel .hlp -background $bckColor
    wm title .hlp $title
    wm resizable .hlp 0 0

    if [file exists $Logo] {
        image create photo ncid -file $Logo
        grid [ttk::label .hlp.img -image ncid] -columnspan 2 -padx 12 -pady 10
    }

    if {$title == "About"} {set jt "center"} else {set jt "left"}

    if {$mesg1 == ""} {
      grid [ttk::label .hlp.s1]
      grid [hyperlink .hlp.um -command [list eval exec $command "http://ncid.sourceforge.net/doc/NCID-UserManual.html" &] -text "User Manual"] -columnspan 2
      grid [hyperlink .hlp.mp -command [list eval exec $command "http://ncid.sourceforge.net/man/man.html" &] -text "Manual Pages"] -columnspan 2 -padx 42
      grid [ttk::label .hlp.s2]
    } else {
      grid [ttk::label .hlp.mesg1 -font FixedFontP -justify $jt -text $mesg1 ] -columnspan 2 -padx 12
      if { $title == "About" } {
      grid [hyperlink .hlp.web -command [list eval exec $command $Website &] -text "NCID website"] -columnspan 2
      grid [ttk::label .hlp.s3]
      }
    }

    if { $mesg2 != "" } {
      grid [ttk::label .hlp.mesg2 -font FixedFontP -justify left -text $mesg2 ] -columnspan 2 -padx 12
    }

    if { $title == "About" } {
      grid [ttk::button .hlp.clip -text "Copy to Clipboard" -command {
              clipboard clear
              clipboard append -- "$mesg1 $mesg2"
              doClipboardPopup "About" "Copied to clipboard: About window text"
            }] \
            -columnspan 2 -pady 10
    }

    grid [ttk::button .hlp.ok -text "OK" -command {
          Disable .menubar
          destroy .hlp}] \
          -columnspan 2 -pady 10

    if {$::tcl_platform(platform) != "unix"} {
      if {[catch {tk::PlaceWindow .hlp widget .} msg]} { logMsg $::LEVEL1 $msg}
    }

    modal {.hlp}
}

proc serverOPT {} {
    global Logo ServerOptions

    set displayDesc "\nOptions sent to clients\n to indicate enabled:"
    set displayOPT "$ServerOptions"
    helpItem "Server Options" $displayDesc $displayOPT
}

proc clearLog {} {
    global display_line_num Begin DiscoveredLineIDs SelHistoryLINE

    set DiscoveredLineIDs [list]
    set SelHistoryLINE ""
    set display_line_num 0
    .vh configure -state normal
    .vh delete 1.0 end
    .vh yview moveto 0.0
    .vh configure -state disabled
}

proc saveSize {flag} {
    global Txt

    set save $Txt
    set Txt ""
    update

    set geometry [wm geometry .]
    set Txt $save
    if {$flag == 0} {
        regexp {(\d+x\d+)\+} $geometry -> geometry
    }
    write_rc_file "geometry\\s+\\S+\\s+\[0-9x\]+" "wm geometry . $geometry"

}

proc write_rc_file {regexpr command} {
  global rcfile

  if [file exists $rcfile] {
    if [file isdirectory $rcfile] {
      logMsg $::LEVEL1 "Unable to save data to $rcfile because it is a directory"
      return
    }
    set id [open $rcfile]
    set data [read $id]
    close $id
    set lines [lrange [split $data "\n"] 0 end-1]
    if {$regexpr == ""} {
      # command from list
      set lines [lsearch -all -inline -not -regexp $lines "$command"]
    } else {
      set index 0
      foreach line $lines {
        if [regexp $regexpr $line] {
          break
        }
        incr index
      }
      if {$index >= [llength $lines]} {
        lappend lines "$command"
      } else {
        lset lines $index "$command"
      }
    }
      set data [join $lines "\n"]
      set id [open $rcfile w]
      puts $id $data
  } else {
    set id [open $rcfile w]
    puts $id $command
  }
  close $id
}

# Change Fonts
proc changeFont {} {
    global fontList bckColor
    global spinvalH spinvalM spinvalP
    global boldH boldM boldP

    toplevel .f -background $bckColor
    wm title .f "Change Fixed Font"
    wm resizable .f 0 0

    eval [concat {font create SelectionFontH} [font configure FixedFontH]]
    eval [concat {font create SelectionFontM} [font configure FixedFontM]]
    eval [concat {font create SelectionFontP} [font configure FixedFontP]]

    set spinvalH [font configure FixedFontH -size]
    set boldH    [font configure FixedFontH -weight]

    set spinvalM [font configure FixedFontM -size]
    set boldM    [font configure FixedFontM -weight]

    set spinvalP [font configure FixedFontP -size]
    set boldP    [font configure FixedFontP -weight]

    set currentFont [font configure FixedFontH -family]

    label .f.ln -font FixedFontM -text "Font Name"
    grid [ttk::labelframe .f.fn -labelwidget .f.ln -labelanchor "n"] -pady 8 -padx 4 -sticky "nsew"
    grid [ttk::combobox .f.fn.cb -font FixedFontM -values $fontList -textvariable currentFont] -pady 5 -padx [list 60 90]
    grid [ttk::button .f.fn.btn -text "Re-scan"] -column 1 -row 0 -pady 5 -padx 5
    .f.fn.cb set $currentFont

    ttk::label .f.hw -font FixedFontH -text "History Window Font"
    grid [ttk::labelframe .f.fh -labelwidget .f.hw -labelanchor "n"] -column 0 -pady 8 -padx 4 -sticky "nsew"
    grid [ttk::checkbutton .f.fh.cb -text "Bold" -variable boldH \
          -onvalue "bold" \
          -offvalue "normal" \
          -command {font configure SelectionFontH -weight $boldH}] \
          -pady 5 -padx 60
    grid [ttk::label .f.fh.label -font FixedFontH -text "Size: "] -column 1 -row 0 -pady 5 -padx 40
    grid [ttk::spinbox .f.fh.size -from 8 -to 36 -width 3 -font FixedFontH -textvariable spinvalH \
          -state readonly -command {font configure SelectionFontH -size $spinvalH}] \
          -column 2 -row 0 -pady 5 -padx 5
    grid [ttk::label .f.hw2 -text "Sample text 0123456789" -font SelectionFontH] -column 1 -row 1 -pady [list 35 5] -padx 25 -sticky "w"

    ttk::label .f.mml -font FixedFontM -text "Message, Menu and Label Font"
    grid [ttk::labelframe .f.fm -labelwidget .f.mml -labelanchor "n"] -column 0 -pady 8 -padx 4 -sticky "nsew"
    grid [ttk::checkbutton .f.fm.cb -text "Bold" -variable boldM \
          -onvalue "bold" \
          -offvalue "normal" \
          -command {font configure SelectionFontM -weight $boldM}] \
          -pady 5 -padx 60
    grid [ttk::label .f.fm.label -font FixedFontM -text "Size: "] -column 1 -row 0 -pady 5 -padx 40
    grid [ttk::spinbox .f.fm.size -from 8 -to 36 -width 3 \
          -font FixedFontP -textvariable spinvalM \
          -state readonly \
          -command {font configure SelectionFontM -size $spinvalM}] \
          -column 2 -row 0 -pady 5 -padx 5
    grid [ttk::label .f.mml2 -text "Sample text 0123456789" -font SelectionFontM] -column 1 -row 2 -pady [list 35 5] -padx 25 -sticky "w"

    ttk::label .f.pw -font FixedFontP -text "Popup Window Font"
    grid [ttk::labelframe .f.fp -labelwidget .f.pw -labelanchor "n"] -column 0  -pady 8 -padx 4 -sticky "nsew"
    grid [ttk::checkbutton .f.fp.cb -text "Bold" -variable boldP \
          -onvalue "bold" \
          -offvalue "normal" \
          -command {font configure SelectionFontP -weight $boldP}] \
          -pady 5 -padx 60
    grid [ttk::label .f.fp.label -font FixedFontP -text "Size: "] -column 1 -row 0 -pady 5 -padx 40
    grid [ttk::spinbox .f.fp.size -from 8 -to 36 -width 3 \
          -font FixedFontP -textvariable spinvalP \
          -state readonly \
          -command {font configure SelectionFontP -size $spinvalP}] \
          -column 2 -row 0 -pady 5 -padx 5
    grid [ttk::label .f.pw2 -text "Sample text 0123456789" -font SelectionFontP] -column 1 -row 3 -pady [list 35 5] -padx 25 -sticky "w"

    grid [ttk::frame .f.f]  -column 0 -sticky "ew" -pady 8
    grid [ttk::button .f.f.cancel -text "Cancel"] -padx 12 -pady 6
    grid [ttk::button .f.f.apply -text "Apply"] -column 1 -row 0 -padx 12
    grid [ttk::button .f.f.ok -text "OK"] -column 2 -row 0 -padx 12

    # change font family
    bind all <<ComboboxSelected>> {
        font configure SelectionFontH -family "$currentFont"
        font configure SelectionFontM -family "$currentFont"
        font configure SelectionFontP -family "$currentFont"
    }

    bind TButton <ButtonRelease-1> {+
        switch -regexp %W {
            ".ctypes.*" { break }
            ".clines.*" { break }
            ".confirm.*" { break }
            ".view.*" { break }
            ".dial.*" { break }
            ".dt.*" { break }
            ".hlp.*" { break }
            ".rply.*" { break }
            ".reply.*" { break }
            default {
                set temp [%W cget -text]
                switch $temp {
                    "Cancel" {
                        destroy .f
                        break
                    }
                    "OK" -
                    "Apply" {
                        font configure FixedFontH -family "$currentFont" \
                            -size $spinvalH -weight $boldH
                        font configure FixedFontM -family "$currentFont" \
                            -size $spinvalM -weight $boldM
                        font configure FixedFontP -family "$currentFont" \
                            -size $spinvalP -weight $boldP
                        logFont
                        if {$temp eq "OK"} {
                            destroy .f
                        }
                        break
                    }
                    "Re-scan" {
                        .f.fn.cb configure -values {}
                        unset fontList
                        scanFonts
                        .f.fn.cb configure -values $fontList
                        break
                    }
                }
            }
        }
    }

    modal {.f}

    font delete SelectionFontH
    font delete SelectionFontM
    font delete SelectionFontP
}

proc logFont {} {
    set fonth "[font configure FixedFontH]"
    set fontm "[font configure FixedFontM]"
    set fontp "[font configure FixedFontP]"

    write_rc_file "FixedFontH" "font create FixedFontH $fonth"
    write_rc_file "FixedFontM" "font create FixedFontM $fontm"
    write_rc_file "FixedFontP" "font create FixedFontP $fontp"

    logMsg $::LEVEL1 "history window font (FixedFontH) has been saved after command: $fonth"
    logMsg $::LEVEL1 "message window and display font (FixedFontM) has been saved after command: $fontm"
    logMsg $::LEVEL1 "help text font (FixedFontP) has been saved after command: $fontp"
}

proc logOne {} {
  global Leading1 oldLeading1 nmbrREQ Country

  if {$Leading1 != $oldLeading1} {
    logRCfileOldNewVarChange $::LEVEL2 oldLeading1 Leading1
    set oldLeading1 $Leading1
    write_rc_file "set Leading1" "set Leading1 \"$Leading1\""
  }

  set dialnmbr $nmbrREQ
  if {$Country == "US"} {
    switch $Leading1 {
      "Leave" {
      }
      "Add" {
        if {[regexp {^91?} $dialnmbr] && [string length $dialnmbr] >= 11} {
          regsub {^91?} $dialnmbr {91} dialnmbr
        } else {
          regsub {^1?} $dialnmbr {1} dialnmbr
        }
      }
      "Remove" {
        if {[regexp {^91} $dialnmbr] && [string length $dialnmbr] == 12} {
          regsub {^91} $dialnmbr {9} dialnmbr
        } else {
          regsub {^1} $dialnmbr {} dialnmbr
        }
      }
    }
  }
  return $dialnmbr
}

proc formatDT {} {
    global bckColor

    toplevel .dt -background $bckColor
    wm title .dt "Date and Time Formats"
    wm resizable .dt 0 0

    grid [ttk::label .dt.s1]
    grid [ttk::radiobutton .dt.12 -text "12 hour time" -variable clock -value 12 -command {logClock .vh}] -columnspan 2
    grid [ttk::radiobutton .dt.24 -text "24 hour time" -variable clock -value 24 -command {logClock .vh}] -columnspan 2 -rowspan 2

    grid [ttk::label .dt.s2]
    grid [ttk::radiobutton .dt.mm -text "Date: MM DD YYYY" -variable AltDate -value 0 -command {logDate .vh}] -columnspan 2 -padx 12
    grid [ttk::radiobutton .dt.dd -text "Date: DD MM YYYY" -variable AltDate -value 1 -command {logDate .vh}] -columnspan 2 -padx 12 -rowspan 2

    grid [ttk::label .dt.s3]
    grid [ttk::radiobutton .dt.s -text "Date Separator: /" -variable DateSepar -value "/" -command {logDate .vh}] -columnspan 2 -padx 12
    grid [ttk::radiobutton .dt.d -text "Date Separator: -" -variable DateSepar -value "-" -command {logDate .vh}] -columnspan 2 -padx 12
    grid [ttk::radiobutton .dt.p -text "Date Separator: ." -variable DateSepar -value "." -command {logDate .vh}] -columnspan 2 -padx 12

    grid [ttk::label .dt.s4]
    grid [ttk::button .dt.ok -text "OK" -command {
          Disable .menubar
          destroy .dt}] \
          -columnspan 2 -pady 10

    modal {.dt}
}

proc logDate {widget} {
    global AltDate oldAltDate DateSepar oldDateSepar YearDot clock Socket display_line_num lbl1 lbl2 lbl3 lbl4 lbl5 lbl6 lbl7 lbl8 historyTextWidth

    set dateflag 0
    if {$AltDate != $oldAltDate} {
        logRCfileOldNewVarChange $::LEVEL2 oldAltDate AltDate
        set oldAltDate $AltDate
        set dateflag 1
        write_rc_file "set AltDate" "set AltDate $AltDate"
    } elseif {$DateSepar != $oldDateSepar} {
        logRCfileOldNewVarChange $::LEVEL2 oldDateSepar DateSepar
        set oldDateSepar $DateSepar
        write_rc_file "set DateSepar" "set DateSepar $DateSepar"
    } else { return }

    $widget configure -state normal
    for {set line 0} {1} {incr line} {
        set temp [$widget dump -text "1.0 + $line l" "1.0 + $line l lineend"]
        if {$temp eq ""} {break}
        # do not check for END or RLY in next line because these are ignored in this script
        if {![regexp {^(?:BLK|CID|HUP|MSG|MWI|NOT|OUT|PID|PUT|RID|WID)} [lindex $temp 1]]} {
            continue
        }
        # MSG lines may have no date or time, if the llength is 8
        if {[llength $temp] < 10} {continue}
        set date [lindex $temp 4]
        set start [lindex $temp 5]
        set stop [lindex $temp 8]
        set f1 [string range $date 0 1]
        set f2 [string range $date 3 4]
        set f3 [string range $date 6 9]
        if {$dateflag} {
            set date "$f2$DateSepar$f1$DateSepar$f3"
        } else {
            set date "$f1$DateSepar$f2$DateSepar$f3"
        }
        $widget insert "$stop - 1 c" "$date"
        $widget delete "$start" "$stop - 1 c"
    }

    if {$clock == 24 && $DateSepar == "."} {set lbl $lbl1}
    if {$clock == 24 && $DateSepar == "-"} {set lbl $lbl2}
    if {$clock == 24 && $DateSepar == "/"} {set lbl $lbl3}
    if {$clock == 12 && $DateSepar == "."} {set lbl $lbl4}
    if {$clock == 12 && $DateSepar == "-"} {set lbl $lbl5}
    if {$clock == 12 && $DateSepar == "/"} {set lbl $lbl6}
    if {$clock == 24 && $DateSepar == "." && $YearDot == 1} {set lbl $lbl7}
    if {$clock == 12 && $DateSepar == "." && $YearDot == 1} {set lbl $lbl8}

    if $YearDot {
      if {$Socket > 0} {
          set display_line_num 0
          clearLog
          puts $Socket "REQ: REREAD"
          flush $Socket
      }
      .la configure -text $lbl
      logMsg $::LEVEL1 "History Text Field Width changed to: $historyTextWidth characters"
      $widget configure -width $historyTextWidth
      set geometry [wm grid .]
      wm minsize . [lindex $geometry 0] [lindex $geometry 1]
      update
      $widget configure -state disabled
    }
}

proc logTypeGroups {} {
  global TypeGroups oldTypeGroups SelectedTypes oldSelectedTypes
  global Socket display_line_num

  set changeflag 0

  if {$TypeGroups != $oldTypeGroups} {
    logRCfileOldNewVarChange $::LEVEL2 oldTypeGroups TypeGroups
    set oldTypeGroups $TypeGroups
    set changeflag 1
    updateViewDisplay
    write_rc_file "set TypeGroups" "set TypeGroups $TypeGroups"
  }
  if {$SelectedTypes != $oldSelectedTypes} {
    logRCfileOldNewVarChange $::LEVEL2 oldSelectedTypes SelectedTypes
    set oldSelectedTypes $SelectedTypes
    set changeflag 1
    updateViewDisplay
    write_rc_file "set SelectedTypes" "set SelectedTypes \{$SelectedTypes\}"
  }

  if {$changeflag && $Socket > 0} {
     set display_line_num 0
     clearLog
     puts $Socket "REQ: REREAD"
     flush $Socket
  }
}

proc logViewLineIDs {} {
  global DiscoveredLineIDs LineIDGroups oldLineIDGroups
  global SelectedLineIDs oldSelectedLineIDs
  global Socket display_line_num

  set changeflag 0

  if {$LineIDGroups != $oldLineIDGroups} {
    logRCfileOldNewVarChange $::LEVEL2 oldLineIDGroups LineIDGroups
    set oldLineIDGroups $LineIDGroups
    set changeflag 1
    write_rc_file "set LineIDGroups" "set LineIDGroups $LineIDGroups"
    if {$LineIDGroups == 0 && $SelectedLineIDs != $DiscoveredLineIDs} {
      set SelectedLineIDs $DiscoveredLineIDs
    }
    updateViewDisplay
  }

  if {$SelectedLineIDs != $oldSelectedLineIDs} {
    logRCfileOldNewVarChange $::LEVEL2 oldSelectedLineIDs SelectedLineIDs
    set oldSelectedLineIDs $SelectedLineIDs
    set changeflag 1
    updateViewDisplay
    write_rc_file "set SelectedLineIDs" "set SelectedLineIDs \"$SelectedLineIDs\""
  }

  if {$changeflag && $Socket > 0} {
     set display_line_num 0
     clearLog
     puts $Socket "REQ: REREAD"
     flush $Socket
  }

}

proc logHosts {} {
  global Hosts HostIndex Host Port oldHost oldPort SelHistoryLINE
  global ChangeHostFlag SelectedLineIDs LineIDGroups TypeGroups
  global ServerOptions

  logMsg $::LEVEL2 "logHosts:"

  set ChangeHostFlag 0
  lassign [split [lindex $Hosts $HostIndex] ":"] Host Port
  #logMsg $::LEVEL2 "Setting Host=$Host and Port=$Port by extracting HostIndex=$HostIndex from Hosts=$Hosts"
  logMsg $::LEVEL2 "    HostIndex=$HostIndex hosts=$Hosts"
  logMsg $::LEVEL2 "    host=$Host oldHost=$oldHost Port=$Port oldPort=$oldPort"
  if {$oldHost != $Host || $oldPort != $Port} {
      if {$oldHost != $Host} {
        logRCfileOldNewVarChange $::LEVEL2 oldHost Host
        set oldHost $Host
        set ChangeHostFlag 1
        write_rc_file "set Host" "set Host $Host"
      }
      if {$oldPort != $Port} {
        logRCfileOldNewVarChange $::LEVEL2 oldPort Port
        set oldPort $Port
        set ChangeHostFlag 1
        write_rc_file "set Port" "set Port $Port"
      }
  }

  if {$ChangeHostFlag} {
    set LineIDGroups 0
    write_rc_file "set LineIDGroups" "set LineIDGroups 0"
    set TypeGroups 0
    write_rc_file "set TypeGroups" "set TypeGroups 0"
    set SelectedLineIDs ""
    set ServerOptions ""
    clearLog
    Reconnect
  }
}

# when switching between multiple servers, make it obvious in the log file
proc logServerAddress {} {
  global Host Port

  set bannerFill "="
  set bannerText "$bannerFill Server address: $Host:$Port $bannerFill"
  set bannerStars [string repeat $bannerFill [string length $bannerText]]
  logMsg $::LEVEL1 "$bannerStars"
  logMsg $::LEVEL1 "$bannerText"
  logMsg $::LEVEL1 "$bannerStars"
}

proc processLineID {lineid} {
  global DiscoveredLineIDs SelectedLineIDs

  set ret 0

  # determine if DiscoveredLineIDs needs to be updated
  if {$lineid == ""} {set lineid "No-LineID"}
  if {[lsearch -exact $DiscoveredLineIDs "$lineid"] == -1} {
     lappend DiscoveredLineIDs "$lineid"
  }

  # check if lineid is in SelectedLineIDs
  if {$SelectedLineIDs == ""} {
    set ret 1
  } else {
    foreach id_ $SelectedLineIDs {if {$id_ == $lineid} {set ret 1; break}}
  }

  return $ret
}

proc updateViewDisplay {} {
  global TypeGroups SelectedTypes
  global LineIDGroups SelectedLineIDs DiscoveredLineIDs
  global SelectedAllTypes SelectedCalls SelectedMessages SelectedSmartPhone

  set selectview [list]
  .tv delete 1.0 2.end
  switch $TypeGroups {
    0 {
      set selectview $SelectedAllTypes; .tv insert 1.end "View All Types:"
    }
    1 {
      set selectview $SelectedCalls; .tv insert 1.end "View Call Types:"
    }
    2 {
      set selectview $SelectedMessages; .tv insert 1.end "View Message Types:"
    }
    3 {
      set selectview $SelectedSmartPhone;.tv insert 1.end "View Smart Phone Types:"
    }
    4 {
      set selectview $SelectedTypes; .tv insert 1.end "View Selected Types: "
    }
  }
  if {$selectview == ""} {
    .tv insert 1.end "View All Types: "
    set SelectedTypes "$SelectedAllTypes"
  } else {set SelectedTypes "$selectview"}
  foreach {type flag} $SelectedTypes {
    if {$flag == 1} {
      .tv insert 1.end " $type" "blank"
    } else {
      .tv insert 1.end " " "blank"
      .tv insert 1.end "$type" "strike $type"
    }
  }

  if {$SelectedLineIDs == ""} {
    set SelectedLineIDs $DiscoveredLineIDs
  } else {
    set viewselected $SelectedLineIDs
    set SelectedLineIDs ""
    foreach vs $viewselected {
      set index [lsearch -exact "$DiscoveredLineIDs" $vs]
      if {$index != -1} {
        lappend SelectedLineIDs $vs
      }
    }
  }

  .tv insert 1.end "\n"
  if {$LineIDGroups == 0} {
    .tv insert 2.end "View All LineIDs: $SelectedLineIDs"
  } else {
    .tv insert 2.end "View Selected LineIDs:"
    set lineids [list]

    # Setup array for viewing lineids
    foreach lineid $DiscoveredLineIDs {
      lappend lineids $lineid 0
    }

    # determine which lineids are wanted for viewing
    foreach lineid $SelectedLineIDs {
      if {[set pos [lsearch -exact $lineids $lineid]] != -1} {
        set pos1 [expr $pos + 1]
        set lineids "[lreplace $lineids $pos1 $pos1 1]"
      }
    }

    # view each lineid
    foreach {lineid value} $lineids {
      # need to enclose lineid with {} again
      if {[regexp {\s} $lineid]} {set lineid "{$lineid}"}
      # determine when to strikeout a lineid
      if {$value == 1} {
        .tv insert 2.end " $lineid" "blank"
      } else {
        .tv insert 2.end " " "blank"
        .tv insert 2.end "$lineid" "strike $lineid"
      }
    }
  }
  update
  if {[lindex [.tv yview] 0] + [lindex [.tv yview] 1] != 1.0} {
    grid .vsb} else {grid remove .vsb}
}

proc logTheme {} {
  global oldThemeName ThemeName
  global Socket display_line_num

  if {$ThemeName != $oldThemeName} {
    logRCfileOldNewVarChange $::LEVEL2 oldThemeName ThemeName
    set oldThemeName $ThemeName
    write_rc_file "set ThemeName" "set ThemeName \"$ThemeName\""

    setStyles
    if {$Socket > 0} {
      set display_line_num 0
      clearLog
      puts $Socket "REQ: REREAD"
      flush $Socket
    }
  }
}

proc logClock {widget} {
    global  clock oldClock DateSepar YearDot Socket display_line_num lbl1 lbl2 lbl3 lbl4 lbl5 lbl6 lbl7 lbl8 historyTextWidth

    if {$clock == $oldClock} { return }
    #not using logRCfileOldNewVarChange here as next line is more user friendly
    logMsg $::LEVEL2 "rcfile and Time display have been changed from: $oldClock to: $clock hours"
    set oldClock $clock
    write_rc_file "set clock" "set clock $clock"
    $widget configure -state normal
    for {set line 0} {1} {incr line} {
        set temp [$widget dump -text "1.0 + $line l" "1.0 + $line l lineend"]
        if {$temp eq ""} {break}
        # do not check for END or RLY in next line because these are ignored in this script
        if {![regexp {^(?:BLK|CID|HUP|MSG|MWI|NOT|OUT|PID|PUT|RID|WID)} [lindex $temp 1]]} {
            continue
        }
        # MSG lines may have no date or time, if so the llength is 8
        if {[llength $temp] < 10} {continue}
        set time [lindex $temp 7]
        set start [lindex $temp 8]
        set stop [lindex $temp 11]
        if {$clock == 12} {
            set hours [string range $time 0 1]
            set minutes [string range $time 3 4]
            set time [convertTo12 $hours $minutes]
        } else {
            set hours [string range $time 0 1]
            set minutes [string range $time 3 4]
            set AmPm [string range $time 6 7]
            set time [convertTo24 $hours $minutes $AmPm]
        }
        $widget insert "$stop - 1 c" "$time"
        $widget delete "$start" "$stop - 1 c"
    }

    if {$clock == 24 && $DateSepar == "."} {set lbl $lbl1}
    if {$clock == 24 && $DateSepar == "-"} {set lbl $lbl2}
    if {$clock == 24 && $DateSepar == "/"} {set lbl $lbl3}
    if {$clock == 12 && $DateSepar == "."} {set lbl $lbl4}
    if {$clock == 12 && $DateSepar == "-"} {set lbl $lbl5}
    if {$clock == 12 && $DateSepar == "/"} {set lbl $lbl6}
    if {$clock == 24 && $DateSepar == "." && $YearDot == 1} {set lbl $lbl7}
    if {$clock == 12 && $DateSepar == "." && $YearDot == 1} {set lbl $lbl8}

    if {$Socket > 0} {
        set display_line_num 0
        clearLog
        puts $Socket "REQ: REREAD"
        flush $Socket
    }
    .la configure -text $lbl
    logMsg $::LEVEL1 "History Text Field Width changed to: $historyTextWidth characters"
    $widget configure -width $historyTextWidth
    set geometry [wm grid .]
    wm minsize . [lindex $geometry 0] [lindex $geometry 1]
    update
    $widget configure -state disabled
}

proc logAuto {menu} {
    global ExitOn autoSave oldAutoSave m

    if {$autoSave eq $oldAutoSave} { return }
    set oldAutoSave $autoSave
    write_rc_file "set autoSave" "set autoSave \"$autoSave\""
    switch $autoSave {
        "size" {
            set temp "save size only"
            $menu entryconfigure *Size -state disabled
            $menu entryconfigure *Position -state disabled
            $menu entryconfigure Quit -command {do_goodbye; saveSize 0; exit}
            wm protocol . WM_DELETE_WINDOW {do_goodbye; saveSize 0; $ExitOn}
        }
        "both" {
            set temp "save size and position"
            $menu entryconfigure *Size -state disabled
            $menu entryconfigure *Position -state disabled
            $menu entryconfigure Quit -command {do_goodbye; saveSize 1; exit}
            wm protocol . WM_DELETE_WINDOW {saveSize 1; $ExitOn}
        }
        "off" {
            set temp "off"
            $menu entryconfigure *Size -state normal
            $menu entryconfigure *Position -state normal
            $menu entryconfigure Quit -command {do_goodbye; exit}
            wm protocol . WM_DELETE_WINDOW $ExitOn
        }
    }
    logMsg $::LEVEL2 "rcfile and autoSave have been set to $temp"
}

proc logStart {} {
    global      autoStart oldAutoStart dtfile asfile

    if {$autoStart eq $oldAutoStart} { return }
    set oldAutoStart $autoStart
    switch $autoStart {
        "on" {
            file copy -force $dtfile $asfile
        }
        "on+alert" {
            set in  [open $dtfile r]
            set out [open $asfile w]
            while {[gets $in line] != -1} {
                regsub {NCID client} $line {NCID client with ncid-alert} line
                regsub {ID\) client} $line {ID) client with desktop notifications} line
                regsub {Exec=ncid} $line {Exec=ncid --module ncid-alert} line
                puts $out $line
            }
            close $in
            close $out
        }
        "alert" {
            set in  [open $dtfile r]
            set out [open $asfile w]
            while {[gets $in line] != -1} {
                regsub {NCID client} $line {NCID Alert} line
                regsub {NCID \(Network Caller ID\) client} $line {Send NCID call or message desktop notifications} line
                regsub {Exec=ncid} $line {Exec=ncid --no-gui --module ncid-alert} line
                puts $out $line
            }
            close $in
            close $out
        }
        "off" {
            file delete $asfile
        }
    }
    write_rc_file "set autoStart" "set autoStart \"$autoStart\""
    logMsg $::LEVEL2 "rcfile and autoStart have been set to $autoStart"
}

# Handle MSG from GUI
proc handleGUIMSG {} {

  # get MSG and clear text input box
  set line [.im get]
  .im delete 0 end
  # get rid of non-printable characters at start/end of string
  set line [string trim $line]
  # send MSG to server, if $line not empty
  if {[string length $line] > 0} {handleMSG $line}
}

# Handle MSG sent to server
proc handleMSG {msg} {
  global Socket LoginName LineName

  puts $Socket "MSG: $msg ###NAME*$LoginName*LINE*$LineName"
  flush $Socket
}

# Handle verbosity levels
proc logMsg {level msg} {
    global Verbose LogChan

    if {$Verbose >= $level} {
       puts "$msg"
       if {$LogChan != ""} {
           set systemTime [clock seconds]
           puts $LogChan "\[[clock format $systemTime -format "%m/%d %H:%M"]\] $msg"
       }
    }
}

# https://stackoverflow.com/a/34221864
proc logArray {verboseLevel heading a {pattern *}} {
    upvar 1 $a array
    if {![array exists array]} {
        return -code error "\"$a\" isn't an array"
    }
    set maxl 0
    set names [lsort [array names array $pattern]]
    foreach name $names {
        if {[string length $name] > $maxl} {
            set maxl [string length $name]
        }
    }
    set maxl [expr {$maxl + [string length $a] + 2}]
    set indent ""
    if {$heading != ""} {
       logMsg $verboseLevel $heading
       set indent "     "
    }
    foreach name $names {
        set nameString [format %s(%s) $a $name]
        logMsg $verboseLevel [format "%s$%-*s = %s" "$indent" $maxl $nameString $array($name)]
    }
}


# handle a PID file, if it can not be created, ignore it
proc doPID {} {
    global PIDfile

    if {$PIDfile != ""} {
        set activepid ""
        set PIDdir [file dirname $PIDfile]
        if {[file writable $PIDfile]} {
            # get the pid's on the first line of the pidfile
            set chan [open $PIDfile r ]
            gets $chan line
            close $chan
            # save any active pid
            foreach p $line {
                if {[file exists /proc/$p]} {set activepid "$p "}
            }
            # truncate the pidfile
            set chan [open $PIDfile w ]
            if {$activepid == ""} {
                # write current PID into pidfile
                puts $chan [pid]
            } else {
                # write active PID's and current PID into pidfile
                puts $chan "$activepid [pid]"
            }
            close $chan
        } elseif {[file writable $PIDdir]} {
            # create the pidfile
            set chan [open $PIDfile "CREAT WRONLY" 0644]
            puts $chan [pid]
            close $chan

        }
        logMsg $::LEVEL1 "Using pidfile: $PIDfile"
    } else {logMsg $::LEVEL1 "Not using a PID file"}
}

# handle log file, if it can not be created, ignore it
proc doLogOpen {} {
    global LogEnable LogDir LogChan LogStatus LogFile

  set access "CREAT WRONLY TRUNC"
  switch $LogEnable {
    1 {
      # create embed process ID in file name
      set LogFile [file normalize [file join $LogDir "ncid-client-[pid].log"]]
      set LogStatus "Log File:      $LogFile\n               enabled - process ID"
    }
    2 {
      # create/overwrite, do not embed process ID in file name
      set log_file ncid-client.log
      if {[machine platform] == "windows"} {set log_file ncid-windows.log}
      if {[machine platform] == "unix"} {set log_file "ncid-[info hostname].log"}
      if {[machine platform] == "android"} {set log_file ncid-androwish.log}
      if {[machine os] == "Darwin"} {regsub {^ncid} $log_file {ncid-mac} log_file}
      set LogFile [file normalize [file join $LogDir $log_file]]
      set LogStatus "Log File:      $LogFile\n               enabled - overwrite"
    }
    default {
      # this should never happen
      set LogStatus "LogEnable out of range: $LogEnable"
      set LogEnable 0
    }
  }

  if {$LogEnable > 0} {
    if {[catch {set LogChan [open $LogFile $access "0644"]} failmsg]} {
      # logfile open failed
      set LogStatus "$failmsg"
      set LogEnable 0
    } else {
      fconfigure $LogChan -buffering line
    }
  }
  set LogStatus [regsub {/.*/} $LogStatus ""]
}

proc scanFonts {} {
    global fontList currentFont

    set numberFonts 0
    set numberFixed 0
    # find a fixed-width font and use it
    foreach family [font families] {
        incr numberFonts

        # Skip Bauhaus9 on Apple Mac -- triggers wish error:
        # CoreText: Invalid 'kern' Table In CTFont <name: Bauhaus93....
        if {$family == "Bauhaus 93"} {
            logMsg $::LEVEL4 "skipping fixed font: $family"
            continue
        }

        # Skip '.LastResort' on Apple Mac -- garbles all text
        if {$family == ".LastResort"} {
            logMsg $::LEVEL4 "skipping fixed font: $family"
            continue
        }

        # skip Emoji fonts, color ones cause "X Error of failed request"
        if {[regexp {Emoji} $family]} {
            logMsg $::LEVEL4 "skipping fixed font: $family"
            continue
        }

        # Microsoft has duplicate fonts that start with @
        if {[regexp {^@} $family]} {
            logMsg $::LEVEL4 "skipping fixed font: $family"
            continue
        }

        if {[font metrics \"$family\" -fixed]} {
            incr numberFixed
            logMsg $::LEVEL4 "detected fixed font $family"
            lappend fontList $family
        }
    }
    # sort and remove duplicates
    set fontList [lsort -dictionary -unique $fontList]

    set currentFont [lindex $fontList 0]
    logMsg $::LEVEL1 "current font set to: $currentFont"
    logMsg $::LEVEL1 "$numberFixed fixed fonts out of $numberFonts fonts"
    write_rc_file "fontList " "set fontList \"$fontList\""
}

proc modal {window} {
  wm transient $window .

  # Tk Command: tkwait visibility
  # https://www.tcl.tk/man/tcl/TkCmd/tkwait.htm
  #   Waits for a change in the visibility state of a window as indicated by a <VisibilityNotify> event.
  #   This is typically used to wait for a newly-created window to appear on the screen before taking some action.
  # https://stackoverflow.com/questions/8929031/grabbing-a-new-window-in-tcl-tk
  #   This prevents the error from sometimes appearing:
  #     RGError: grab failed: window not viewable
  #
  # Tk Command: wininfo viewable
  # https://www.tcl.tk/man/tcl/TkCmd/winfo.htm
  # http://wiki.tcl.tk/10013
  #   Needed for (Windows, OSX/Aqua) because <VisibilityNotify> events are never delivered.
  #   On a windows platform, tkwait visibility does not return on a visable vindow.
  if {![winfo viewable $window]} { tkwait visibility $window }

  grab $window

  # Resolves the lack of focus on a newly created window
  focus $window

  wm protocol $window WM_DELETE_$window {grab release $window; destroy $window}
  raise $window
  tkwait window $window
}

# Hyperlink Widget: https://wiki.tcl.tk/36776
proc hyperlink { name args } {
  global fgColor

  if {"Underline-Font" ni [font names]} {
    font create Underline-Font
  }
  # font size may have changed
  font configure Underline-Font {*}[font actual FixedFontP] -underline true

  if { [ dict exists $args -command ] } {
    set command [ dict get $args -command ]
    dict unset args -command
  }

  # add -foreground, -font and -cursor, but only if they are missing
  set args [ dict merge [ dict create -foreground $fgColor \
            -font Underline-Font -cursor hand2 ] $args ]

  ttk::label $name {*}$args

  if { [ info exists command ] } {
    bind $name <Button-1> $command
  }

  return $name
}

proc setBrowser {} {
  global command browser

  # open is the OS X equivalent to xdg-open on Linux, start is used on Windows
  set commands {xdg-open open start}
  foreach browser $commands {
    if {$browser eq "start"} {
      set command [list {*}[auto_execok start] {}]
    } else {
      set command [auto_execok $browser]
    }
    if {[string length $command]} {
      break
    }
  }

  if {[string length $command] == 0} {
    return -code error "couldn't find browser"
  }
}

########################################################################
#                      MAIN ROUTINE STARTS HERE                        #
########################################################################

if {$nameWidth == ""
    || ![regexp {^[2345]+[0-9]+$} $nameWidth]
    || $nameWidth > 50} {
    exitMsg 10 "nameWidth should be 20-50 but is \"$nameWidth\""
}

if {$ClipboardPopup == 1} {
  if {$ClipboardPopupTime == "" || ![regexp {^\d$} $ClipboardPopupTime]} {
    exitMsg 10 "ClipboardPopupTime should be 0-9 but is \"$ClipboardPopupTime\""
  }
} elseif {$ClipboardPopup != 0} {
  exitMsg 10 "ClipboardPopup should be 0-1 but is \"$ClipboardPopup\""
}

# AndroWish - packages sdltk and borg are included, no need to install
set sdltk_present [expr {[info commands "sdltk"]} ne {""}]
set borg_present [expr {[info commands "borg"]} ne {""}]

if {$NoGUI} {
    if {$Host == ""} {set Host $DefaultHost}
    if {$Port == ""} {set Port $DefaultPort}
} else {
    switch $::tcl_platform(platform) {
      "unix" {
        set rcfile $UnixRCfile
        set asfile $UnixASfile
      }
      "windows" {
        #set rcfile [file join $::env(AppData) "ncid.dat"]
        set rcfile $WinRCfile
      }
    }
   if {$PortableDir != ""} {set rcfile [file join $PortableDir ".ncid"]}

   processRCfile
   set delayedMsgs "$delayedMsgs\nProcessed RC file: $rcfile"
   set RCfileHost $Host
   set RCfilePort $Port
   set RCfileoldHost $oldHost
   set RCfileoldPort $oldPort
   set RCfileHosts $Hosts
   set RCfileHostIndex $HostIndex
   set RCfileThemeName $ThemeName
}

getArg
set delayedMsgs "$delayedMsgs\nProcessed command line arguments"

checkHosts

if {![regexp {^[0-2]+$} $LogEnable]} {
      set LogStatus "Log enable out of range: $LogEnable"
      set LogEnable 0
}

if {$LogEnable > 0} {
    # Make sure LogDir is created or set LogEnable 0
    if {$PortableDir != ""} {set LogDir [file join $PortableDir "logs"]}
    if {[regexp {[/+\w+]$} $LogDir]} {
        if {![file isdirectory $LogDir]} {
            if {[catch {file mkdir $LogDir} msg]} {
                set LogEnable 0; set LogStatus $msg
            }
        }
        set LogDirLocation "Log Directory: [file normalize $LogDir]"
    } else {set LogEnable 0}
    if {$LogEnable > 0} {doLogOpen}
}

set oldHost $Host
set oldPort $Port
set ArgHost $Host
set ArgPort $Port
set ArgoldHost $oldHost
set ArgoldPort $oldPort
set ArgHosts $Hosts
set ArgHostIndex $HostIndex

if {$NoGUI && $Verbose == 0} {set Verbose 1}

if {$HostnameFlag} {
    regsub {ncid} $VersionIDENT "$hostname/ncid" VersionIDENT
} else {
    set LineName ncid
}

if {$Module != ""} {
    regsub {.*/(.*)} $Module {\1} ModName
    if {$NoGUI} {
        regsub {ncid} $VersionInfo "$ModName" VersionInfo
        regsub {ncid} $VersionIDENT "$ModName" VersionIDENT
    } else {
        regsub {ncid} $VersionInfo "ncid using module $ModName" VersionInfo
        regsub {ncid} $VersionIDENT "ncid using module $ModName" VersionIDENT
    }
}

# LoginName on chromebook and android are system-generated user names
# like uX_aYY where X is a user# like 0, 1, 2 and YY seems to be a
# process id for that user instance. It's currently not possible to
# retrieve the gmail address associated with the login user.
if {[machine platform] == "chromebook" } {
   set LoginName "chromebook user"
} elseif {[machine platform] == "android" } {
   set LoginName "android user"
} else {
   set LoginName $tcl_platform(user)
}

# log command line and any options on separate lines
  set cl "Command line: $::argv0"
  for {set cnt 0} {$cnt < $::argc} {incr cnt} {
      set optarg [lindex $::argv [expr $cnt + 1]]
      set opt [lindex $::argv $cnt]
      if {[string index $opt 0] == "-"} {
        logMsg $::LEVEL1 $cl
        set cl "              $opt"
      } else {
        append cl " " $opt;
      }
  }
  logMsg $::LEVEL1 $cl

logMsg $::LEVEL1 "$VersionInfo"
if {$NoGUI} {
    logMsg $::LEVEL1 "        Command line mode"
} else {
    logMsg $::LEVEL1 "        GUI mode"
}
logMsg $::LEVEL1 "Verbose Level: $Verbose"

# bug in AndroWish - as of version 2018-06-30,
# '[info nameofexecutable]' returns null
if {($Interpreter == "") && ([machine platform] == "android")} {
    set Interpreter $::env(PACKAGE_CODE_PATH)/wish
}
logMsg $::LEVEL1 "Interpreter: $Interpreter"
logMsg $::LEVEL1 "Default Host: $DefaultHost"
logMsg $::LEVEL1 "Default Port: $DefaultPort"
if {!$NoGUI && $::tcl_platform(platform) == "unix"} {
    logMsg $::LEVEL1 "AutoStart File: $asfile"
}

# Observed OS encoding systems
# Windows 10:       cp1252
# Mac (native GUI): utf-8
# Mac (XQuartz):    utf-8
# AndroWish:        utf-8
# Linux:            utf-8

logMsg $::LEVEL1 "Operating System Encoding: [encoding system]"

if {$LogDirLocation != ""} {logMsg $::LEVEL1 $LogDirLocation}
logMsg $::LEVEL1 $LogStatus
logMsg $::LEVEL1 "Platform: [machine platform]"
logMsg $::LEVEL1 "OS: [machine os]"
if {[machine platform] == "android"} {
   logMsg $::LEVEL1 "Android device model: [machine model]"
}
logMsg $::LEVEL1 "TCL library: [info library]"
logMsg $::LEVEL1 "TCL version: [info patchlevel]"

if {$PortableDir != ""} {
    logMsg $::LEVEL1 "Using PortableDir: $PortableDir"
}
logMsg $::LEVEL1 $delayedMsgs

if {$OptPmsg != ""} {logMsg $::LEVEL1 "$OptPmsg"}

if {!$NoGUI} {
    set DistThemes [lsort -dictionary [ttk::themes]]

    # only allow default theme for now
    set DistThemes default

    set AddonThemes ""
    if {[file isdirectory $ThemeDir] && [glob -nocomplain -dir $ThemeDir *] != 0} {
        lappend auto_path $ThemeDir

        # This forces an update of the available packages list.
        # It's required for package names to find the themes in
        # <path>/themes/*.tcl
        eval [package unknown] Tcl [package provide Tcl]
        set AddonThemes [lsort -dictionary [ttk::themes]]
        foreach t $DistThemes {
          set AddonThemes [lsearch -all -inline -not -exact $AddonThemes "$t"]
        }
    }

    #on Windows, force dialogs to have readable checkboxes and radiobuttons
    #test by going to View->TYPEs->Select and Preferences->Date and Time
    #acceptable  : alt default classic
    #unacceptable: winnative clam vista
    if {[machine platform] == "windows"} {ttk::style theme use "default"}

    #on AndroWish, any style besides droid is acceptable
    if {[ttk::style theme use] == "droid"} {ttk::style theme use "default"}

}

logMsg $::LEVEL1 "HostnameFlag: $HostnameFlag"
logMsg $::LEVEL1 "LineName: $LineName"
logMsg $::LEVEL1 "LoginName: $LoginName"
logMsg $::LEVEL1 "Delay between reconnect tries to the server: $Delay (seconds)"

# dump environment variables - useful when running in portable mode
logArray $::LEVEL5 "Environment variables:" ::env

set About \
"
$VersionInfo
$Author
"

if {!$NoGUI} {
    logMsg $::LEVEL1 "Detected windowing system: [machine osgui]"
    logMsg $::LEVEL1 "Distributed Themes: $DistThemes"
    logMsg $::LEVEL1 "Custom Themes: day night"
    if {$AddonThemes != ""} {
        logMsg $::LEVEL1 "Addon Themes Dir: $ThemeDir"
        logMsg $::LEVEL1 "Addon Themes: $AddonThemes"
    }
    logMsg $::LEVEL1 "Current Theme: $ThemeName"
    logMsg $::LEVEL1 "ImageDir: $ImageDir"
    logMsg $::LEVEL1 "Popup time: $PopupTime"
    if {$NoExit} {
        set ExitOn do_nothing
        logMsg $::LEVEL1 "The \"Close Window\" ttk::button is disabled"
    }
    if {![regexp {^(:?char|word|none)$} $WrapLines]} {
        logMsg $::LEVEL1 "WrapLines set to invalid value of \"$WrapLines\", using default"
        set WrapLines "char"
    } else {
        logMsg $::LEVEL1 "WrapLines set to \"$WrapLines\""
    }
    if {$DialPrefix != ""} {
        logMsg $::LEVEL1 "Dial prefix: $DialPrefix"
    } else {
        logMsg $::LEVEL1 "Dial prefix: none"
    }
    setBrowser
    makeWindow

    logMsg $::LEVEL1 "Time Field Width: $timeWidth characters"
    logMsg $::LEVEL1 "Number Field Width: $nmbrWidth characters"
    logMsg $::LEVEL1 "Name Field Width: $nameWidth characters"
    logMsg $::LEVEL1 "Line Label Field Width: $lineIDWidth characters"
    logMsg $::LEVEL1 "Mesg Type Field Width: $mtypeWidth characters"
    logMsg $::LEVEL1 "Calculated History Text Field Width: $historyTextWidth characters"

    if {$ClipboardPopup == 0} {
        set cpt "$ClipboardPopupTime second"
        if {$ClipboardPopupTime != 1} {append cpt "s"}
        logMsg $::LEVEL2 "Clipboard Window Popup Time:  $cpt"
    } else {logMsg $::LEVEL2 "Clipboard Popup Window Time: forever"}

    switch $TypeGroups {
        0 {logMsg $::LEVEL1 "View Types: All"}
        1 {logMsg $::LEVEL1 "View Types: Calls"}
        2 {logMsg $::LEVEL1 "View Types: Messages"}
        3 {logMsg $::LEVEL1 "View Types: Smart Phone"}
        4 {logMsg $::LEVEL1 "View Types: Custom"
           logMsg $::LEVEL3 "            SelectedTypes contains $SelectedTypes"
    }
    }
    switch $LineIDGroups {
        0   {logMsg $::LEVEL1 "View Lines: All"}
        >=1 {logMsg $::LEVEL1 "View Lines: $SelectedLineIDs"}
    }
}

checkCountry $Country
logMsg $::LEVEL1 "Country Code: $Country"
if {$Country == "US"} {
    if $NoOne {
        logMsg $::LEVEL1 "Leading digit '1' in phone number will NOT be displayed"
    } else {
        logMsg $::LEVEL1 "Leading digit '1' in phone number WILL be displayed"
    }
}

if {$DateSepar != "/" && $DateSepar != "-" && $DateSepar != "."} {
    exitMsg 7 "Date separator \"$DateSepar\" is not supported. Please change it."
}

if $AltDate {
    logMsg $::LEVEL1 "Date Format: DD${DateSepar}MM${DateSepar}YYYY"
} else { logMsg $::LEVEL1 "Date Format: MM${DateSepar}DD${DateSepar}YYYY" }

if {$WakeUp} {
    if {![file executable $ModDir/ncid-wakeup]} {
        set WakeUp 0
        logMsg $::LEVEL1 "Module ncid-wakeup not found or not executable, wakeup option removed"
    }
}

if {$Module != ""} {
    if {[file exists $Module]} {
        if {![file executable $Module]} {
            # Simple test to see if running under Cygwin
            if {[file exists $CygwinBat]} {
                # The Cygwin TCL cannot execute shell scripts
                set ExecSh 1
            } else {
                exitMsg 2 "Module Not Executable: $Module"
            }
        }
    } else {exitMsg 3 "Module Not Found: $Module"}
    logMsg $::LEVEL1 "Using output Module: $Module"
    # change module name from <path>/ncid-<name> to ncid_<name>
    regsub {.*/(.*)} $Module {\1} ModName
    regsub {\-} $ModName {_} modopt
    # set the module option variable in $$modopt
    if {[catch {eval [subst $$modopt]} oops]} {
        logMsg $::LEVEL1 "No optional \"$modopt\" variable in ncid.conf"
    } else {
        regsub {.*set *(\w+)\s+.*} [eval concat $$modopt] {\1} modvar
        regsub {.*set *(\w+)\s+(\w+).*} [eval concat $$modopt] {\2} modval
        if {$modvar == "Ring"} { set CallOnRing 1 }
        logMsg $::LEVEL1 "Optional \"$modopt\" variable set \"$modvar\" to \"$modval\" in ncid.conf"
    }
    if {$CallOnRing} {
      switch -- $Ring {
        -9 {logMsg $::LEVEL1 "Will execute $Module every ring after CID"}
        -2 {logMsg $::LEVEL1 "Will execute $Module after hangup after answer"}
        -1 {logMsg $::LEVEL1 "Will execute $Module after hangup with no answer"}
         0 {logMsg $::LEVEL1 "Will execute $Module when ringing stops"}
         default {logMsg $::LEVEL1 "Will execute $Module at Ring $Ring"}
      }
    } elseif {$Module != ""} {
       logMsg $::LEVEL1 "Will execute $Module when CID arrives"
    }
}

# dump certain variables for troubleshooting
  logMsg $::LEVEL5 ""
  logMsg $::LEVEL5 "Status of variables after processing the config file:"
  logMsg $::LEVEL5 "    Host:      $ConfigFileHost"
  logMsg $::LEVEL5 "    Port:      $ConfigFilePort"
  logMsg $::LEVEL5 "    oldHost:   $ConfigFileoldHost"
  logMsg $::LEVEL5 "    oldPort:   $ConfigFileoldPort"
  logMsg $::LEVEL5 "    Hosts:     $ConfigFileHosts"
  logMsg $::LEVEL5 "    HostIndex: $ConfigFileHostIndex"

  if {!$NoGUI} {
  logMsg $::LEVEL5 ""
  logMsg $::LEVEL5 "Status of variables after processing RC file:"
  logMsg $::LEVEL5 "    Host:      $RCfileHost"
  logMsg $::LEVEL5 "    Port:      $RCfilePort"
  logMsg $::LEVEL5 "    oldHost:   $RCfileoldHost"
  logMsg $::LEVEL5 "    oldPort:   $RCfileoldPort"
  logMsg $::LEVEL5 "    Hosts:     $RCfileHosts"
  logMsg $::LEVEL5 "    HostIndex: $RCfileHostIndex"
  logMsg $::LEVEL5 "    ThemeName: $RCfileThemeName"
  }

  logMsg $::LEVEL5 ""
  logMsg $::LEVEL5 "Status of variables after processing command line arguments:"
  logMsg $::LEVEL5 "    Host:      $ArgHost"
  logMsg $::LEVEL5 "    Port:      $ArgPort"
  logMsg $::LEVEL5 "    oldHost:   $ArgoldHost"
  logMsg $::LEVEL5 "    oldPort:   $ArgoldPort"
  logMsg $::LEVEL5 "    Hosts:     $ArgHosts"
  logMsg $::LEVEL5 "    HostIndex: $ArgHostIndex"
  logMsg $::LEVEL5 ""

if {$NoGUI} doPID
connectCID
if {!$NoGUI} {bind .im <KeyPress-Return> handleGUIMSG}

# enter event loop
vwait forever
