package Venus::Log;

use 5.018;

use strict;
use warnings;

use Venus::Class 'attr', 'base', 'with';

base 'Venus::Kind::Utility';

with 'Venus::Role::Buildable';

# ATTRIBUTES

attr 'handler';
attr 'level';
attr 'separator';

# STATE

state $NAME = {trace => 1, debug => 2, info => 3, warn => 4, error => 5, fatal => 6};
state $CODE = {reverse %$NAME};

# BUILDERS

sub build_arg {
  my ($self, $data) = @_;

  return {
    level => $self->level_name($data) || $self->level_name(1),
  };
}

sub build_args {
  my ($self, $data) = @_;

  $data->{level} ||= $self->level_name(1);

  return $data;
}

sub build_self {
  my ($self, $data) = @_;

  $self->handler(sub{CORE::print(STDOUT @_, "\n")}) if !$self->handler;
  $self->separator(" ") if !$self->separator;

  return $self;
}

# METHODS

sub commit {
  my ($self, $level, @args) = @_;

  my $req_level = $self->level_code($level);
  my $set_level = $self->level_code($self->level);

  return ($req_level && $set_level && ($req_level >= $set_level))
    ? $self->write($self->output($self->input(@args)))
    : $self;
}

sub debug {
  my ($self, @args) = @_;

  return $self->commit('debug', @args);
}

sub error {
  my ($self, @args) = @_;

  return $self->commit('error', @args);
}

sub fatal {
  my ($self, @args) = @_;

  return $self->commit('fatal', @args);
}

sub info {
  my ($self, @args) = @_;

  return $self->commit('info', @args);
}

sub input {
  my ($self, @args) = @_;

  return (@args);
}

sub level_code {
  my ($self, $data) = @_;

  $data = $data ? lc $data : $self->level;

  return $$NAME{$data} || ($$CODE{$data} && $$NAME{$$CODE{$data}});
}

sub level_name {
  my ($self, $data) = @_;

  $data = $data ? lc $data : $self->level;

  return $$CODE{$data} || ($$NAME{$data} && $$CODE{$$NAME{$data}});
}

sub output {
  my ($self, @args) = @_;

  return (join $self->separator, map $self->string($_), @args);
}

sub string {
  my ($self, $data) = @_;

  require Scalar::Util;

  if (!defined $data) {
    return '';
  }

  my $blessed = Scalar::Util::blessed($data);
  my $isvenus = $blessed && $data->isa('Venus::Core') && $data->can('does');

  if (!$blessed && !ref $data) {
    return $data;
  }
  if ($blessed && ref($data) eq 'Regexp') {
    return "$data";
  }
  if ($isvenus && $data->does('Venus::Role::Explainable')) {
    return $self->dump(sub{$data->explain});
  }
  if ($isvenus && $data->does('Venus::Role::Valuable')) {
    return $self->dump(sub{$data->value});
  }
  if ($isvenus && $data->does('Venus::Role::Dumpable')) {
    return $data->dump;
  }
  if ($blessed && overload::Method($data, '""')) {
    return "$data";
  }
  if ($blessed && $data->can('as_string')) {
    return $data->as_string;
  }
  if ($blessed && $data->can('to_string')) {
    return $data->to_string;
  }
  if ($blessed && $data->isa('Venus::Kind')) {
    return $data->stringified;
  }
  else {
    return $self->dump(sub{$data});
  }
}

sub trace {
  my ($self, @args) = @_;

  return $self->commit('trace', @args);
}

sub warn {
  my ($self, @args) = @_;

  return $self->commit('warn', @args);
}

sub write {
  my ($self, @args) = @_;

  $self->handler->(@args);

  return $self;
}

1;



=head1 NAME

Venus::Log - Log Class

=cut

=head1 ABSTRACT

Log Class for Perl 5

=cut

=head1 SYNOPSIS

  package main;

  use Venus::Log;

  my $log = Venus::Log->new;

  # $log->trace(time, 'Something failed!');

  # "0000000000 Something failed!"

  # $log->error(time, 'Something failed!');

  # "0000000000 Something failed!"

=cut

=head1 DESCRIPTION

This package provides methods for logging information using various log levels.

=cut

=head1 ATTRIBUTES

This package has the following attributes:

=cut

=head2 handler

  handler(CodeRef $code) (CodeRef)

The handler attribute holds the callback that handles logging.

I<Since C<1.68>>

=over 4

=item handler example 1

  # given: synopsis

  package main;

  my $handler = $log->handler;

  my $events = [];

  $handler = $log->handler(sub{push @$events, [@_]});

=back

=cut

=head2 level

  level(Str $name) (Str)

The level attribute holds the current log level.

I<Since C<1.68>>

=over 4

=item level example 1

  # given: synopsis

  package main;

  my $level = $log->level;

  # "trace"

  $level = $log->level('fatal');

  # "fatal"

=back

=cut

=head2 separator

  separator(Any $data) (Any)

The separator attribute holds the value used to join multiple log message arguments.

I<Since C<1.68>>

=over 4

=item separator example 1

  # given: synopsis

  package main;

  my $separator = $log->separator;

  # ""

  $separator = $log->separator("\n");

  # "\n"

=back

=cut

=head1 INHERITS

This package inherits behaviors from:

L<Venus::Kind::Utility>

=cut

=head1 INTEGRATES

This package integrates behaviors from:

L<Venus::Role::Buildable>

=cut

=head1 METHODS

This package provides the following methods:

=cut

=head2 debug

  debug(Str @data) (Log)

The debug method logs C<debug> information and returns the invocant.

I<Since C<1.68>>

=over 4

=item debug example 1

  # given: synopsis

  package main;

  # $log = $log->debug(time, 'Something failed!');

  # "0000000000 Something failed!"

=back

=over 4

=item debug example 2

  # given: synopsis

  package main;

  # $log->level('info');

  # $log = $log->debug(time, 'Something failed!');

  # noop

=back

=cut

=head2 error

  error(Str @data) (Log)

The error method logs C<error> information and returns the invocant.

I<Since C<1.68>>

=over 4

=item error example 1

  # given: synopsis

  package main;

  # $log = $log->error(time, 'Something failed!');

  # "0000000000 Something failed!"

=back

=over 4

=item error example 2

  # given: synopsis

  package main;

  # $log->level('fatal');

  # $log = $log->error(time, 'Something failed!');

  # noop

=back

=cut

=head2 fatal

  fatal(Str @data) (Log)

The fatal method logs C<fatal> information and returns the invocant.

I<Since C<1.68>>

=over 4

=item fatal example 1

  # given: synopsis

  package main;

  # $log = $log->fatal(time, 'Something failed!');

  # "0000000000 Something failed!"

=back

=over 4

=item fatal example 2

  # given: synopsis

  package main;

  # $log->level('unknown');

  # $log = $log->fatal(time, 'Something failed!');

  # noop

=back

=cut

=head2 info

  info(Str @data) (Log)

The info method logs C<info> information and returns the invocant.

I<Since C<1.68>>

=over 4

=item info example 1

  # given: synopsis

  package main;

  # $log = $log->info(time, 'Something failed!');

  # "0000000000 Something failed!"

=back

=over 4

=item info example 2

  # given: synopsis

  package main;

  # $log->level('warn');

  # $log = $log->info(time, 'Something failed!');

  # noop

=back

=cut

=head2 input

  input(Str @data) (Str)

The input method returns the arguments provided to the log level methods, to
the L</output>, and can be overridden by subclasses.

I<Since C<1.68>>

=over 4

=item input example 1

  # given: synopsis

  package main;

  my @input = $log->input(1, 'Something failed!');

  # (1, 'Something failed!')

=back

=cut

=head2 output

  output(Str @data) (Str)

The output method returns the arguments returned by the L</input> method, to
the log handler, and can be overridden by subclasses.

I<Since C<1.68>>

=over 4

=item output example 1

  # given: synopsis

  package main;

  my $output = $log->output(time, 'Something failed!');

  # "0000000000 Something failed!"

=back

=cut

=head2 string

  string(Any $data) (Str)

The string method returns a stringified representation of any argument provided
and is used by the L</output> method.

I<Since C<1.68>>

=over 4

=item string example 1

  # given: synopsis

  package main;

  my $string = $log->string;

  # ""

=back

=over 4

=item string example 2

  # given: synopsis

  package main;

  my $string = $log->string('Something failed!');

  # "Something failed!"

=back

=over 4

=item string example 3

  # given: synopsis

  package main;

  my $string = $log->string([1,2,3]);

  # [1,2,3]

=back

=over 4

=item string example 4

  # given: synopsis

  package main;

  my $string = $log->string(bless({}));

  # "bless({}, 'main')"

=back

=cut

=head2 trace

  trace(Str @data) (Log)

The trace method logs C<trace> information and returns the invocant.

I<Since C<1.68>>

=over 4

=item trace example 1

  # given: synopsis

  package main;

  # $log = $log->trace(time, 'Something failed!');

  # "0000000000 Something failed!"

=back

=over 4

=item trace example 2

  # given: synopsis

  package main;

  # $log->level('debug');

  # $log = $log->trace(time, 'Something failed!');

  # noop

=back

=cut

=head2 warn

  warn(Str @data) (Log)

The warn method logs C<warn> information and returns the invocant.

I<Since C<1.68>>

=over 4

=item warn example 1

  # given: synopsis

  package main;

  # $log = $log->warn(time, 'Something failed!');

  # "0000000000 Something failed!"

=back

=over 4

=item warn example 2

  # given: synopsis

  package main;

  # $log->level('error');

  # $log = $log->warn(time, 'Something failed!');

  # noop

=back

=cut

=head2 write

  write(Any @data) (Log)

The write method invokes the log handler, i.e. L</handler>, and returns the invocant.

I<Since C<1.68>>

=over 4

=item write example 1

  # given: synopsis

  package main;

  # $log = $log->write(time, 'Something failed!');

  # bless(..., "Venus::Log")

=back

=cut