package Moxy;
use strict;
use warnings;
require Class::Accessor::Fast;
use base qw/Class::Accessor::Fast/;

our $VERSION = '0.05';

__PACKAGE__->mk_accessors(qw/config/);

use Path::Class;
use YAML;
use Encode;
use FindBin;
use UNIVERSAL::require;
use Carp;
use Log::Dispatch;
my $TERM_ANSICOLOR_ENABLED = eval { use Term::ANSIColor; 1; };

sub new {
    my ($class, $config) = @_;

    my $self = bless { config => $config, }, $class;

    $self->{logger} = Log::Dispatch->new;

    $self->_init_server;

    $self->_load_plugins;

    $self->_init_ua_info;

    $self->_init_storage;

    $self->_init_logger;

    return $self;
}

sub run {
    my $self = shift;

    $self->{server}->run($self);
}

sub _load_plugins {
    my $self = shift;

    for my $plugin (@{$self->config->{plugins}}) {
        $self->log(debug => "load plugin: $plugin->{module}");

        my $module = "Moxy::Plugin::" . $plugin->{module};
        $module->require or die $@;
        $module->register($self);
    }
}

sub assets_path {
    my $self = shift;

    return $self->{__assets_path} ||= do {
        $self->config->{global}->{assets_path}
            || dir( $FindBin::RealBin, 'assets' )->stringify;
    };
}

# -------------------------------------------------------------------------

sub ua_list {
    my $self = shift;
    return $self->{__ua_list} ||= YAML::LoadFile( file( $self->assets_path, qw/common useragent.yaml/)->stringify );
}

sub _init_ua_info {
    my $self = shift;

    my $ua_hash;
    for my $agents (values %{$self->ua_list}) {
        for my $ua (@{$agents}) {
            $ua_hash->{$ua->{agent}} = $ua;
        }
    }
    $self->{__ua_hash} = $ua_hash;
}

sub get_ua_info {
    my ($self, $ua) = @_;

    return $self->{__ua_hash}->{$ua||''};
}

# -------------------------------------------------------------------------

sub _init_server {
    my $self = shift;

    my $conf = $self->{config}->{global}->{server};

    my $proto = $conf->{module} ? "Moxy::Server::$conf->{module}" : "Moxy::Server::HTTPProxy";

    $self->log(debug => "SETUP $proto");

    $proto->use or die $@;
    my $server = $proto->new($self, $conf);
    $self->{server} = $server;
}

# -------------------------------------------------------------------------

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

    my $mod = $self->{config}->{global}->{storage}->{module};
       $mod = $mod ? "Moxy::Storage::$mod" : 'Moxy::Storage::DBM_File';
    $mod->use or die $@;
    $self->{storage} = $mod->new($self, $self->{config}->{global}->{storage} || {});
}

sub storage { shift->{storage} }

# -------------------------------------------------------------------------

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

    for my $target (@{$self->config->{global}->{log}->{targets}}) {
        $target->{module}->use or die $@;
        $self->{logger}->add( $target->{module}->new( %{ $target->{conf} } ) );
    }
}

sub log {
    my ($self, $level, $msg, %opt) = @_;

    # hack to get the original caller as Plugin or Server
    my $caller = $opt{caller};
    unless ($caller) {
        my $i = 0;
        while (my $c = caller($i++)) {
            last if $c !~ /Plugin|Server/;
            $caller = $c;
        }
        $caller ||= caller(0);
    }

    chomp($msg);
    if ( $self->config->{global}->{log}->{encoding} ) {
        $msg = Encode::decode_utf8($msg) unless utf8::is_utf8($msg);
        $msg = Encode::encode( $self->config->{global}->{log}->{encoding}, $msg );
    }

    $self->{logger}->log(level => $level, message => "$caller [$level] $msg\n");
}

# -------------------------------------------------------------------------

sub register_hook {
    my ($self, @hooks) = @_;

    while ( my ( $hook, $callback ) = splice( @hooks, 0, 2 ) ) {
        croak "invalid args for register_hook" unless ref $callback eq 'CODE';

        push @{ $self->{hooks}->{$hook} }, $callback;
    }
}

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

    $self->log(debug => "Run hook: $hook");
    for my $action (@{$self->{hooks}->{$hook}}) {
        $action->($self, @args);
    }
}

sub get_hooks {
    my ($self, $hook) = @_;

    my $hooks = $self->{hooks}->{$hook};
    return unless $hooks;
    return wantarray ? @$hooks : $hooks;
}

1;
__END__

=head1 NAME

Moxy - Mobile web development proxy

=head1 DESCRIPTION

Moxy is a mobile web development proxy.

=head1 AUTHOR

    Kan Fushihara
    Tokuhiro Matsuno

=head1 SEE ALSO

L<http://coderepos.org/share/wiki/ssb>
