// Use ItemProp.nui // ITEM_PROPERTY command for use within arrays

Use Strings.nui  // String manipulation for VDF
Use Set.utl      // cArray, cSet and cStack classes
Use Array.nui    // Item_Property command (No User Interface)

// ************************************************
// From here on down to the last couple of lines
// is just one big private interface
// ************************************************

function SortValue_Number global number lnValue returns string
  integer lbNeg
  string lsValue
  move (lnValue<0) to lbNeg
  if lbNeg move (abs(lnValue)) to lnValue
  get NumToStrR lnValue 8 23 to lsValue
  // We use blank (for minus) and plus to make it sort right
  // (and we need to move the sign to the end to make it work)
  if lbNeg begin
    get String_NegateSortOrder lsValue to lsValue
    move (" "+lsValue) to lsValue
  end
  else     move ("+"+lsValue) to lsValue
  function_return lsValue
end_function
function SortValue_Real global real lrValue returns string
  string lsSortValue lsExponent
  move lrValue to lsSortValue
  move (ExtractWord(lsSortValue,"e",2)) to lsExponent
  if lsExponent ne "" replace ("e"+lsExponent) in lsSortValue with ""
  else move "    " to lsSortValue
  function_return (append(lsExponent,lsSortValue))
end_function
function SortValue_Date global date ldValue returns string
  function_return (NumToStrR(integer(ldValue),0,6))
end_function
function SortValue_Integer global integer liValue returns string
  integer lbNeg
  string lsValue
  move (liValue<0) to lbNeg
  if lbNeg move (abs(liValue)) to liValue
  get NumToStrR liValue 0 10 to lsValue
  // We use blank (for minus) and plus to make it sort right
  // (and we need to move the sign to the end to make it work)
  if lbNeg begin
    get String_NegateSortOrder lsValue to lsValue
    move (" "+lsValue) to lsValue
  end
  else     move ("+"+lsValue) to lsValue
  function_return lsValue
end_function

function SortValueAuto global integer liType string lsValue returns string
  if liType eq ITMP_INTEGER function_return (SortValue_Integer(lsValue))
  if liType eq ITMP_STRING  function_return (lsValue+" ")
  if liType eq ITMP_REAL    function_return (SortValue_Real(lsValue))
  if liType eq ITMP_NUMBER  function_return (SortValue_Number(lsValue))
  if liType eq ITMP_DATE    function_return (SortValue_Date(lsValue))
end_function

function SortValue_ToNumber global string lsValue returns number
  function_return (number(trim(lsValue)))
end_function
function SortValue_ToReal global string lsValue returns real
  string lsExponent
  move (left(lsValue,4)) to lsExponent
  replace lsExponent in lsValue with ""
  if lsExponent eq "" function_return (real(lsValue))
  function_return (real(lsValue+"e"+lsExponent))
end_function
function SortValue_ToDate global string lsValue returns date
  function_return (date(trim(lsValue)))
end_function
function SortValue_ToInteger global string lsValue returns integer
  function_return (integer(trim(lsValue)))
end_function

desktop_section
  object ITMP_DataArray is a cArray
  end_object
  object ITMP_SortArray is a cArray
  end_object
  object ITMP_SortSegments is a cArray
    property integer pbDescending public 0
    item_property_list // See? It's taking its own medicine!
      item_property integer piColumn.i
      item_property integer piType.i
      item_property integer pbUppercase.i
    end_item_property_list
    register_function item_property_type integer liColumn returns integer
    procedure DoColumnTypes integer lhObj
      integer liMax liRow
      get row_count to liMax
      decrement liMax
      for liRow from 0 to liMax
        set piType.i liRow to (item_property_type(lhObj,piColumn.i(self,liRow)))
      loop
    end_procedure
    procedure DoSortData integer lhObj
      integer lhData lhSort liColumnCount liDataRowCount liDataRow
      integer liColumn liType liRow liMaxSegment liSegment lbUppercase
      string lsSortValue
      move (ITMP_DataArray(self)) to lhData
      move (ITMP_SortArray(self)) to lhSort
      send delete_data to lhData
      send delete_data to lhSort

      send DoColumnTypes lhObj

      get column_count of lhObj to liColumnCount
      send Clone_Array lhObj lhData

      get row_count of lhObj to liDataRowCount
      decrement liDataRowCount

      if (row_count(self)=1) begin // Fast
        get piColumn.i 0 to liColumn
        get piType.i 0 to liType
        get pbUppercase.i 0 to lbUppercase
        for liRow from 0 to liDataRowCount
          get SortValueAuto liType (value(lhObj,liRow*liColumnCount+liColumn)) to lsSortValue
          if lbUppercase move (uppercase(lsSortValue)) to lsSortValue
          set value of lhSort item liRow to (lsSortValue+pad(string(liRow),10))
        loop
      end
      else begin // Not so fast
        get row_count to liMaxSegment
        decrement liMaxSegment
        for liRow from 0 to liDataRowCount
          move "" to lsSortValue
          for liSegment from 0 to liMaxSegment
            get piColumn.i liSegment to liColumn
            get piType.i liSegment to liType
            get pbUppercase.i liSegment to lbUppercase
            if lbUppercase ;
              move (lsSortValue+uppercase(SortValueAuto(liType,value(lhObj,liRow*liColumnCount+liColumn)))) to lsSortValue
            else ;
              move (lsSortValue+SortValueAuto(liType,value(lhObj,liRow*liColumnCount+liColumn))) to lsSortValue
          loop
          set value of lhSort item liRow to (lsSortValue+pad(string(liRow),10))
        loop
      end

      if (pbDescending(self)) send sort_items to lhSort DESCENDING
      else send sort_items to lhSort
      set pbDescending to DFFALSE

      // Put the data back:
      for liRow from 0 to liDataRowCount
        get value of lhSort item liRow to lsSortValue
        move (right(lsSortValue,10)) to liDataRow
        for liColumn from 0 to (liColumnCount-1)
          set value of lhObj item (liRow*liColumnCount+liColumn) to (value(lhData,liDataRow*liColumnCount+liColumn))
        loop
      loop
      send delete_data to lhData
      send delete_data to lhSort
    end_procedure
  end_object
end_desktop_section

procedure ITMP_Sort_DoReset global
  send delete_data to (ITMP_SortSegments(self))
  set pbDescending of (ITMP_SortSegments(self)) to DFFALSE
end_procedure
procedure ITMP_Sort_DoAddSegment global integer liColumn integer lbUppercase
  integer lbTemp liRow
  if (NUM_ARGUMENTS>1) move lbUppercase to lbTemp
  else move DFFALSE to lbTemp
  get row_count of (ITMP_SortSegments(self)) to liRow
  set piColumn.i of (ITMP_SortSegments(self)) liRow to liColumn
  set pbUppercase.i of (ITMP_SortSegments(self)) liRow to lbTemp
end_procedure
procedure ITMP_Sort_DoSortData global integer lhObj
  send DoSortData to (ITMP_SortSegments(self)) lhObj
end_procedure

// **************************
// Last couple of lines:
// **************************

// To sort the rows of an item_property array by the value
// of the first subsequently the third "column",
//
//  send sort_rows of oMyArray 0 2
//
procedure sort_rows for Array integer liTemp // Actually takes a variable count of parameters
  integer liArg liColumn
  send ITMP_Sort_DoReset
  for liArg from 1 to num_arguments
    move liArg& to liColumn // tricky way to parse passed arguments
    send ITMP_Sort_DoAddSegment liColumn
  loop
  send ITMP_Sort_DoSortData self
end_procedure

// Does the same as the one above, but sorts the rows of the array in
// descending order:
procedure sort_rows_descending for Array integer liTemp // Variable parameter count
  integer liArg liColumn
  send ITMP_Sort_DoReset
  set pbDescending of (ITMP_SortSegments(self)) to DFTRUE
  for liArg from 1 to num_arguments
    move liArg& to liColumn // tricky way to parse passed arguments
    send ITMP_Sort_DoAddSegment liColumn
  loop
  send ITMP_Sort_DoSortData self
end_procedure