#/*
# *  Copyright 2007 hkrn <hikarin@users.sourceforge.jp>
# *
# *  Licensed under the Apache License, Version 2.0 (the "License");
# *  you may not use this file except in compliance with the License.
# *  You may obtain a copy of the License at
# *
# *      http://www.apache.org/licenses/LICENSE-2.0
# *
# *  Unless required by applicable law or agreed to in writing, software
# *  distributed under the License is distributed on an "AS IS" BASIS,
# *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# *  See the License for the specific language governing permissions and
# *  limitations under the License.
# */
#
# $Id: Note.pm 610 2007-05-09 14:49:15Z hikarin $
#

package Img0ch::App::Note;

use strict;
use base qw(Img0ch::App::BBS);

sub new {
    my ( $iClass, $iKernel, @args ) = @_;
    my $iApp = $iClass->SUPER::new( $iKernel, @args );
    $iApp->{__applet_magic} = undef;
    $iApp->{__draft}        = undef;
    $iApp->{__image_type}   = undef;
    return $iApp;
}

sub run {
    my ($iApp) = @_;
    $iApp->{_config}->get('Maintenance')
        and die 'img0ch MAINTENANCE: Please try again later.', "\n";

    $iApp->capture();
    my $iRequest = $iApp->request();
    if ( $iRequest->param('post') ) {
        eval {
            my @args = (
                'note_error', 'shiftjis',
                sub { ${ $_[0] } =~ s/<br>/\n/gxms; ${ $_[0] } }
            );
            $iApp->init()         or return $iApp->redirect_error(@args);
            $iApp->get_host()     or return $iApp->redirect_error(@args);
            $iApp->validate()     or return $iApp->redirect_error(@args);
            $iApp->is_writeable() or return $iApp->redirect_error(@args);
            $iApp->get_date();
            $iApp->post() or return $iApp->redirect_error(@args);
            $iApp->update_subback( undef, 1 );
            $iApp->update_index();
            $iApp->redirect_success();
        };
        if ($@) {
            $iRequest->send_http_header('text/plain');
            $iRequest->print( \'error', \"\n\n", \$@ );
        }
    }
    else {
        my $action;
        if ( my $path_info = $iRequest->path_info() ) {
            my @path = split '/', $path_info;
            ( $path[1] || 'canvas' ) =~ /(\w+)/xms;
            $action = $1;
            ( $path[2] || 'test' ) =~ /([\w\-]+)/xms;
            my $bbs = $1;
            ( $path[3] || '00000000' ) =~ /(\d{9,10})/xms;
            my $key = $1;
            ( $path[4] || '0' ) =~ /(\d+)/xms;
            my $resno       = $1;
            my $iNewRequest = Img0ch::App::Note::Request->new($iRequest);
            my @params      = $iRequest->param();
            map {
                $iNewRequest->set_param_internal( $_, $iRequest->param($_) )
            } @params;
            $iNewRequest->set_param_internal( 'action', $action );
            $iNewRequest->set_param_internal( 'bbs',    $bbs );
            $iNewRequest->set_param_internal( 'key',    $key );
            $iNewRequest->set_param_internal( 'resno',  $resno );
            $iApp->{_request} = $iRequest = $iNewRequest;
        }
        else {
            $action = $iRequest->param('action') || $iRequest->param('mode');
        }
        my $action_method = $iApp->available_methods(
            {   animate       => \&create_animation,
                animation     => \&create_animation,
                canvas        => \&create_canvas,
                confirm       => \&create_confirm_password,
                continue      => \&search_drafts,
                create_canvas => \&create_canvas,
                load          => \&load_canvas,
                pch           => \&load_pch,
                post          => \&create_postform,
                remove        => \&remove_draft,
                save          => \&save_draft,
                update        => \&update_note,
            }
            )->{$action}
            || \&create_canvas;
        $iApp->init() or return $iApp->redirect_error();
        eval {
            my ( $iTemplate, $ctype, $body ) = $action_method->($iApp);
            $iRequest->send_http_header( $ctype || 'text/html' );
            $iTemplate ? $iTemplate->flush() : $iRequest->print($body);
        };
        if ($@) {
            $iRequest->send_http_header('text/plain');
            $iRequest->print( \'error', \"\n\n", \$@ );
        }
    }

    return 1;
}

sub create_animation {
    my ( $iApp, $id ) = @_;

    require Img0ch::Template;
    my $iRequest  = $iApp->request();
    my $iTemplate = Img0ch::Template->new(
        $iApp->bbs(),
        {   file    => 'note_animation',
            request => $iRequest,
            setting => $iApp->setting(),
        }
    );

    my ( $key, $magic, $resno, $width, $height );
    if (    $key = $iRequest->key()
        and $resno = $iRequest->param_int('resno') )
    {
        require Img0ch::Plugin::Module::Note;
        require Img0ch::Upload;
        my $iBBS    = $iApp->bbs();
        my $iNote   = Img0ch::Plugin::Module::Note->new( $iBBS, $key );
        my $iUpload = Img0ch::Upload->new( $iBBS, $key );
        ( undef, $width, $height, undef ) = @{ $iUpload->get($id) };
        $magic = $iNote->get($resno)->{applet} || 'P';
    }
    elsif ( $id = $iRequest->param_int('draft') ) {
        require Img0ch::Plugin::Module::Draft;
        my $iDraft = Img0ch::Plugin::Module::Draft->new( $iApp->kernel() );
        $magic  = $iDraft->get_applet_magic($id) || 'P';
        $width  = $iDraft->get_width($id);
        $height = $iDraft->get_height($id);
    }

    $iTemplate->param(
        {   CanvasWidth  => ( $iRequest->param_int('canvas_width')  || 500 ),
            CanvasHeight => ( $iRequest->param_int('canvas_height') || 500 ),
            DraftId      => ( $id                                   || 0 ),
            ImageWidth => ( $width || $iRequest->param_int('width') || 300 ),
            ImageHeight =>
                ( $height || $iRequest->param_int('height') || 300 ),
            KEY              => $key,
            PaintBBSAsViewer => ( $magic eq 'P' ? 1 : 0 ),
            ResNumber => ( $resno || 0 ),
        }
    );
    return $iTemplate;
}

sub create_canvas {
    my ( $iApp, $id, $requireLoadImage, $edit_pass ) = @_;

    require Img0ch::Template;
    my $template_param = $iApp->update_subback( $iApp->bbs(), 0, 1 );
    my $iBBS           = $iApp->bbs();
    my $iRequest       = $iApp->request();
    my $iTemplate      = Img0ch::Template->new(
        $iBBS,
        {   file    => 'note_canvas',
            request => $iRequest,
            setting => $iApp->setting(),
        }
    );

    my $jpeg = $iRequest->param('jpeg') || $iRequest->param('imgjpeg');
    my $pbbs = $iRequest->param('pbbs')
        || $iRequest->param('applet')
        =~ /\A[pP][aA][iI][nN][tT][bB][bB][sS]/xms;
    my $c_normal = $iRequest->param('c_normal')
        || $iRequest->param('applet') =~ /\A[cC]_[nN][oO][rR][mM][aA][lL]/xms;
    my $c_pro = $iRequest->param('c_pro')
        || $iRequest->param('applet') =~ /\A[cC]_[pP][rR][oO]/xms;
    $pbbs or $c_normal or $c_pro or $pbbs = 1;

    my ( $ext, $key ) = ( '', $iRequest->param_int('key') );
    my ( $width, $height )
        = ( $iRequest->param_int('width'), $iRequest->param_int('height') );
    if ($requireLoadImage) {
        require Img0ch::Upload;
        my $iUpload = Img0ch::Upload->new( $iBBS, $key );
        ( $ext, $width, $height, undef ) = @{ $iUpload->get($id) };
    }

    $iTemplate->param(
        {   %$template_param,
            CanvasWidth  => ( $iRequest->param_int('canvas_width')  || 500 ),
            CanvasHeight => ( $iRequest->param_int('canvas_height') || 500 ),
            DraftId => ( !$requireLoadImage ? $id : 0 ),
            EditPass        => $edit_pass,
            EnableAnimation => ( $iRequest->param('animation') ? 1 : 0 ),
            EnableJPEG      => ( $jpeg ? 1 : 0 ),
            Extension       => $ext,
            ImageWidth  => ( $width  || 300 ),
            ImageHeight => ( $height || 300 ),
            KEY         => $key,
            ResNumber => ( $requireLoadImage ? $id : 0 ),
            UsePaintBBS       => $pbbs,
            UseShiiPainter    => $c_normal,
            UseShiiPainterPro => $c_pro,
        }
    );
    return $iTemplate;
}

sub create_postform {
    my ($iApp) = @_;
    my $template_param = $iApp->update_subback( $iApp->bbs(), 0, 1 );
    my $iRequest       = $iApp->request();
    my $iTemplate      = Img0ch::Template->new(
        $iApp->bbs(),
        {   file    => 'note_form',
            request => $iRequest,
            setting => $iApp->setting(),
        }
    );

    require Img0ch::Plugin::Module::Draft;
    $iRequest->init( $iApp->config() );
    my $iNote = Img0ch::Plugin::Module::Draft->new( $iApp->kernel() );
    my $id    = $iRequest->param_int('draft') || 0;
    my $image = $iNote->get_image_base64($id);

    $iTemplate->param(
        {   %$template_param,
            Image   => $image,
            DraftId => $id,
        }
    );
    return $iTemplate;
}

sub create_confirm_password {
    my ( $iApp, $key, $resno ) = @_;
    my $iRequest = $iApp->request();
    $key   ||= $iRequest->key();
    $resno ||= $iRequest->param_int('resno');

    require Img0ch::Plugin::Module::Note;
    my $iBBS   = $iApp->bbs();
    my $iNote  = Img0ch::Plugin::Module::Note->new( $iBBS, $key );
    my $data   = $iNote->get($resno);
    my $applet = $data->{applet};
    my $failed = 0;
    if ( my $input = $iRequest->param('editpass') ) {
        my $pass = $data->{pass};
        if ( $input eq $pass ) {
            my $iTemplate = $iApp->create_canvas( $resno, 1, $pass );
            return $iTemplate;
        }
        $failed = 1;
    }

    require Img0ch::Template;
    my $iTemplate = Img0ch::Template->new(
        $iApp->bbs(),
        {   file    => 'note_confirm',
            request => $iRequest,
            setting => $iApp->setting(),
        }
    );

    $iTemplate->param(
        {   Animation      => $data->{animation},
            Failed         => $failed,
            Image          => $iNote->get_image_base64($resno),
            ImageType      => ( $data->{type} || 'jpg' ),
            KEY            => $key,
            ResNumber      => $resno,
            UsePaintBBS    => ( $applet eq 'P' ? 1 : 0 ),
            UseShiiPainter => ( $applet eq 'S' ? 1 : 0 ),
        }
    );
    return $iTemplate;
}

sub load_pch {
    my ($iApp) = @_;
    my $iRequest = $iApp->request();
    $iRequest->init( $iApp->config() );

    my ( $file, $pch );
    if (    my $key = $iRequest->key()
        and my $resno = $iRequest->param_int('resno') )
    {
        require Img0ch::Plugin::Module::Note;
        my $iBBS = $iApp->bbs();
        my $iNote = Img0ch::Plugin::Module::Note->new( $iBBS, $key );
        $file = "${resno}.pch";
        $pch  = $iNote->get($resno)->{animation};
    }
    elsif ( my $id = $iRequest->param_int('draft') ) {
        require Img0ch::Plugin::Module::Draft;
        my $iDraft = Img0ch::Plugin::Module::Draft->new( $iApp->kernel() );
        $file = "${id}.pch";
        $pch  = $iDraft->get_animation($id);
    }

    if ( $iRequest->param('download') ) {
        $iRequest->set_header( 'content-disposition',
            'attachment; filename="' . $file . '"' );
        $iRequest->set_header( 'content-length', length($pch) );
    }

    return ( undef, 'application/octet-stream', \$pch );
}

sub load_canvas {
    my ($iApp) = @_;
    my $iRequest = $iApp->request();
    $iRequest->init( $iApp->config() );

    my ( $image, $type, $ext, $file );
    if (    my $key = $iRequest->param_int('key')
        and my $resno = $iRequest->param('resno') )
    {
        require Img0ch::Plugin::Module::Note;
        my $iBBS = $iApp->bbs();
        my $iNote = Img0ch::Plugin::Module::Note->new( $iBBS, $key );
        $image = $iNote->get_image($resno);
        $ext   = $iNote->get($resno)->{type};
        $file  = "${resno}.${ext}";
        $type  = 'image/' . $ext;
    }
    elsif ( my $id = $iRequest->param_int('draft') ) {
        require Img0ch::Plugin::Module::Draft;
        my $iDraft = Img0ch::Plugin::Module::Draft->new( $iApp->kernel() );
        $image = $iDraft->get_image($id);
        $ext   = $iDraft->get_image_type($id);
        $file  = "${id}.${ext}";
        $type  = 'image/' . $ext;
    }

    if ( $iRequest->param('download') ) {
        $iRequest->set_header( 'content-disposition',
            'attachment; filename="' . $file . '"' );
    }

    return ( undef, $type, \$image );
}

sub save_draft {
    my ($iApp)   = @_;
    my $iConfig  = $iApp->config();
    my $iRequest = $iApp->request();
    $iRequest->init( $iApp->config() );

    require Img0ch::Plugin::Module::Draft;
    my $iDraft  = Img0ch::Plugin::Module::Draft->new( $iApp->kernel() );
    my $now     = $iRequest->now();
    my $ellapse = $iDraft->get_ellapse() + $iRequest->param_int('timer');
    my $expires = $iConfig->get_int('DraftExpire') || 86400;

    require Img0ch::Upload;
    my ( $width, $height )
        = Img0ch::Upload->get_dimensions( $iRequest->tempfile() );

    $iDraft->purge( $now, $expires );
    $iDraft->set_ip( $iRequest->ip() );
    $iDraft->set_image( $iRequest->get_image() );
    $iDraft->set_image_type( $iApp->{__image_type} );
    $iDraft->set_animation( $iRequest->get_animation() );
    $iDraft->set_ellapse($ellapse);
    $iDraft->set_last_modified($now);
    $iDraft->set_width($width);
    $iDraft->set_height($width);
    $iDraft->set_edit_pass( $iRequest->param('editpass') );
    $iDraft->set_applet_magic( $iApp->{__applet_magic} );
    $iDraft->save();
    return ( undef, undef, \'' );
}

sub remove_draft {
    my ($iApp) = @_;
    my $iRequest = $iApp->request();
    $iRequest->init( $iApp->config() );

    require Img0ch::Plugin::Module::Draft;
    my $iDraft = Img0ch::Plugin::Module::Draft->new( $iApp->kernel() );
    my $id     = $iRequest->param_int('draft');
    $iDraft->remove($id);
    $iDraft->save();
    return $iApp->search_drafts(1);
}

sub search_drafts {
    my ( $iApp, $noCanvas ) = @_;
    my $iRequest = $iApp->request();
    $iRequest->init( $iApp->config() );

    my $key   = $iRequest->key();
    my $resno = $iRequest->param_int('resno');
    if ( $key and $resno ) {
        my $iTemplate = $iApp->create_confirm_password( $key, $resno );
        return $iTemplate;
    }
    if ( my $id = $iRequest->param_int('draft') and !$noCanvas ) {
        my $iTemplate = $iApp->create_canvas( $id, 0 );
        return $iTemplate;
    }

    require Img0ch::Template;
    my $iTemplate = Img0ch::Template->new(
        $iApp->bbs(),
        {   file    => 'note_drafts',
            request => $iRequest,
            setting => $iApp->setting(),
        }
    );

    require Img0ch::Plugin::Module::Draft;
    my $iNote = Img0ch::Plugin::Module::Draft->new( $iApp->kernel() );
    my $stack = [];

    $iNote->iterate(
        sub {
            my ( $hash,  $argument ) = @_;
            my ( $stack, $ip )       = @$argument;
            $hash->{ip} eq $ip or return 0;
            my $applet = $hash->{applet} || 'P';
            my $type   = $hash->{type}   || 'jpg';
            unshift @$stack,
                {
                animation => length( $hash->{animation} ),
                ellapse   => int( $hash->{ellapse} / 1000 ),
                height    => $hash->{height},
                id        => $hash->{id},
                image     => $hash->{image},
                isJpeg    => ( $type eq 'jpg' ? 1 : 0 ),
                isPbbs    => ( $applet eq 'P' ? 1 : 0 ),
                isShii    => ( $applet eq 'S' ? 1 : 0 ),
                modified  => scalar( localtime( $hash->{modified} ) ),
                type      => $type,
                width     => $hash->{width}
                };
            return 0;
        },
        [ $stack, $iRequest->ip() ]
    );

    $iTemplate->param( { DraftCount => scalar(@$stack), Drafts => $stack } );
    return $iTemplate;
}

sub capture {
    my ($iApp)   = @_;
    my $iRequest = $iApp->request();
    my $rawdata  = $iRequest->rawdata();
    my $magic = substr $$rawdata, 0, 1;

    length $$rawdata > 8388608 and return 0;    ## LIMIT 8MB
    if ( $magic eq 'S' or $magic eq 'P' ) {
        my $body_length = Img0ch::Kernel::intval( substr $$rawdata, 1, 8 );
        my $image_length
            = Img0ch::Kernel::intval( substr $$rawdata, $body_length + 9, 8 );
        if ( $body_length and $image_length ) {
            my $ext1_from = $body_length + $image_length + 19;
            my $ext1_length
                = Img0ch::Kernel::intval( substr $$rawdata, $ext1_from, 8 );

            my $ext2_from = $ext1_from + $ext1_length;
            my $ext2_length
                = Img0ch::Kernel::intval( substr $$rawdata, $ext2_from, 8 );

            my $iNewRequest = Img0ch::App::Note::Request->new($iRequest);
            my $body = substr $$rawdata, 9, $body_length;
            foreach my $pair ( split '&', $body ) {
                my ( $name, $data ) = split '=', $pair, 2;
                $name =~ s/\+/ /gxms;
                $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack( 'H2', $1 )/egxms;
                $data =~ s/\+/ /gxms;
                $data =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack( 'H2', $1 )/egxms;
                $iNewRequest->set_param_internal( $name, $data );
            }

            my $image = substr $$rawdata, $body_length + 19, $image_length;
            my $type
                = $iNewRequest->param('image_type') eq 'png' ? 'png' : 'jpg';
            $iApp->{__image_type}   = $type;
            $iApp->{__applet_magic} = $magic;
            $iNewRequest->set_image_internal( \$image );
            $iNewRequest->set_image_to_file_internal( \$image, $type );

            my $ext1_body = substr $$rawdata, $ext1_from + 8, $ext1_length;
            $iNewRequest->set_animation_internal( \$ext1_body );

  #            my $ext2_body = substr $$rawdata, $ext2_from + 8, $ext2_length;
  #            $iNewRequest->set_thumbnail_internal(\$ext2_body);

            $iApp->{_request} = $iNewRequest;
            return 1;
        }
    }
    elsif ( my $id = $iRequest->param('noteID') ) {
        require Img0ch::Plugin::Module::Draft;
        my $iDraft = Img0ch::Plugin::Module::Draft->new( $iApp->kernel() );
        my $image  = $iDraft->get_image($id);
        my $ext    = $iDraft->get_image_type($id);
        if ($image) {
            my $iNewRequest = Img0ch::App::Note::Request->new($iRequest);
            my @params      = $iRequest->param();
            map {
                $iNewRequest->set_param_internal( $_, $iRequest->param($_) )
            } @params;
            $iNewRequest->set_image_to_file_internal( \$image, $ext );
            $iApp->{__draft}  = $iDraft;
            $iApp->{_request} = $iNewRequest;
            return 1;
        }
    }

    return 0;
}

sub update_note {
    my ($iApp)   = @_;
    my $iRequest = $iApp->request();
    my $key      = $iRequest->key();
    my $resno    = $iRequest->param_int('resno');

    require Img0ch::Plugin::Module::Note;
    my $iBBS  = $iApp->bbs();
    my $iNote = Img0ch::Plugin::Module::Note->new( $iBBS, $key );
    my $data  = $iNote->get($resno);
    my $ext   = $data->{type};
    my $edit  = $iRequest->param('editpass');
    if ( $edit eq $data->{pass} ) {
        require Img0ch::Upload;
        my $iUpload = Img0ch::Upload->new( $iBBS, $key );
        if ( -w $iUpload->path( $resno, $ext ) ) {
            $iUpload->set( $iRequest->tempfile(), $resno, $ext );
            $iUpload->set_remove_password( $iRequest->param('pass') );
            $iUpload->save();
            $iNote->set(
                $resno,
                {   animation => $iRequest->get_animation(),
                    applet    => $data->{applet},
                    ellapse =>
                        ( $data->{ellapse} + $iRequest->param_int('timer') ),
                    modified => $iRequest->now(),
                    pass     => $edit,
                    type     => $ext
                }
            );
            $iNote->save();
        }
    }
    return;
}

sub get_applet_type { $_[0]->{__applet_magic} }

sub get_draft_repository { $_[0]->{__draft} }

sub available_methods { shift; return $_[0] }

package Img0ch::App::Note::Request;

use base qw(Img0ch::Request::Interface);

BEGIN {
    my $pkg = __PACKAGE__;
    for my $method (qw(image thumbnail animation)) {
        my $attr = '__' . $method;
        no strict 'refs';
        *{"${pkg}::set_${method}_internal"} = sub {
            my ( $iApp, $body ) = @_;
            caller() eq 'Img0ch::App::Note' or return;
            $iApp->{$attr} = $body;
            return;
        };
        *{"${pkg}::get_${method}"} = sub {
            my ( $iApp, $body ) = @_;
            return $iApp->{$attr};
            }
    }
    for my $method (
        qw(agent fh get_app_uri get_device_id
        get_remote_host is_mobile_device
        ip ip_int msec now path_info query)
        )
    {
        no strict 'refs';
        *{"${pkg}::${method}"} = sub { $_[0]->{__orig}->$method };
    }
}

sub new {
    my ( $iClass, $iRequest ) = @_;
    bless { __orig => $iRequest, __param => {} }, $iClass;
}

sub signature { $_[0]->{__orig}->signature() }

*credit = \&signature;

sub init {
    my ( $iRequest, $iConfig ) = @_;
    $iRequest->{__orig}->init($iConfig);
    my $bbs = $iRequest->param('bbs');
    $bbs =~ /\A([\w\-]+)\z/xms;
    $iRequest->{__bbs} = $1 || '';
    my $key = $iRequest->param('key');
    $key =~ /\A(\d\d\d\d\d\d\d\d\d\d?)\z/xms;
    $iRequest->{__key} = $1 || '';
    return 1;
}

sub param {
    my ( $iRequest, $key, $unescape ) = @_;
    $unescape ||= 0;

    if ( !wantarray ) {
        my $value = $iRequest->{__param}->{$key};
        if ( !$unescape ) {
            $value = Img0ch::Kernel::escape_html_entities($value);
        }
        return $value;
    }
    elsif ( wantarray and !$key ) {
        return keys %{ $iRequest->{__param} };
    }
    else {
        my $value = $iRequest->{__param}->{$key};
        if ( !$unescape ) {
            $value = Img0ch::Kernel::escape_html_entities($value);
        }
        return $value || $iRequest->{__orig}->param( $key, $unescape );
    }
}

sub get_header { shift->{__orig}->get_header(@_) }

sub set_header { shift->{__orig}->set_header(@_) }

sub is_uploadable {1}

sub fsize { $_[0]->{__fsize} }

sub cookie { shift->{__orig}->cookie(@_) }

sub send_http_header { shift->{__orig}->send_http_header(@_) }

sub print { shift->{__orig}->print(@_) }

sub set_param_internal {
    my ( $iRequest, $key, $value ) = @_;
    caller() eq 'Img0ch::App::Note' or return;
    $iRequest->{__param}->{$key} = $value;
    return;
}

sub set_image_to_file_internal {
    my ( $iRequest, $image, $suffix ) = @_;
    caller() eq 'Img0ch::App::Note' or return;
    require File::Temp;
    my ( $fh, $temp )
        = File::Temp::tempfile( UNLINK => 1, SUFFIX => ".${suffix}" );
    binmode $fh;
    print {$fh} ${$image};
    close $fh;
    $iRequest->{__file}  = $temp;
    $iRequest->{__fsize} = -s $temp || 0;
    $iRequest->{__tmp}   = $temp;
    return;
}

1;
__END__
