Wednesday, March 14, 2018

Use of F#



Initial Reaction to F#

Upon reading that F# was an up and coming programming language I decided to try it out.  Even more so upon discovering that the F must be for functional since I thought maybe, as a Microsoft supported product, there would be more examples of how to use it than for Elixir.

It hasn't gone all that well.

I discovered two downloads, one for yet another compiler that has to run from a DOS window and a second that was supposed to be an IDE with a GUI (Integrated Development Environment, Graphical User Interface).

I haven't been able to use the IDE GUI download (Visual Studio Code - Ionide-fsharp).  When I try to debug any application it results in a request for the environment where there are lots of possibilities.  I initially selected C, C#, a couple of others, and one for F# (ionide-fsharp if I remember correctly).  When I attempted to act on an F# file and look through the possible environment selections, the F# one isn't presented.  And when I go back to look through the panel with the list of possibilities, the F# option is no longer presented.  So I have not been able to do anything with it.

The other download (Microsoft VS Code\Code.exe) I have been able to use although it seems to be inconsistent in its ability to compile.  For instance, F# allows a "variable" to actually be a variable by specifying "mutable" when it is declared.  But at times it will report an error that a portion of the object (such as a field of a record structure) cannot be changed; that is, that it isn't mutable.  An example will be provided later.  Another, for instance, is that I was able to move a value into an array of a structure type (again if I remember correctly) but I couldn't extract it again.  That is, after copying the value into the array it was forever lost.  Or so, at least, it seemed.

Another thing that I found strange:  I had the "bright" idea that it might be easier to parse the lines of the Learn to Code GR file by reading one character at a time and immediately checking if the character was one of the ones that delimited the fields of interest.  This worked well enough.  But resulted in finding the strange part.  As I read a single character from the file, it had to be read into a buffer at the same position that it was in the file.  That is, the implementation of the read of N bytes of the file starting at M couldn't be to a buffer that was sized as N bytes.  Instead, to read the entire file this way would mean having a buffer at least the size of the contents of the file.

Besides these problems with the compiler, one of the reasons for trying F# was that I thought there would be plenty of examples of its use from Microsoft so that it would be easier to obtain some answers than for Elixir.  Yet each time I looked up a feature the Microsoft site would have that examples for C, C++, and C# were available but that a F# example wasn't available.

Oh well.  With the ability to use mutable I was making progress with the Learn to Code GR application while having the problems with the compiler's idiosyncrasies.  But, of course, this was unhelpful to being able to use Elixir since all the talk on the internet was to avoid using mutable since it wasn't a functional language feature and how learning to do without it would allow one to find out how wonderful a functional language could be.  Needless to say I'm still awaiting that moment and still in favor of languages that allow me to code.

Another problem was sometimes the compiler would degree a construct like
  let valueArray = [| for i in 0 .. 19 -> "" |]
as unknown when it was referenced later and sometimes it would recognize that it did exist.  It seemed to just depend upon which errors it was reporting.

And yet another problem would be that it would declare perfectly valid constructs in any other language as code to be implicitly ignored.  This is, not allowing certain code due, I suppose, having deduced that the code segment could never execute when, in fact, if it allowed the code sequence, it would execute as conditions varied.

By reworking the code I was able to get around problems such as the above by using do loops instead of while loops.  However, since there isn't a "break" to exit the loop early I had to allow all the loops to continue to the end while adding an if statement to avoid actually doing anything from the point where one would normally exit the loop.  That is, the compiler seemed to have problems with the code inside of a while loop.

Using it reminded me of my experience with Java Script.  With Java Script html files I had to wait until running the application to find that the file had to be fixed.  That is, no compiler.  And with the .js files the compiler missed various erroneous constructs so they had to be found when attempting to run the application.  So I learned that only small changes should be made at a time; the better to be able to find where an error had been introduced.

Likewise with this F# compiler.  A change could cause the compiler to report many errors to lines of code that hadn't been reported to be in error before.  That is, the actual error could be far removed from the ones being reported.  Therefore, like Java Script I found it best to make changes in small increments so if errors were reported elsewhere I would know that the reported code was not in error and that it was something in the newly added/changed code that was causing the problem.

Although I couldn't find examples on Microsoft sites of F# code that did provide examples for other Microsoft supported languages I did find later on http://visualfsharp.com/ which provides information on "Microsoft Visual F# Programming" with text and examples in 30 different sections.  Although its examples aren't real world examples they were helpful.  (By real world I mean that, for instance for a car dealership, the data would be pre-canned in the code rather than coming from a database.  That is, the inventory along with the price, etc would be in the code whereas in the real world a change in such data would not require a change to the program.) 

I read through the entire set of online pages and copied the contents into a Word document so that I could refer to before I returned to coding the Learn to Code application.

Learn to Code GR

My attempt to use F# was to repeat the Learn to Code GR application of all my recent posts.

As mentioned above, I coded it in small segments to avoid the reporting of errors where no error existed.  Likewise, so that errors could be immediately fixed before proceeding.  Note, as a for instance, an error that occurred at the very end.  The code
  . . .
  let mutable maxPair : KeyTable = nullItem
  . . .
        if maxSum > maxPair.Sum then
          maxPair.Key <- item.Key
          maxPair.Value <- item.Value
          maxPair.Sum <- maxSum 
doesn't compile.  Each of the lines to set one of maxPair fields yields the error
error FS0005: This field is not mutable
even thou maxPair has been declared to be mutable.  However, either of the constructs
          maxPair <- { Key = item.Key
                       Value = item.Value
                       Sum = maxSum }
and
          let tempPair = {
              Key = item.Key
              Value = item.Value
              Sum = maxSum
              }
          maxPair <- tempPair
compile satisfactorily.  That is, the record fields of a mutable record variable can't be assigned individually but can be in total.

Another difference from what occurred using other languages that I learned about was that reading a line from a text file does not result in the trailing carriage return and line feed/new line characters being input.  Therefore, all the lines of the file that are input are like the last line of the file.  That is, without either of the trailing CR or NL characters.  Therefore, the statements in the FindSeparator function that check for CR and NL could be removed.  This does simplify the parse of the line since it makes all the lines have a similar format.

Another comment is that I found using an array of a record type object much easier to use than attempting to use a Key and KeyTable class that I used in other languages that supported a class.  Therefore, the code declares a KeyTable record type and then a keyTable array of null KeyTable objects.  (This because the array has to be sized and initialized when it is declared.)

The declaration of a function and of a variable (where variables are either constant or variable depending upon whether declared as mutable) both start with the let keyword and have the = sign to denote that the body follows.  The specification of the type is usually not necessary since F# is supposed to be able to figure it out.  But I have chosen to supply it (at least most of the time).  '<-' is used to assign a new value to a mutable variable. 

The use of indentation and white space is mostly used rather than brackets, trailing ';' symbols, ',' in function calling parameters to use as separators, and such.  Therefore, careful attention is required in regards to indentation so as to group code as desired. 

The F# version of the Learn To Code GR application follows.

module LearnToCodeGRModule =
  open System
  open System.IO
  open System.Net

  // Create record type
  type KeyTable = {
       Key : string
       Value : string
       Sum : int
       }

  // Continue use of record type by creating global keyTable array
  let nullItem = {
      Key = ""
      Value = ""
      Sum = 0
      }
  let mutable keyTable : KeyTable array =
    [| for i in 0..59 -> nullItem |]


  // Add new record to keyTable
  let AddRecord (start : int) (key : string) (value : string) =

    let mutable srchItem : KeyTable = nullItem
    let mutable i : int = start
    let mutable cont : string = "Yes"
   
    for i = start to keyTable.Length-1 do
      srchItem <- keyTable.[i]
      if srchItem.Sum = 0 then
        if cont = "Yes" then
          let mutable newItem : KeyTable = {
              Key = key
              Value = value
              Sum = 1
              }
          keyTable.[i] <- newItem
          cont <- "No"

//end AddRecord function

  // Update keyTable with new key, value pair
  let Update (key : string) (value : string) =

    let mutable ignoreItem : string = "No"
    let mutable item : KeyTable = nullItem
    for i = 0 to keyTable.Length-1 do
      item <- keyTable.[i]

      if ignoreItem = "No" then
        if item.Sum = 0 then // reached empty location without a match
          AddRecord i key value
          ignoreItem <- "Yes"
        elif item.Key = key then // key matches to array item
          if item.Value = value then // value also matches
            let mutable modItem : KeyTable = {
                Key = item.Key
                Value = item.Value
                Sum = item.Sum + 1
                }
            keyTable.[i] <- modItem
            ignoreItem <- "Yes"

//end of Update function

  // Find next field of interest indicator character
  let HT : char = '\t'
  let CR : char = '\r'
  let NL : char = '\n'
  let FindSeparator(segment : string) : int =
    let mutable sepLocation = -1
    for c = 0 to segment.Length-1 do
      if sepLocation = -1 then
        if segment.[c] = HT || segment.[c] = CR || segment.[c] = NL then
          sepLocation <- c
    if sepLocation = -1 then // no final separator
      sepLocation <- segment.Length
    sepLocation
//end of FindSeparator function

  // Parse the line and Update the keyTable
  let ParseLine(line) =
    let mutable endField = -1
    let mutable partialLine : string = line
    let mutable discard : string = ""
    let mutable key : string = ""
    let mutable value1 : string = ""
    let mutable value2 : string = ""
    for f = 0 to 3 do // four fields of interest expected
      let endField : int = FindSeparator(partialLine)
      let mutable seg : string = partialLine.[0..endField-1]
      if f = 0 then
        discard <- seg
      elif f = 1 then
        key <- seg
      elif f = 2 then
        value1 <- seg
      else
        value2 <- seg
      partialLine <- partialLine.[endField+1..partialLine.Length-1]

    // Update keyTable with the key, value pairs of the line
    Update key value1
    Update key value2

//end of ParseLine function

  // Read and process lines from the file of path
  let ByLine(path) =
    use input = File.OpenText(path)
    printfn "Data read from the file follows"
    while not input.EndOfStream do
      let line = input.ReadLine()
      printfn "%s" line
      ParseLine( line )
//end of ByLine function

  // Read the file and perform the task.  This is the main function.
  ByLine( "C:/Source/LearnToCodeGR/max-col-sum-by-Key.tsv" )

  // Report the results
  // Note: Instead of invoking a Report function at the end of the ByLine
  //       function it is included here as an extension of the main function
  printfn ""
  printfn "Report of the results"
  printfn "Key  Value Sum  Total Max"
  let mutable maxPair : KeyTable = nullItem
  let mutable keyTotal = 0
  let mutable maxSum = keyTable.[0].Sum
  let mutable currentKey = keyTable.[0].Key

  let mutable item : KeyTable = nullItem

  for i = 0 to keyTable.Length-1 do
    item <- keyTable.[i]
    if item.Sum <> 0 then // a non-null array position
      if item.Key <> currentKey then
        printfn "                 %d   %d" keyTotal maxSum
        currentKey <- item.Key // new key
        keyTotal <- item.Sum   // initial Sum of new key
        maxSum <- item.Sum
        if maxSum > maxPair.Sum then
          maxPair <- { Key = item.Key
                       Value = item.Value
                       Sum = maxSum }
      else
        if item.Sum > maxSum then
          maxSum <- item.Sum
        if maxSum > maxPair.Sum then
          maxPair <- { Key = item.Key
                       Value = item.Value
                       Sum = maxSum }

        keyTotal <- keyTotal + item.Sum
      printfn "%s   %s    %d" item.Key item.Value item.Sum

  printfn "                 %d   %d" keyTotal maxSum // of last element
  printfn " "
  printfn "Key and Value with the most references is"
  printfn "Key  Value Sum"
  printfn "%s   %s    %d" maxPair.Key maxPair.Value maxPair.Sum 

where the results, as seen on the console or to a file piped to capture the console output – that is,
learntocodegr > LearnToCodeGRresults.dat
are as follows.

Data read from the file follows
0,912_NUM   1000  1     1
0,912_NUM   1000  2     1
0,912_NUM   1000  1     1
0,912_NUM   1000  2     2
0,912_NUM   1000  1     1
0,912_NUM   1000  2     2
0,912_NUM   1000  1     1
0,912_NUM   1000  1     1
0,912_NUM   1000  2     2
0,912_NUM   1000  1     1
0,912_NUM   2000  1     1
0,912_NUM   2000  1     1
0,912_NUM   2000  1     1
0,912_NUM   2000  1     1
0,912_NUM   2000  2     2
0,912_NUM   2000  1     1
0,912_NUM   2000  1     1
0,912_NUM   2000  2     2
0,912_NUM   2000  1     1
0,912_NUM   2000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   4000  1     1
0,912_NUM   4000  1     1
0,912_NUM   4000  3     3
0,912_NUM   4000  2     1
0,912_NUM   4000  1     1
0,912_NUM   4000  1     1
0,912_NUM   4000  1     1
0,912_NUM   4000  1     1
0,912_NUM   4000  4     2
0,912_NUM   4000  1     1
0,912_NUM   5000  2     2
0,912_NUM   5000  2     2
0,912_NUM   5000  1     1
0,912_NUM   5000  1     1
0,912_NUM   5000  1     1
0,912_NUM   5000  2     2
0,912_NUM   5000  3     1
0,912_NUM   5000  2     2
0,912_NUM   5000  4     3
0,912_NUM   5000  3     3

Report of the results
Key  Value Sum  Total Max
1000   1    13
1000   2    7
                 20   13
2000   1    16
2000   2    4
                 20   16
3000   1    20
                 20   20
4000   1    15
4000   3    2
4000   2    2
4000   4    1
                 20   15
5000   2    8
5000   1    7
5000   3    4
5000   4    1
                 20   8

Key and Value with the most references is
Key  Value Sum
3000   1    20





Monday, February 19, 2018

Use of Windows Controls from C++, Part 3





Well that was easy.  I started mid morning to implement the use of the list box that I created yesterday and played around getting a dialog box to compile.  Then decided that a dialog box wasn't something I wanted to use and returned to what would be necessary to fill in the empty list box.  By 2:15 I had it filled in with all the files and folders in my starting folder with a vertical scroll bar replacing the control that had had only boundary lines.

And now, one and half days later it is all working.  It seems that I'm beginning to be quite at home at the online University of Microsoft.

The application detects when a folder/directory versus a file is highlighted and switches to displaying the files in the new folder when a folder is selected.  Otherwise, it opens the file as the file to parse.  So this is similar to the OpenFileDialog() that is an available function with the visual compilers.

I did have some problems.  I wanted to have the list box display in the order found when using the FindNextFile function but the files were in alpha order even thou LBS_SORT was not specified as a style and I couldn't find a way to avoid this so I had to give up.  The files were not in the order presented by Windows Explorer for any of the ways to change the order such as by name, by type, by creation date, etc.

Also, to empty the list box prior to displaying the files of a newly selected folder, the LB_RESETCONTENT message didn't work.  By that I mean didn't work well.  The list box just disappeared except initially a strange vague shape where its top should have been.  So I had to delete its contents line by line and this in reverse from last to first using the LB_DELETESTRING message to the list box control.

However, when one way wouldn't work I was able to find another without too much delay.

Jing captures of the window are shown below.  First how the window is displayed when the application is first launched.  Then, after the Select File button has been clicked.  Following that after the needed folder name has been selected and then after the file to be parsed has been selected.  Finally, how the sequence can be restarted by clicking the Select File button once again.

Following these graphics the code is presented.  The code to report the results, including the Key and KeyTable classes, is the same as the last post (except for truncating the initial label to avoid overlapping the list box) as well as the code to parse the file.  The code to open the file in the StartApp function is the same except for needing the obtainFilePath() function to obtain the path of the selected file.








The non-visual C++ code that creates the window and its controls is as follows.

#ifndef KEYTABLECPP
#define KEYTABLECPP

// Key class
class Key
{
  public:
    Key(); // constructor

    int key;        // value of key
    int valueCount; // number of different values associated with key
    static const int VALUESSIZE = 20; // initial size of values & sums
                                      //  arrays for particular key
    int *values;    // will point to the array
    int *sums;      //  "

}; // end class Key

// KeyTable class
class KeyTable //: public Component
{

  public:
    KeyTable(); // constructor

    int getKey(int index);
    int getKeyCount();
    // Return number of different values for key at index
    int getValueCount(int index);

    int setKey(int key);
    int setValue(int index, int value);
    int getValue(int indexKey, int indexValue);
    int incSum(int indexKey, int indexValue);
    int getSum(int indexKey, int indexValue);

  private:
    int keyCount; // number of different keys
    static const int TABLESIZE = 30; // initial size of keys array
    Key* keys = new Key[TABLESIZE];  // instances of "struct" class

}; // end class KeyTable

#endif // KEYTABLECPP

Key and KeyTable class within KeyTable.cpp
#include <iostream>

#include "keytable.h"

Key::Key() // constructor
{
  valueCount = 0;
  key = 0;
  values = new int[VALUESSIZE];
  sums = new int[VALUESSIZE];
  for (int i = 0; i < VALUESSIZE; i++)
  {
    values[i] = -1; // so no match until a value added
    sums[i] = 0;    // so any first increment of the sum will be to 1
  }
} // end Key constructor


KeyTable::KeyTable() // constructor
{
  this->keyCount = 0;
} // end KeyTable constructor

// Return key at index
KeyTable::getKey(int index)
{
  int tempKey;
  if (index < KeyTable::keyCount)
  {
    int key = KeyTable::keys[index].key;
    return key;
  }
  else
  {
    return 0;
  }
} // end getKey

// Return number of keys in table
KeyTable::getKeyCount()
{
  return keyCount;
} // end getKeyCount

KeyTable::getValueCount(int index)
{
  return this->keys[index].valueCount;
} // end getValueCount

KeyTable::setKey(int key)
{
  for (int i = 0; i < keyCount; i++)
  {
    if (this->keys[i].key == key) // key already in table
    {
      return i;
    }
  } // end for loop
  if (this->keyCount < TABLESIZE) // add key to table if room
  {
    this->keys[keyCount].key = key;
    int index = keyCount;
    keyCount++;
    return index;
  }
  else
  {
    std::cout << "Error: Too many different keys" << "\n";
    return -1;
  }
} // end setKey

// Add value for key at index, return value index
KeyTable::setValue(int index, int value)
{
  for (int i = 0; i < this->keys[index].valueCount; i++)
  {
    if ( this->keys[index].values[i] == value )
    {
      // Increment sum
      this->keys[index].sums[i]++;
      return i;
    }
  } // end for loop

  // Add value to array for key since didn't return for existing value
  this->keys[index].values[this->keys[index].valueCount] = value;
  this->keys[index].sums[this->keys[index].valueCount]++;
  int valueIndex = this->keys[index].valueCount;
  this->keys[index].valueCount++; // increment number of values for key
  return valueIndex;
} // end setValue

// Get value for key at indexes, return value
KeyTable::getValue(int indexKey, int indexValue)
{
  return this->keys[indexKey].values[indexValue];
} // end getValue

// Increment Sum for value of key at indexKey and value at indexValue
KeyTable::incSum(int indexKey, int indexValue)
{
  // Increment sum
  int count = this->keys[indexKey].sums[indexValue]++;

  return count;
} // end incSum

// Get sum for key at indexes, return sum
KeyTable::getSum(int indexKey, int indexValue)
{
  return this->keys[indexKey].sums[indexValue];
} // end getSum

WinMain.cpp
#include <stdio.h>
#include <iostream>
#include <cstring>

#include "windows.h"
#include "wingdi.h"
#include "winuser.h"

#include "afxres.h"

#include <keytable.h>

// Global variables

HINSTANCE hinst;

HWND hMainWnd; // the main window
ATOM mainWindow;

HWND hSelListBoxWnd = 0; // the listbox

int ncmdShow; // from lauch of the application

// Structure to keep track of controls created for the window
int numberOfControls = 0; // number of controls created
struct Controls {
  std::string name; // name of control
  HWND handle;      // handle of control
};
Controls control[10];

// Structure to keep track of files and folders loaded into listbox
int listBoxItemsCount = 0; // largest index into the array
int nameMaxSize = 300;
struct ListBoxItems {
  DWORD fileAttributes; // attributes of the file or folder
  CHAR  fileName[300];  // name of the file or folder
};
int listBoxItemsMax = 500;
ListBoxItems listBoxItems[500];

int dirListBoxIndex = 0; // index to use to obtain directory for fileRelocate
std::string startDir;    // directory to use for list box

int fileListBoxIndex = 0; // index to use to obtain filename to be opened

// Other variables used by both the app thread and the callback thread
BOOL locateFile = FALSE;   // activate ListBox to select the file
BOOL relocateFile = FALSE; // clear ListBox to fill with files of new directory
BOOL fileSelected = FALSE; // file to parse selected
BOOL fileOpenEvent = TRUE; // only run the Parse, etc once
FILE* selectedFile;

BOOL finished = FALSE; // finished looking at window, time to exit

// Function prototypes.
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
BOOL InitApplication(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);

BOOL fileLocate();      // Find file to be used using listbox
BOOL fileRelocate();    // Find file in new directory using listbox
BOOL emptyTheListBox(); // Empty the listbox
char* obtainFilePath(); // Form the filepath from selected filename

BOOL StartApp(); // Determine the LearnToCodeGR results

// Note: MainWndProc runs in a Windows OpSys thread
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);


// Application entry point.
int WINAPI WinMain( HINSTANCE hinstance,
                    HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine,
                    int nCmdShow )
{
  MSG msg; // message

  ncmdShow = nCmdShow;

  // Create main window and the initial controls
  BOOL result;
  result = InitApplication(hinstance);
  if (!result)
    return FALSE;

  if (!InitInstance(hinstance, nCmdShow))
    return FALSE;

  // Main "forever" loop
  BOOL fGotMessage;
  while ((fGotMessage = GetMessage(&msg, (HWND) NULL, 0, 0))!=0 && fGotMessage != -1)
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);

    // Set of possibilities to run in application thread initiated by setting variable
    // in callback thread.

    // Fill listbox using initial directory/folder
    if (locateFile)
    {
      fileLocate();
    }

    // Fill listbox after the selection of a new directory/folder
    if (dirListBoxIndex > 0)
    {
      fileRelocate();
    }

    // Parse selected file and display results
    if ((fileSelected) & (fileOpenEvent)) // open selected file
    {
      fileOpenEvent = FALSE; // dummy indication that can only be used once
      // Start main portion of the application.
      result = StartApp();
    }

    // Exit the forever loop to exit the application
    if (finished)
    {
      break; // exit while loop
    }

  } // end while loop

  // Exit the application
  return msg.wParam;
  UNREFERENCED_PARAMETER(lpCmdLine);

} // end WinMain

// To treat Windows events while executing in a separate thread
LRESULT CALLBACK MainWndProc( HWND hMainWnd,    // handle to window
                              UINT uMsg,        // message identifier
                              WPARAM wParam,    // first message parameter
                              LPARAM lParam )   // second message parameter
{

  PAINTSTRUCT ps;
  HDC hdc;

  switch (uMsg)
  {
    case WM_CREATE:
      // Initialize the window.
      return 0;

    case WM_PAINT:
    {
      // Paint the window's client area.
      return 0;
    }

    case WM_SIZE:
      // Set the size and position of the window.
      return 0;

    case WM_DESTROY:
      // Clean up window-specific data objects.
      return 0;

    case WM_DRAWITEM:
    {
      return 0;
    }

    case WM_SETTEXT:
    {
      return true;
    }

    case WM_INITDIALOG:
    {
      return 0;
    }

    // WM_COMMAND (treat control events)

    case WM_COMMAND:
    {
      // Indicate when control event of interest occurs
      BOOL matchedHandle = FALSE;

      // Find if control of interest used
      HWND errorHandle = 0;
      for (int c = 0; c < numberOfControls; c++)
      {
      if (control[c].handle == (HWND)lParam) // found the control
        {
          matchedHandle = TRUE;

          std::string name = control[c].name;
          if (name == "Select ListBox") // Select from ListBox clicked
          {
            switch (LOWORD(wParam))
            {
              case IDOK:
              case IDCANCEL:
                EndDialog(control[c].handle, LOWORD(wParam));
                return TRUE;
            }
            switch (HIWORD(wParam))
            {
              case LBN_SELCHANGE: // listbox selection made
              {
                // Get selected index.
                int lbItem = (int)SendMessage(control[c].handle, LB_GETCURSEL, 0, 0);

                // Get listbox item data.
                int index = (int)SendMessage(control[c].handle, LB_GETITEMDATA,
                                             lbItem, 0);

                // Determine if the selected listbox item is a directory/folder
                if ((listBoxItems[index].fileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                    != 0)
                {
                  dirListBoxIndex = index;
                }
                else
                {
                  fileListBoxIndex = index;
                  fileSelected = TRUE; // indicate that file should be opened
                }

                return TRUE;

              }
            }
          }
          else if (name == "Select File") // Select File button clicked
          {
            locateFile = TRUE; // indicate that ListBox should be activated
          } // end Select File

          if (name == "Done")
          {
            finished = TRUE; // set for use by WinMain thread
            return TRUE;
          }

        } // end if control[c].handle

      } // end for loop
      if (matchedHandle)
      {
      return TRUE;
      } else
      {
        return 0;
      }

    } // end case WM_COMMAND

    case WM_NOTIFY:
    {
      return TRUE;
    }

    // Process other messages.
    default:
      return DefWindowProc(hMainWnd, uMsg, wParam, lParam);
  }
  return 0;

} // end MainWndProc callback


// Register the main window class
BOOL InitApplication(HINSTANCE hinstance)
{
  WNDCLASSEX wcx;

  // Fill in the window class structure with parameters
  // that describe the main window.
  wcx.cbSize = sizeof(wcx);          // size of structure
  wcx.style = CS_HREDRAW | CS_VREDRAW;     // redraw if size changes
  wcx.lpfnWndProc = (WNDPROC) MainWndProc; // points to window procedure
  wcx.cbClsExtra = 0;                // no extra class memory
  wcx.cbWndExtra = 0;                // no extra window memory
  wcx.hInstance = hinstance;         // handle to instance
  wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);    // predefined app. icon
  wcx.hCursor = LoadCursor(NULL, IDC_ARROW);      // predefined arrow
  wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // white background brush
  wcx.lpszMenuName =  "MainMenu";    // name of menu resource
  wcx.lpszClassName = "MainWClass";  // name of window class
  wcx.hIconSm = (HICON)LoadImage( hinstance, // small class icon
                                  MAKEINTRESOURCE(5),
                                  IMAGE_ICON,
                                  GetSystemMetrics(SM_CXSMICON),
                                  GetSystemMetrics(SM_CYSMICON),
                                  LR_DEFAULTCOLOR );

  // Register the window class.
  mainWindow = RegisterClassEx(&wcx);
  DWORD y;
  if (mainWindow==0)
  { y = GetLastError();
  }

  // Return RegisterClassEx(&wcx);
  return (BOOL)mainWindow;

} // end InitApplication


// Create the main window and the initial controls.
BOOL InitInstance(HINSTANCE hinstance, int nCmdShow)
{
  // Save the application-instance handle.
  hinst = hinstance;

  // Create the main window.
  hMainWnd = CreateWindow
             ( "MainWClass",        // name of window class
               "LearnToCodeGR",     // title-bar string
               WS_OVERLAPPEDWINDOW, // top-level window
               CW_USEDEFAULT,       // default horizontal position
               CW_USEDEFAULT,       // default vertical position
               CW_USEDEFAULT,       // default width
               CW_USEDEFAULT,       // default height
               (HWND) NULL,         // no owner window
               (HMENU) NULL,        // use class menu
               hinstance,           // handle to application instance
               (LPVOID) NULL );     // no window-creation data

  if (!hMainWnd)
    return FALSE;

  if (hMainWnd!=0)
  {
    // Create Select File button control.
    HWND hwndSelButton = CreateWindow
                         ( "BUTTON",      // Predefined class; Unicode assumed
                           "Select File", // Button text
                           WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles
                           10,            // x position
                           10,            // y position
                           100,           // Button width
                           35,            // Button height
                           hMainWnd,      // Parent window
                           NULL,          // no menu
                           (HINSTANCE)GetWindowLong(hMainWnd, GWL_HINSTANCE),
                           NULL );        // Pointer not needed.
    control[numberOfControls].handle = hwndSelButton; // Capture handle
    control[numberOfControls].name = "Select File";   //  and name of the
    numberOfControls++;                               //  button control

    // Create button to use to exit the application.
    HWND hwndDoneButton = CreateWindow
                          ( "BUTTON",      // Predefined class; Unicode assumed
                            "Done",        // Button text
                            WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
                                           // Styles
                            10,            // x position
                            310,           // y position
                            100,           // Button width
                            35,            // Button height
                            hMainWnd,      // Parent window
                            NULL,          // no menu
                            (HINSTANCE)GetWindowLong(hMainWnd, GWL_HINSTANCE),
                            NULL );        // Pointer not needed.
    control[numberOfControls].handle = hwndDoneButton; // Capture handle
    control[numberOfControls].name = "Done";           //  and name of the
    numberOfControls++;                                //  button control

  } // end if for main window created

  // Show the main window and send a WM_PAINT message to the window procedure.
  ShowWindow(hMainWnd, nCmdShow);
  UpdateWindow(hMainWnd);

  return TRUE;
}

// Find files in directory and display in the listbox
BOOL findFiles(HWND listBox, std::string startDir)
{
  WIN32_FIND_DATA FindFileData;

  int size;
  size = startDir.size();
  char *startVal = new char[size+1];
  startVal[size] = 0; // trailing null
  memcpy(startVal, startDir.c_str(), size);

  HANDLE hFind = FindFirstFile((LPCSTR)startVal, &FindFileData);

  int index = 0;

  if(hFind == INVALID_HANDLE_VALUE)
  {
    return false;
  }
  else do
  {
    // Add file name to array and send to listbox
    int pos = (int)SendMessage( listBox, LB_ADDSTRING, 0,
                                (LPARAM)FindFileData.cFileName );
    if (index < listBoxItemsMax)
    { listBoxItems[index].fileAttributes = FindFileData.dwFileAttributes;
      for (int f = 0; f < nameMaxSize; f++)
      {
        listBoxItems[index].fileName[f] = FindFileData.cFileName[f];
      }
      if (index > listBoxItemsCount)
      {
        listBoxItemsCount = index;
      }
    }
    // Set the array index of the file/folder as item data.
    // This will enable it to be retrieved from the array
    // even after the items are sorted by the list box.
    SendMessage(listBox, LB_SETITEMDATA, pos, (LPARAM) index);
    index++;

  } while (FindNextFile(hFind, &FindFileData));

  DWORD findError = GetLastError();
  if (findError != 18) // not end of file
  {
    std::string findErrorStr = "FindNextFile Error " + std::to_string(findError);
    std::cout << findErrorStr << '\n';
  }
  if (index > listBoxItemsCount) // for the last increment
  {
    listBoxItemsCount = index;
  }

  FindClose(hFind);
  return true;

} // end findFiles


// Create listbox and fill with Get the selected file via a List Box
// or empty if already exists.
BOOL fileLocate()
{
  if (hSelListBoxWnd == 0)
  {
    hSelListBoxWnd = CreateWindow
                     ( "LISTBOX",     // Predefined class; Unicode assumed
                       "Select File", // Title text
                       LBS_NOTIFY | WS_VISIBLE | WS_VSCROLL | WS_BORDER | WS_CHILD,
                       200,           // x position
                       10,            // y position
                       350,           // Box width
                       400,           // Box height
                       hMainWnd,      // Parent window
                       NULL,          // no menu
                       (HINSTANCE)GetWindowLong(hMainWnd, GWL_HINSTANCE),
                       NULL );        // Pointer not needed.
    control[numberOfControls].handle = hSelListBoxWnd; // Capture handle
    control[numberOfControls].name = "Select ListBox"; //  and name of the
    numberOfControls++;                                //  button control
  }
  else
  {
    emptyTheListBox();
  }

  // Select initial directory/folder
  startDir = ("C:/Source/*");

  // Fill the listbox with file names in the folder
  findFiles(hSelListBoxWnd,startDir);

  // Cause the controls to be displayed in the main window
  UpdateWindow(hMainWnd);

  locateFile = FALSE; // don't repeat call to this routine from forever loop

} // end fileLocate

// Empty the listbox and redisplay
BOOL emptyTheListBox()
{
  for (int i = 0; i < listBoxItemsCount; i++)
  {
    int j = listBoxItemsCount - 1 - i;

    SendMessage(hSelListBoxWnd, LB_DELETESTRING, j, 0);
  }

  UpdateWindow(hSelListBoxWnd); // cause the list box to be redisplayed

} // end emptyTheListBox


// Clear the ListBox and then add files of new selected directory
BOOL fileRelocate()
{

  // Save selected directory name
  CHAR directoryName[nameMaxSize];
  memcpy(directoryName,listBoxItems[dirListBoxIndex].fileName,nameMaxSize);

  emptyTheListBox();

  dirListBoxIndex = 0; // prepare for next selection

  listBoxItemsCount = 0; // empty listBoxItems array

  int last = startDir.length();
  std::string slash = "/";
  startDir.insert(last-1,slash);         // add extra / before *
  startDir.insert(last-1,directoryName); // insert new directory before last /

  findFiles(hSelListBoxWnd,startDir); // load list box from selected folder

  UpdateWindow(hSelListBoxWnd); // cause the list box to be redisplayed

  return FALSE; // don't repeat call to this routine until new folder selected

} // end fileRelocate


// File path to be returned via obtainFilePath
// Declared as static to avoid being on stack when returned
char filePath[64];

// Obtain file path by combining directory and file name
char* obtainFilePath()
{
  int last = startDir.length();
  memcpy(filePath,startDir.c_str(),last-1); // start with directory

  for (int i = 0; i < 64; i++) // append selected file to directory
  {
    filePath[last-1+i] = listBoxItems[fileListBoxIndex].fileName[i];
    if (listBoxItems[fileListBoxIndex].fileName[i] == 0)
    {
      break; // exit the loop after trailing null added to filePath
    }
  }

  return filePath;
} // end obtainFilePath


/*
 * From here to the end is the C++ code for the LearnToCodeGR application.
 * This code runs in the WinMain application thread.
 */

KeyTable keyTable; // Instance of KeyTable class object

// Report the key and value with the most references
void Report()
{
  // Report individual results to the console.
  printf("\n");
  printf("Report   ");
  printf("\n");

  for (int i = 0; i < keyTable.getKeyCount(); i++)
  {
    for (int j = 0; j < keyTable.getValueCount(i); j++)
    {
      std::string report = ("Key ");
      report = report + std::to_string(keyTable.getKey(i));
      report = report + (" Value ");
      report = report + std::to_string(keyTable.getValue(i,j));
      report = report + (" Sum ");
      report = report + std::to_string(keyTable.getSum(i,j));
      std::cout << report << '\n';
    } // end for loop
  } // end for loop

  // Items identifying the key with the largest number of a particular value.
  int key = 0;
  int value = 0;
  int sum = 0;

  for (int i = 0; i < keyTable.getKeyCount(); i++)
  {
    for (int j = 0; j < keyTable.getValueCount(i); j++)
    {
      if (keyTable.getSum(i,j) > sum) // save key and value with greater sum
      {
        key = keyTable.getKey(i);
        value = keyTable.getValue(i,j);
        sum = keyTable.getSum(i,j);
      }
    } // end for loop
  } // end for loop

  // Report the key and value with the greatest number of references to the console.
  printf("\n");
  std::string report = ("Key and Value with greatest Sum ");
  report = report + std::to_string(key) + " ";
  report = report + std::to_string(value) + " ";
  report = report + std::to_string(sum);
  std::cout << report << '\n';

  // Create and report the results to window controls.

  // Convert int's to char arrays.
  std::string keyStr = std::to_string(key);
  int size;
  size = keyStr.size();
  char *keyVal = new char[size+1];
  keyVal[size] = 0;
  memcpy(keyVal, keyStr.c_str(), size);

  std::string valueStr = std::to_string(value);
  size = valueStr.size();
  char *valueVal = new char[size+1];
  valueVal[size] = 0;
  memcpy(valueVal, valueStr.c_str(), size);

  std::string sumStr = std::to_string(sum);
  size = sumStr.size();
  char *sumVal = new char[size+1];
  sumVal[size] = 0;
  memcpy(sumVal, sumStr.c_str(), size);

  HWND hResultsWnd1 = CreateWindow
                      ( "EDIT",           // predefined class
                        "Key and Value with", // label text
                        WS_CHILD | WS_VISIBLE | ES_LEFT,   // Styles
                        10, 125, 150, 20, // set size and location
                        hMainWnd,         // parent window
                        NULL,             // edit control ID
                        (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                        NULL );           // pointer not needed
  HWND hResultsWnd2 = CreateWindow
                      ( "EDIT",           // predefined class
                        " greatest Sum", // label text
                        WS_CHILD | WS_VISIBLE | ES_LEFT,   // Styles
                        10, 150, 150, 20, // set size and location
                        hMainWnd,         // parent window
                        NULL,             // edit control ID
                        (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                        NULL );           // pointer not needed

  HWND hKeyLabelWnd = CreateWindow
                      ( "EDIT",          // predefined class
                        "Key ",          // label text
                        WS_CHILD | WS_VISIBLE | ES_LEFT, // Styles
                        10, 180, 60, 20, // set size and location
                        hMainWnd,        // parent window
                        NULL,            // edit control ID
                        (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                        NULL );          // pointer not needed
  HWND hKeyValueWnd = CreateWindow
                      ( "EDIT",          // predefined class
                        (LPCTSTR)keyVal, // result for key
                        WS_CHILD | WS_VISIBLE | ES_LEFT | WS_BORDER,
                        60, 178, 60, 30, // set size in WM_SIZE message
                        hMainWnd,        // parent window
                        NULL,            // edit control ID
                        (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                        NULL );          // pointer not needed

  HWND hValLabelWnd = CreateWindow
                      ( "EDIT",          // predefined class
                        "Value",         // label text
                        WS_CHILD | WS_VISIBLE | ES_LEFT, // Styles
                        10, 220, 60, 20, // set size and location
                        hMainWnd,        // parent window
                        NULL,            // edit control ID
                        (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                        NULL );          // pointer not needed
  HWND hValValueWnd = CreateWindow
                      ( "EDIT",           // predefined class
                        (LPCTSTR)valueVal,// result for key
                        WS_CHILD | WS_VISIBLE | ES_LEFT | WS_BORDER,
                        60, 218, 60, 30,  // set size in WM_SIZE message
                        hMainWnd,         // parent window
                        NULL,             // edit control ID
                        (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                        NULL );           // pointer not needed

  HWND hSumLabelWnd = CreateWindow
                      ( "EDIT",          // predefined class
                        "Sum ",          // label text
                        WS_CHILD | WS_VISIBLE | ES_LEFT, // Styles
                        10, 260, 60, 20, // set size and location
                        hMainWnd,        // parent window
                        NULL,            // edit control ID
                        (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                        NULL );          // pointer not needed
  HWND hSumValueWnd = CreateWindow
                      ( "EDIT",          // predefined class
                        (LPCTSTR)sumVal, // result for key
                        WS_CHILD | WS_VISIBLE | ES_LEFT | WS_BORDER,
                        60, 258, 60, 30, // set size in WM_SIZE message
                        hMainWnd,        // parent window
                        NULL,            // edit control ID
                        (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                        NULL );          // pointer not needed

  UpdateWindow(hMainWnd); // cause the controls to be displayed in the main window

  return;

} // end Report

// Update keyTable with data from a parsed line
void Update(int nKey, int nValue1, int nValue2)
{
  // This function first checks if the key is new and, if so, adds it to the
  // keys array.  It then does similar for the values associated with it.

  // Check whether the key is already in the table
  int keyIndex = -1;
  int valueIndex = -1;

  int keyCount = keyTable.getKeyCount();
  for (int i = 0; i < keyCount; i++)
  {
    if (keyTable.getKey(i) == nKey)
    {
      keyIndex = i;  // key already in table
      break; // exit loop
    }
  } // end loop
  if (keyIndex < 0) // key not in the table
  {
    keyIndex = keyTable.setKey(nKey); // add key
    valueIndex = keyTable.setValue(keyIndex,nValue1); // add value for key
    if (nValue1 != nValue2)
    { keyTable.setValue(keyIndex,nValue2); }
    else
    { keyTable.incSum(keyIndex,valueIndex); }
  }
  else // key in the table
  {
    int valueCount = keyTable.getValueCount(keyIndex);
    valueIndex = keyTable.setValue(keyIndex,nValue1); // add value for key
    if (nValue1 != nValue2)
    { keyTable.setValue(keyIndex,nValue2); }
    else
    { keyTable.incSum(keyIndex,valueIndex); }
  }

  return;

} // end Update

#define NINE 57 // ASCII character for digit 9
#define ZERO 48 // ASCII character for digit 0

// Convert character array to int
int toInt(char* data, int length) //iS, int iE)
{
  int index = length - 1; // loop in reverse
  int digit;
  int m = 1;      // multiplier for shift
  int number = 0; // Numeric result

  while (index >= 0) //iS)
  {
    digit = data[index] - ZERO; // convert ASCII to digit
    number = number + (m * digit);
    m = m * 10;
    index--;
  }

  return number;

} // end toInt

enum Scan //Fields
{ BYPASS, FKEY, VALUE1, VALUE2 };

// Extract the fields of interest from the file's line and retain the data
void Parse(int count, char* data)
{
  #define CR '\r' // carriage return
  #define HT '\t' // horizontal tab
  #define LF '\n' // line feed
  #define TRUE 1

  // Parse the supplied line of data from the file to obtain the three
  // fields of interest (key and two values), convert those fields to
  // integers, and then update a data structure (the KeyTable class) to
  // retain the data for evaluation when the complete file has been parsed.

  int keyValue = 0;
  int value1Value = 0;
  int value2Value = 0;

  char alphaDigits [10];
  int numDigits = 0;

  bool updateDone = false;

  int offset = 0; // offset into data
  Scan scanPhase = BYPASS;

  // Parse all bytes of record previously read from the file.  Each field of
  // interest is preceded by a horizontal tab.  The end of the final field
  // is marked by a CR LF or only a LF (NL) except the last record where
  // there are no more characters to be treated.
  while (offset < count)
  {
    switch (scanPhase)
    {
      case BYPASS:
        if (data[offset] == HT)
        {
          scanPhase = FKEY;
          keyValue = 0;
          value1Value = 0;
          value2Value = 0;
          numDigits = 0;
        }
        break;

      case FKEY:
        if (data[offset] != HT)
        {
          if ((data[offset] >= ZERO) & (data[offset] <= NINE))
          {
            alphaDigits[numDigits] = data[offset];
            numDigits++;
          }
        }
        else
        {
          keyValue = toInt(alphaDigits, numDigits);
          scanPhase = VALUE1;
          numDigits = 0;
        }
        break;

      case VALUE1:
        if (data[offset] != HT)
        {
          if ((data[offset] >= ZERO) & (data[offset] <= NINE))
          {
            alphaDigits[numDigits] = data[offset];
            numDigits++;
          }
        }
        else
        {
          value1Value = toInt(alphaDigits, numDigits);
          scanPhase = VALUE2;
          numDigits = 0;
        }
        break;

      case VALUE2:
        if ((data[offset] == LF) | (data[offset] == CR) | offset+1 >= count)
        {
          value2Value = toInt(alphaDigits, numDigits);
          // Update tables with the data from the record.
          Update( keyValue, value1Value, value2Value );
          updateDone = true;

          // Initialize for next record
          keyValue = 0;
          value1Value = 0;
          value2Value = 0;
          numDigits = 0;
          scanPhase = BYPASS;
        }
        else
        {
          if ((data[offset] >= ZERO) & (data[offset] <= NINE))
          {
            alphaDigits[numDigits] = data[offset];
            numDigits++;
          }
        }
        break;
    } // end switch

    // Complete processing of final record when not terminated by New Line
    offset++; // increment to next data buffer position
    if (offset >= count) // no more data
    {
      if (value2Value > 0) // last record fully parsed without trailing NL
      {
        if (!updateDone)
        {
          value2Value = toInt(alphaDigits, numDigits);
          Update( keyValue, value1Value, value2Value );
          updateDone = true;
        }
      }
      break; // exit loop
    }

  } // end loop

  return;

} // end Parse


BOOL StartApp()
// Treat the opened the LearnToCodeGR file.
{

  char* filePath;
  filePath = obtainFilePath();

  FILE* file;
  char buffer[30];
  file = fopen(filePath, "r");

  int currPos;
  int lastPos = 0;
  int length;

  if(file == NULL)
  {
    perror("Error opening file");
    return FALSE;
  }
  else
  { // Read file records until end-of-file

    // Read each record/line of the file and Parse it.
    while (!feof(file))
    {
      fgets(buffer, 30, (FILE*)file);
      currPos = ftell(file);
      length = currPos - lastPos; // number of characters read into buffer
      lastPos = currPos;
      for (int i=0; i < length; i++) // output the
        printf("%c ", buffer[i]);    //   record to
      printf("\n");                  //   the console

      // Parse the line and update to retain all the necessary data.
      Parse(length, buffer);
    }

    // Close the selected file.
    fclose(file);

    // Report the results.
    Report();

  }
  return TRUE;

} // end StartApp