package Zing::Term;

use 5.014;

use strict;
use warnings;

use registry 'Zing::Types';
use routines;

use Data::Object::Class;
use Data::Object::ClassHas;
use Data::Object::Space;

extends 'Zing::Class';

use Scalar::Util ();

use overload '""' => 'string';

our $VERSION = '0.27'; # VERSION

# ATTRIBUTES

has 'handle' => (
  is => 'ro',
  isa => 'Name',
  req => 1,
);

has 'symbol' => (
  is => 'ro',
  isa => 'Name',
  req => 1,
);

has 'bucket' => (
  is => 'ro',
  isa => 'Name',
  req => 1,
);

has 'system' => (
  is => 'ro',
  isa => 'Name',
  req => 1,
);

has 'target' => (
  is => 'ro',
  isa => 'Name',
  req => 1,
);

# BUILDERS

state $symbols = {
  'Zing::Channel'  => 'channel',
  'Zing::Data'     => 'data',
  'Zing::Domain'   => 'domain',
  'Zing::Kernel'   => 'kernel',
  'Zing::KeyVal'   => 'keyval',
  'Zing::Lookup'   => 'lookup',
  'Zing::Mailbox'  => 'mailbox',
  'Zing::Meta'     => 'meta',
  'Zing::Process'  => 'process',
  'Zing::PubSub'   => 'pubsub',
  'Zing::Queue'    => 'queue',
  'Zing::Repo'     => 'repo',
  'Zing::Table'    => 'table',
};

fun BUILDARGS($self, $item, @data) {
  my $args = {};

  if (Scalar::Util::blessed($item)) {
    if ($item->isa('Zing::Data')) {
      $args->{symbol} = $symbols->{'Zing::Data'};
      $args->{bucket} = $item->name;
    }
    elsif ($item->isa('Zing::Lookup')) {
      $args->{symbol} = $symbols->{'Zing::Lookup'};
      $args->{bucket} = $item->name;
    }
    elsif ($item->isa('Zing::Domain')) {
      $args->{symbol} = $symbols->{'Zing::Domain'};
      $args->{bucket} = $item->name;
    }
    elsif ($item->isa('Zing::Table')) {
      $args->{symbol} = $symbols->{'Zing::Table'};
      $args->{bucket} = $item->name;
    }
    elsif ($item->isa('Zing::Channel')) {
      $args->{symbol} = $symbols->{'Zing::Channel'};
      $args->{bucket} = $item->name;
    }
    elsif ($item->isa('Zing::Kernel')) {
      $args->{symbol} = $symbols->{'Zing::Kernel'};
      $args->{bucket} = $item->name;
    }
    elsif ($item->isa('Zing::Mailbox')) {
      $args->{symbol} = $symbols->{'Zing::Mailbox'};
      $args->{bucket} = $item->name;
    }
    elsif ($item->isa('Zing::Process')) {
      $args->{symbol} = $symbols->{'Zing::Process'};
      $args->{bucket} = $item->name;
    }
    elsif ($item->isa('Zing::Queue')) {
      $args->{symbol} = $symbols->{'Zing::Queue'};
      $args->{bucket} = $item->name;
    }
    elsif ($item->isa('Zing::Meta')) {
      $args->{symbol} = $symbols->{'Zing::Meta'};
      $args->{bucket} = $item->name;
    }
    elsif ($item->isa('Zing::KeyVal')) {
      $args->{symbol} = $symbols->{'Zing::KeyVal'};
      $args->{bucket} = $item->name;
    }
    elsif ($item->isa('Zing::PubSub')) {
      $args->{symbol} = $symbols->{'Zing::PubSub'};
      $args->{bucket} = $item->name;
    }
    elsif ($item->isa('Zing::Repo')) {
      $args->{symbol} = $symbols->{'Zing::Repo'};
      $args->{bucket} = $item->name;
    }
    else {
      $self->throw(error_term_unknow_object($item));
    }
    $args->{target} = ($item->env->target || 'global');
    $args->{handle} = ($item->env->handle || 'main');
    $args->{system} = ($item->env->system || 'zing');
  }
  elsif(defined $item && !ref $item) {
    my $schema = [split /:/, "$item", 5];

    my $system = $schema->[0];
    my $handle = $schema->[1];
    my $target = $schema->[2];
    my $symbol = $schema->[3];
    my $bucket = $schema->[4];

    unless (grep {$_ eq $symbol} values %$symbols) {
      $self->throw(error_term_unknow_symbol("$item"));
    }

    $args->{system} = $system;
    $args->{handle} = $handle;
    $args->{target} = $target;
    $args->{symbol} = $symbol;
    $args->{bucket} = $bucket;
  }
  else {
    $self->throw(error_term_unknown());
  }

  return $args;
}

# METHODS

method channel() {
  unless ($self->symbol eq 'channel') {
    $self->throw(error_term_invalid("channel"));
  }

  return $self->string;
}

method data() {
  unless ($self->symbol eq 'data') {
    $self->throw(error_term_invalid("data"));
  }

  return $self->string;
}

method domain() {
  unless ($self->symbol eq 'domain') {
    $self->throw(error_term_invalid("domain"));
  }

  return $self->string;
}

method kernel() {
  unless ($self->symbol eq 'kernel') {
    $self->throw(error_term_invalid("kernel"));
  }

  return $self->string;
}

method keyval() {
  unless ($self->symbol eq 'keyval') {
    $self->throw(error_term_invalid("keyval"));
  }

  return $self->string;
}

method lookup() {
  unless ($self->symbol eq 'lookup') {
    $self->throw(error_term_invalid("lookup"));
  }

  return $self->string;
}

method mailbox() {
  unless ($self->symbol eq 'mailbox') {
    $self->throw(error_term_invalid("mailbox"));
  }

  return $self->string;
}

method meta() {
  unless ($self->symbol eq 'meta') {
    $self->throw(error_term_invalid("meta"));
  }

  return $self->string;
}

method object(Maybe[Env] $env) {
  require Zing::Env;

  $env = Zing::Env->new(
    ($env ? %{$env} : ()),
    handle => $self->handle,
    target => $self->target,
  );

  my $space = Data::Object::Space->new(
    ({reverse %$symbols})->{$self->symbol}
  );

  return $space->build(env => $env, name => $self->bucket);
}

method process() {
  unless ($self->symbol eq 'process') {
    $self->throw(error_term_invalid("process"));
  }

  return $self->string;
}

method pubsub() {
  unless ($self->symbol eq 'pubsub') {
    $self->throw(error_term_invalid("pubsub"));
  }

  return $self->string;
}

method queue() {
  unless ($self->symbol eq 'queue') {
    $self->throw(error_term_invalid("queue"));
  }

  return $self->string;
}

method repo() {
  unless ($self->symbol eq 'repo') {
    $self->throw(error_term_invalid('repo'));
  }

  return $self->string;
}

method string() {
  my $system = $self->system;
  my $handle = $self->handle;
  my $target = $self->target;
  my $symbol = $self->symbol;
  my $bucket = $self->bucket;

  return lc join ':', $system, $handle, $target, $symbol, $bucket;
}

method table() {
  unless ($self->symbol eq 'table') {
    $self->throw(error_term_invalid('table'));
  }

  return $self->string;
}

# ERRORS

fun error_term_invalid(Str $name) {
  code => 'error_term_invalid',
  message => qq(Error in term: not a "$name" term),
}

fun error_term_unknown() {
  code => 'error_term_unknown',
  message => qq(Unrecognizable term (or object) provided),
}

fun error_term_unknow_object(Object $item) {
  code => 'error_term_unknow_object',
  message => qq(Error in term: Unrecognizable "object": $item),
}

fun error_term_unknow_symbol(Str $term) {
  code => 'error_term_unknow_symbol',
  message => qq(Error in term: Unrecognizable "symbol" in: $term),
}

1;



=encoding utf8

=head1 NAME

Zing::Term - Resource Representation

=cut

=head1 ABSTRACT

Resource Representation

=cut

=head1 SYNOPSIS

  use Zing::KeyVal;
  use Zing::Term;

  my $term = Zing::Term->new(Zing::KeyVal->new(name => 'nodes'));

  # $term->keyval;

=cut

=head1 DESCRIPTION

This package provides a mechanism for generating and validating (global and
local) resource identifiers.

=cut

=head1 LIBRARIES

This package uses type constraints from:

L<Zing::Types>

=cut

=head1 ATTRIBUTES

This package has the following attributes:

=cut

=head2 bucket

  bucket(Str)

This attribute is read-only, accepts C<(Str)> values, and is required.

=cut

=head2 handle

  handle(Str)

This attribute is read-only, accepts C<(Str)> values, and is required.

=cut

=head2 symbol

  symbol(Str)

This attribute is read-only, accepts C<(Str)> values, and is required.

=cut

=head2 system

  system(Str)

This attribute is read-only, accepts C<(Str)> values, and is required.

=cut

=head2 target

  target(Str)

This attribute is read-only, accepts C<(Str)> values, and is required.

=cut

=head1 METHODS

This package implements the following methods:

=cut

=head2 channel

  channel() : Str

The channel method validates and returns a "channel" resource identifier.

=over 4

=item channel example #1

  use Zing::Channel;

  Zing::Term->new(Zing::Channel->new(name => 'chat'));

  # $term->channel;

=back

=cut

=head2 data

  data() : Str

The data method validates and returns a "data" resource identifier.

=over 4

=item data example #1

  use Zing::Data;
  use Zing::Process;

  Zing::Term->new(Zing::Data->new(name => '0.0.0.0'));

  # $term->data;

=back

=cut

=head2 domain

  domain() : Str

The domain method validates and returns a "domain" resource identifier.

=over 4

=item domain example #1

  use Zing::Domain;

  Zing::Term->new(Zing::Domain->new(name => 'transaction'));

  # $term->domain;

=back

=cut

=head2 kernel

  kernel() : Str

The kernel method validates and returns a "kernel" resource identifier.

=over 4

=item kernel example #1

  use Zing::Kernel;

  Zing::Term->new(Zing::Kernel->new(scheme => ['MyApp', [], 1]));

  # $term->kernel;

=back

=cut

=head2 keyval

  keyval() : Str

The keyval method validates and returns a "keyval" resource identifier.

=over 4

=item keyval example #1

  use Zing::KeyVal;

  Zing::Term->new(Zing::KeyVal->new(name => 'listeners'));

  # $term->keyval;

=back

=cut

=head2 lookup

  lookup() : Str

The lookup method validates and returns a "lookup" resource identifier.

=over 4

=item lookup example #1

  use Zing::Lookup;

  Zing::Term->new(Zing::Lookup->new(name => 'employees'));

  # $term->lookup;

=back

=cut

=head2 mailbox

  mailbox() : Str

The mailbox method validates and returns a "mailbox" resource identifier.

=over 4

=item mailbox example #1

  use Zing::Mailbox;
  use Zing::Process;

  Zing::Term->new(Zing::Mailbox->new(name => '0.0.0.0'));

  # $term->mailbox;

=back

=cut

=head2 meta

  meta() : Str

The meta method validates and returns a "meta" resource identifier.

=over 4

=item meta example #1

  use Zing::Meta;

  Zing::Term->new(Zing::Meta->new(name => 'random'));

  # $term->meta;

=back

=cut

=head2 object

  object(Maybe[Env] $env) : Object

The object method reifies an object from its resource identifier.

=over 4

=item object example #1

  use Zing::Process;

  my $term = Zing::Term->new(Zing::Process->new);

  $term->object;

=back

=cut

=head2 process

  process() : Str

The process method validates and returns a "process" resource identifier.

=over 4

=item process example #1

  use Zing::Process;

  Zing::Term->new(Zing::Process->new);

  # $term->process;

=back

=cut

=head2 pubsub

  pubsub() : Str

The pubsub method validates and returns a "pubsub" resource identifier.

=over 4

=item pubsub example #1

  use Zing::PubSub;

  Zing::Term->new(Zing::PubSub->new(name => 'operations'));

  # $term->pubsub;

=back

=cut

=head2 queue

  queue() : Str

The queue method validates and returns a "queue" resource identifier.

=over 4

=item queue example #1

  use Zing::Queue;

  Zing::Term->new(Zing::Queue->new(name => 'workflows'));

  # $term->queue;

=back

=cut

=head2 repo

  repo() : Str

The repo method validates and returns a "repo" resource identifier.

=over 4

=item repo example #1

  use Zing::Repo;

  Zing::Term->new(Zing::Repo->new(name => 'miscellaneous'));

  # $term->repo;

=back

=cut

=head2 string

  string() : Str

The string method returns a resource identifier. This method is called
automatically when the object is used as a string.

=over 4

=item string example #1

  # given: synopsis

  $term->string;

=back

=cut

=head1 AUTHOR

Al Newkirk, C<awncorp@cpan.org>

=head1 LICENSE

Copyright (C) 2011-2019, Al Newkirk, et al.

This is free software; you can redistribute it and/or modify it under the terms
of the The Apache License, Version 2.0, as elucidated in the L<"license
file"|https://github.com/cpanery/zing/blob/master/LICENSE>.

=head1 PROJECT

L<Wiki|https://github.com/cpanery/zing/wiki>

L<Project|https://github.com/cpanery/zing>

L<Initiatives|https://github.com/cpanery/zing/projects>

L<Milestones|https://github.com/cpanery/zing/milestones>

L<Contributing|https://github.com/cpanery/zing/blob/master/CONTRIBUTE.md>

L<Issues|https://github.com/cpanery/zing/issues>

=cut