#!/usr/bin/perl -w
#
#Example of JSON, objects and Tie for storage

use strict; #we covered warnings with the -w in the shebang line
use feature "say";  #so we can use say

use experimental qw(signatures); #makes the subs easier for newbies

my $thereAre = People->size(); #class method to get size of the DB
say "There are $thereAre records";

if ($thereAre) { #the first time there will not be any
    print "Record to show ? ";
    my $recN = <<>>; #get from kbd or file
    chomp $recN;    #remove the end of line
    if ($recN =~ /\D/a or $recN !~ /\d/a) {	#a non digit
        say "Not a valid record number - digits only";
    }
    elsif ($recN > $thereAre or $recN < 1) {
        say "Invalid record number";
    }
    else { #appears to be a real record number
        my $person = People->loadData($recN);  #read it
        #NOTE: loadData is an alternate constructor that
        #   makes an object of type People and pre-loads
        #   it from the DB
        $person->show(); #and show it
    }
}

#now add one
print "Enter last, first of person to add\n? ";
my $name = <<>>;
chomp $name;		#remove the end of line
my @lAndF = split(/,\s*/,$name); #separate and remove separator
if (@lAndF == 2) {	#there is a last and first found
    my $person1 = People->new(@lAndF); #create a new one
    while (1) {	#till he says no more children
        print "Child's name (empty to end) ? ";
        my $child = <<>>;
        chomp $child;
        if (!$child) {last} #no more
        $person1->addChild($child); 
    }
    say "Saved as record " , $person1->saveIt();
}
else {
    say "Could not recognize as last, first";
}


exit;

#######################################################################
### Usually our class would be in a separate module, we include it here
#   since this is merely demo, unlikely to be re-used and it is easier
#   to read as an example if all in one file

package People;	#our class name

use JSON::PP;	#JSON PP is the Pure Perl implementation for JSON

use Tie::File;  #each saved record will be a line in the file
    #REMEMBER:  Tie::File is smart enough that it does NOT need to
    #   bring in the entire file and so can operate with huge files


sub new($class,$last='None',$first='None'){ #Constructor for the object
    my $self = {};          #where this object will store data, a hash
    $self->{first} = "\L\u$first"; #and initialize it and set case
    $self->{last} = "\L\u$last"; #first char upper and rest lower case
    $self->{children} = []; #none yet.  An empty array
    bless $self,$class;     #this is to be an object of the class
    return $self;           #and return the object
}

sub loadData($class,$recN) { #called almost as an 
                            # "alternative constructor"
    # Called as a class method, not an object method.
    # Because we call it as a class method we do get class passed in
    my $fd = tie my @data,"Tie::File","personData.dat"; #where we 
                            # load from
    $fd->flock();           #so the DB is OURS for a moment
    my $self = decode_json($data[$recN]); #load the hash from file
                            # the decode converts the string back to the
                            # structure (hash & array) that was encoded
    bless $self,$class;     #this is to be an object of the class
    return $self;           #return what we loaded
}

sub asJson {    #convert this object to a JSON string
    my $self = shift;
    return encode_json({%$self}); #the function returns a string that 
                # is the JSON encoded data making up the hash or array 
                # or object that is passed as a reference.
}               # The {} ensures that it is a reference to hash
                #NOTE: encode_json GUARANTEES that the generated
                #    text will be a single (sometimes long) line
                #   It also handles UTF-8 for us so we can safely
                #   use extended character sets etc

sub saveIt ($self) { #save the object as the next record
    my $fd = tie my @data,"Tie::File","personData.dat"; #tie the file
    $fd->flock();   #so it is OURS for a moment - the tie will be 
                    # untied and the lock released when we exit this 
                    # lexical scope, that is exit this block
    if (!@data) {push(@data,"#line 0 not used for data")} 
                    # so line == rec#
                    #the first time the file was empty, so we
                    # created an extra line there
    push(@data,asJson($self)); #add it - remember we tied the array 
                    # to file
    return $#data;  #and tell him where saved
}

sub addChild($self,$childName='') {
    if ($childName) { #we do need a name
        push(@{$self->{children}},"\L\u$childName"); #add it to array
    }
}

sub show { #show the contents of this object
    my $self = shift;
    my @children = @{$self->{children}}; #what children if any
    my $n = @children;
    print "$self->{first} $self->{last} has $n child";
    if ($n > 0) {
        if ($n > 1) {print "ren"} #more than one
        print ", they are:";
        for (my $i=0;$i<$n;$i++) {
        print "\n  $self->{children}[$i]";
        }
    }
    else {print "ren"}  #for no children
    print "\n";
}

sub size { #this is a class method and returns the number of records
    my $fd = tie my @data,"Tie::File","personData.dat"; #load from here
    if ($#data < 0) {return 0} #there are NONE  -1 when empty
    return $#data; #last element is size since we don't use  0
}
__END__
