unit uDataSetGenerico;
{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

interface
uses
  Classes,
  ufields,
  sysutils;

type
  TDescripcionDeCampo = record
    nombre, tipo: string;
  end;
  TDescripcionDeCampos = array of TDescripcionDeCampo;

  TDataSetGenerico = class;

  { TDataRecord }

  TDataRecord = class
    private
      fielsLst: TList;
    public
      padre: TDataSetGenerico;
      valores: array of ansiString;
      freePadre: boolean;

      constructor Create( padre: TDataSetGenerico );
      procedure SetValById( id: integer; val: string );
      function GetByIdAsString( id: integer ): ansistring;
      function GetByNameAsString( nombre: string ): ansistring;
      function GetByNameAsFloat(nombre: string; defVal: double=0): double;
      function GetByNameAsInt(nombre: string; defVal: integer=0): integer;
      function GetByNameAsBoolean( nombre: string ): boolean;
      procedure Free;

      function ToString: ansistring; override;

      property valor[i: Integer]: ansiString read GetByIdAsString write SetValById; default;

      function GetField( i: integer ): TField;
      function FieldByName( nombre: string ): TField;
      property fields[i: integer]: TField Read GetField;
  end;

  TDataRecords = class( TList )
    padre: TDataSetGenerico;
    constructor Create( padre: TDataSetGenerico );
    function GetFieldRec( i: integer ): TDataRecord;
    property filas[i: Integer]: TDataRecord read GetFieldRec; default;
    procedure Free;
  end;

  TFieldListEmulator = class
    padre: TDataSetGenerico;
    constructor Create( padre: TDataSetGenerico );
    function count: integer;
    function GetField( i: integer ): TField;
    property fields[i: integer]: TField Read GetField; default;
  end;

  { TDataSetGenerico }

  TDataSetGenerico = class
    private
      kFichaActiva: integer;
    public
      nrows, nfields: Integer;
      descripcionDeCampos: TDescripcionDeCampos;
      filas_: TDataRecords;
      fields: TFieldListEmulator;

      constructor Create(nrows, nfields: Integer);

      function first: TDataRecord;
      function next: TDataRecord;
      function go(kRec: integer ): TDataRecord;

      function FieldName( kField: integer ): string;
      function FieldType( kField: integer ): string;
      function kOfField( nombre: string ): integer; // -1 si no existe
      function eof: boolean;
      destructor destroy; override;

      function getFila(i: Integer): TDataRecord;
      procedure setFila(i: Integer; fila: TDataRecord);

      property filas[i: Integer]: TDataRecord read getFila write setFila;
      function GetField( i: integer ): TField;
      function FieldByName( nombre: string ): TField;
      function GetFieldVal( i: integer ): string;
      function GetFieldValByName(nombre: string): string;

      function GetDT(kRow: integer; FieldName: string ): TDateTime;
      function GetI(kRow: integer; FieldName: string; defVal: integer=0): integer;
      function GetF(kRow: integer; FieldName: string; defVal: double=0.0): double;

  end;

  TCeldas = array of array of String;
  TDataSetStringGrid = class(TDataSetGenerico)
    public
      //Si nCampos es -1 se toma el largo de encabezados como nFields
      Constructor Create(encabezados: array of String; nFilas: Integer; nCampos: Integer = -1); reintroduce; overload;
      //
      Constructor Create(encabezados: array of String; celdas: TCeldas); reintroduce; overload;
  end;

implementation

//----------------------
//Métodos de TDataRecord
//----------------------

constructor TDataRecord.Create( padre: TDataSetGenerico );
begin
  inherited Create();
  self.padre:= padre;
  self.freePadre:= false; // por defecto las fichas no liberan al papa
  SetLength(valores, padre.nfields);
  self.fielsLst := TList.Create;
end;

procedure TDataRecord.SetValById( id: integer; val: string );
begin
  valores[id]:= val;
end;

function TDataRecord.GetByIdAsString( id: integer ): ansistring;
begin
  result:= valores[id];
end;

function TDataRecord.GetByNameAsString( nombre: string ): ansistring;
var
  k: integer;
begin
  k:= padre.kOfField(nombre);
  if k < 0 then
     raise Exception.Create('Error, GetByNameAsString, campo no conocido: '+nombre );
  result:= valores[k];
end;

function TDataRecord.GetByNameAsFloat( nombre: string; defVal: double = 0 ): double;
var
  k: integer;
  res: double;
  rescode: integer;
begin
  k:= padre.kOfField(nombre);
  if k < 0 then
     raise Exception.Create('Error, GetByNameAsFloat, campo no conocido: '+nombre );
  val( valores[k], res, rescode );
  if rescode <> 0 then
    result:= defVal
  else
    result:= res;
end;

function TDataRecord.GetByNameAsInt( nombre: string; defVal: integer = 0 ): integer;
var
  k: integer;
  res: integer;
  rescode: integer;
begin
  k:= padre.kOfField(nombre);
  if k < 0 then
     raise Exception.Create('Error, GetByNameAsInt, campo no conocido: '+nombre );
  val( valores[k], res, rescode );
  if rescode <> 0 then
    result:= defVal
  else
    result:= res;
end;

function TDataRecord.GetByNameAsBoolean( nombre: string ): boolean;
var
  s: string;
  c: char;
begin
  s:=  GetByNameAsString( nombre );
  if s  = '' then
  begin
    result:= false;
    exit;
  end;
  c:= LowerCase( s[1] );
  result:= ( c = '1') or  (c = 't' ) or ( c = 'v' );

end;

function TDataRecord.GetField( i: integer ): TField;
begin
  result:= TField.Create( valores[i]);
  fielsLst.Add(Result);
end;

function TDataRecord.FieldByName( nombre: string ): TField;
var
  k: integer;
begin
  k:= padre.kOfField( nombre );
  if k < 0 then
    raise Exception.Create( 'Error, FieldByName, campo no conocido: '+ nombre );
  result:= GetField( k );
end;



procedure TDataRecord.Free;
var
  i: Integer;
begin

  for i:=0 to fielsLst.Count-1 do
    TField(fielsLst[i]).Free;

  fielsLst.Free;

  if freePadre  then
    Padre.Free;

  SetLength(valores, 0);
  inherited Free;
end;

function TDataRecord.ToString: ansistring;
var
  i: Integer;
  res: string;
begin
  res:= '';
  for i:=Low(self.valores) to High(self.valores) do
    res:=res + self.valor[i] + ', ';
  result:= res;
end;

//---------------------------
//Métodos de TDataSetGenerico
//---------------------------

constructor TDataSetGenerico.Create(nrows, nfields: Integer);
begin
  inherited Create;
  self.nrows:= nrows;
  self.nfields:= nfields;
  SetLength(descripcionDeCampos, nfields);
  filas_:= TDataRecords.Create( self );
  fields:= TFieldListEmulator.Create( self );
  kFichaActiva:= -1;
end;

function TDataSetGenerico.first: TDataRecord;
begin
  kFichaActiva:= 0;
  if nrows > 0 then
    result:= filas_[0]
  else
    result:= nil;
end;

function TDataSetGenerico.next: TDataRecord;
begin
  kFichaActiva:= kFichaActiva + 1;
  if kFichaActiva < filas_.Count then
    result:= filas_[kFichaActiva]
  else
  begin
    kFichaActiva:= filas_.count;
    result:= NIL;
  end;
end;

function TDataSetGenerico.go(kRec: integer ): TDataRecord;
begin
  if ( kRec >= 0 ) and ( kRec < filas_.count ) then
  begin
    kFichaActiva:= kRec;
    result:= filas_[kRec];
  end
  else
    result:= nil;
end;

function TDataSetGenerico.FieldName( kField: integer ): string;
begin
  result:= self.descripcionDeCampos[kField].nombre;
end;

function TDataSetGenerico.FieldType( kField: integer ): string;
begin
  result:= self.descripcionDeCampos[kField].tipo;
end;

function TDataSetGenerico.kOfField( nombre: string): integer; // -1 si no existe
var
  i, res: Integer;
  sl_nombre: string;
begin
  sl_nombre:= LowerCase( nombre );
  res:= -1;
  for i:= 0 to high(descripcionDeCampos) do
    if descripcionDeCampos[i].nombre = sl_nombre then
    begin
      res:= i;
      break;
    end;
  if res < 0 then
    raise Exception.Create(' No se encuentra el campo: '+Nombre );
  result:= res;
end;

function TDataSetGenerico.eof: boolean;
begin
  if kFichaActiva < 0 then next;
  result:= (kFichaActiva >= filas_.count)
end;

destructor TDataSetGenerico.destroy;
begin
  SetLength(descripcionDeCampos, 0);
  filas_.Free;
  inherited destroy;
end;

function TDataSetGenerico.getFila(i: Integer): TDataRecord;
begin
  result:= filas_[i];
end;

procedure TDataSetGenerico.setFila(i: Integer; fila: TDataRecord);
begin
  filas_.items[i]:= fila;
end;


function TDataSetGenerico.GetField( i: integer ): TField;
begin
  if kFichaActiva < 0 then next;
  result:= TField.Create( filas[kFichaActiva].valor[i] );
end;

function TDataSetGenerico.FieldByName( nombre: string ): TField;
var
  k: integer;
begin
  k:= kOfField( nombre );
  if k >= 0 then
    result:= GetField( k )
  else
    result:= nil;
end;


function TDataSetGenerico.GetFieldVal( i: integer ): string;
begin
  result:= filas[kFichaActiva].valor[i];
end;

function TDataSetGenerico.GetFieldValByName( nombre: string ): string;
var
  k: integer;
begin
  k:= kOfField( nombre );
  if k >= 0 then
     result:= GetFieldVal( k )
  else
     result:= '';
end;

function TDataSetGenerico.GetDT(kRow: integer; FieldName: string): TDateTime;
var
  kField: integer;
  s: string;
begin
  kField:= kOfField( FieldName );
  if kField >= 0 then
  begin
    s:= trim( filas[kRow].valor[kField] );
    if s <> ' ' then
      result:= StrToDateTime(s)
    else
      result:= 0;
  end
  else
    result:= 0;
end;

function TDataSetGenerico.GetI( kRow: integer; FieldName: string; defVal: integer = 0 ): integer;
var
  kField: integer;
  s: string;
begin
  kField:= kOfField( FieldName );
  if kField >= 0 then
  begin
    s:= trim( filas[kRow].valor[kField] );
    if s <> '' then
      result:= StrToInt( s )
    else
      result:= defVal;
  end
  else
    raise Exception.Create('Nombre de campo desconocido: '+FieldName );
end;

function TDataSetGenerico.GetF(kRow: integer; FieldName: string; defVal: double = 0.0): double;
var
  kField: integer;
  s: string;
begin
  kField:= kOfField( FieldName );
  if kField >= 0 then
  begin
    s:= trim( filas[kRow].valor[kField] );
    if s <> '' then
      result:= StrToFloat( s )
    else
      result:= defVal;
  end
  else
    raise Exception.Create('Nombre de campo desconocido: '+FieldName );
end;


//-------------------
// Métodos de TDataRecors
//-------------------------
constructor TDataRecords.Create( padre: TDataSetGenerico );
var
  k: integer;
begin
  inherited Create;
  self.Padre:= padre;
  for k:= 0 to padre.nrows-1 do
    add( TDataRecord.Create( padre ));
end;

function TDataRecords.GetFieldRec( i: integer ): TDataRecord;
begin
  result:=  items[ i ];
end;

procedure TDataRecords.Free;
var
  i: Integer;
  f: TDataRecord;
begin
  for i:= 0 to count-1 do
  begin
    f:= items[i];
    if not f.freePadre then
        f.Free;
  end;
  inherited Free;
end;

//-----------------------------
//Métodos de TDataSetStringGrid
//-----------------------------

Constructor TDataSetStringGrid.Create(encabezados: array of string; nFilas, nCampos: Integer);
var
  i: Integer;
begin
  if nCampos < 0 then
    inherited Create(nFilas, Length(encabezados))
  else
    inherited Create(nFilas, nCampos);
    
  for i:= 0 to high(encabezados) do
  begin
    descripcionDeCampos[i].nombre:= encabezados[i];
    descripcionDeCampos[i].tipo:= 'string';
  end;
  for i:= length(encabezados) to nfields - 1 do
  begin
    descripcionDeCampos[i].nombre:= '';
    descripcionDeCampos[i].tipo:= 'string';
  end;
end;

Constructor TDataSetStringGrid.Create(encabezados: array of string; celdas: TCeldas);
var
  i, j: Integer;
begin
  inherited Create(Length(celdas), length(encabezados));
  for i:= 0 to nfields - 1 do
  begin
    descripcionDeCampos[i].nombre:= encabezados[i];
    descripcionDeCampos[i].tipo:= 'string';
  end;

  for i:= 0 to nrows - 1 do
  begin
    for j:= 0 to nfields - 1 do
      filas_[i].SetValById(j, celdas[i][j]);
  end;
end;

//*******************************
// Métodos de TFieldListEmulator
//-------------------------------

constructor TFieldListEmulator.Create( padre: TDataSetGenerico );
begin
  inherited Create;
  self.Padre:= padre;
end;

function TFieldListEmulator.count: integer;
begin
  result:= padre.nfields;
end;

function TFieldListEmulator.GetField( i: integer ): TField;
begin
  if padre.kFichaActiva < 0 then
    padre.next;
  result:= padre.filas[ padre.kFichaActiva ].fields[ i ];
end;


end.
