#!/usr/bin/perl -I../www/libs -Ilibs
use strict;
use warnings;

package API::News;

use POSIX qw(strftime); # options for the file upload
use DBI;
use Encode;
use HTML::Entities;
use Image::Magick;
use List::MoreUtils ();
use MIME::Base64;
use MIME::Entity;
use ECS::CDN::S3;
use Try::Tiny;

use SOAP::Lite;
use base 'SOAP::Server::Parameters';

use API::Config;
use lib $API::Config::Config->{Website_libs};

use ECS::Site;
use ECS::Endeca::Updates::NewsArticle::Producer;
use ECS::Endeca::Updates::NewsProduct::Producer;

sub save {
    my $self = shift;
    my $p = shift;
    ## Envelopes that hold image strings
    my $envelope = pop;
    my @parts = $envelope->parts();

    my $site = ECS::Site->fromID( $p->{'siteID'} );
    my $dbh = $site->site_dbh;

   
    my $s3 = ECS::CDN::S3->new();
    my $container = 'static';

    my $news_uploads = "/tmp/";
    ## SOAP return
    my $result = {
        'success' => 1,
        'error' => '',
        'update_DB' => $API::Config::Config->{update_DB},
        'data' => $p,
    };

    my $attachment_idx = 0;

    if ($p->{imageSmall} && $parts[0]->[$attachment_idx]) { # small image needs uploaded

        if ($p->{orgImageSmall} && $p->{orgImageSmall} ne "nosmallv2.jpg") { # remove the old image if exists
            unlink($news_uploads.$p->{orgImageSmall});
        }

        my $filename = $p->{filenameSmall}.$p->{extSmall};
        my $filepath = $news_uploads.$filename;
        my $webpfilename = $filename;
        $webpfilename =~ s/\.jpg/.webp/;
        my $webpfilepath = $news_uploads.$webpfilename;
        my $webp_content_type = 'image/webp';
        # Upload image to image_uploads when write_image is true (API::Config)
        # ------------------------------------------------------------------
        if($API::Config::Config->{write_image}){
            #my $filename = $news_uploads.$p->{filenameSmall}.$p->{extSmall};
            #my $webpfilename = $filename;
            #$webpfilename =~ s/\.jpg/.webp/;
            open(my $of, ">", $filepath) || die "Cannot open small file for writing ($filepath)";
            my $part = $parts[0]->[$attachment_idx];
            $attachment_idx++;
            print $of $part->bodyhandle->as_string;
            close($of);
            # convert to webp
            `$API::Config::Config->{cwebpExec} $filepath -metadata all -quiet -o $webpfilepath`;
            unless (-e $webpfilepath) {
                $result->{error} .= " Unable to create small image webp. Make sure original is a jpg image ending in .jpg ";
            }
            # optimize jpg
            my $smallimage = Image::Magick->new;
            $smallimage->Read("$filepath");
            if ($smallimage->Get('quality') > 85) {
                $smallimage->Write(filename=>$filepath, compression=>'JPEG', quality=>85, 'sampling-factor'=>'4:2:0');
            } else {
                $smallimage->Write(filename=>$filepath, compression=>'JPEG', 'sampling-factor'=>'4:2:0');
            }
        }


            my $c_result = try {
                $s3->put_file($s3->build_path(static => "img/news/$webpfilename"), $webpfilepath, { 'Content-Type' => $webp_content_type });
            } catch {
                $result->{error} .= " Unable to write large image webp to the cloud. $_ ";
                undef;
            };

        # Upload Small image to Rackspace cloud's static container in "/img/news"
        # -----------------------------------------------------------------------
        if($API::Config::Config->{write_cloud}){
            my $jpeg_content_type = 'image/jpg';
            my $c_result = try {
                $s3->put_file($s3->build_path(static => "img/news/$filename"), $filepath, { 'Content-Type' => $jpeg_content_type });
            } 
            catch {
                $result->{error} .= " Unable to write large image jpg to the cloud. $_";
            };
            # upload webp

            $c_result = try {
                $s3->put_file($s3->build_path(static => "img/news/$webpfilename"), $webpfilepath, { 'Content-Type' => $webp_content_type });
            }
            catch {
                $result->{error} .= " Unable to write large image jpg to the cloud. $_";
            };
        }

        $p->{orgImageSmall} = $filename; # use orgImageSmall to update/insert small image data
    }

    if ($p->{imageLarge} && $parts[0]->[$attachment_idx]) { # large image needs uploaded

        if ($p->{orgImageLarge} && $p->{orgImageLarge} ne "nolarge.jpg") { # remove the old image if exists
            unlink($news_uploads.$p->{orgImageLarge});
        }

        my $filebase = $p->{filenameLarge};
        my $filename = $p->{filenameLarge}.$p->{extLarge};
        my $filepath = $news_uploads.$filename;
        my $webpfilename = $filename;
        $webpfilename =~ s/\.jpg/.webp/;
        my $webpfilepath = $news_uploads.$webpfilename;

        # Upload image to image_uploads when write_image is true (API::Config)
        # ------------------------------------------------------------------
        if($API::Config::Config->{write_image}){
            open(my $of, ">".$filepath) || die "Cannot open large file for writing ($filepath)";
            my $part = $parts[0]->[$attachment_idx];
            $attachment_idx++;
            print $of $part->bodyhandle->as_string;
            close($of);
            # convert to webp
            `$API::Config::Config->{cwebpExec} $filepath -metadata all -quiet -o $webpfilepath`;
            unless (-e $webpfilepath) {
                $result->{error} .= " Unable to create large image webp. Make sure original is a jpg image ending in .jpg ";
            }
            # optimize jpg
            my $largeimage = Image::Magick->new;
            $largeimage->Read("$filepath");
            if ($largeimage->Get('quality') > 85) {
                $largeimage->Write(filename=>$filepath, compression=>'JPEG', quality=>85, 'sampling-factor'=>'4:2:0');
            } else {
                $largeimage->Write(filename=>$filepath, compression=>'JPEG', 'sampling-factor'=>'4:2:0');
            }
        }

        # Upload Large image to Rackspace cloud's static container in "/img/news"
        # -----------------------------------------------------------------------
        if($API::Config::Config->{write_cloud}){
            my $jpeg_content_type = 'image/jpg';
            my $c_result = try { 
                $s3->put_file($s3->build_path(static => "img/news/$filename"), $filepath, { 'Content-Type' => $jpeg_content_type });
            } catch {
                $result->{error} .= " Unable to write large image jpg to the cloud. $_ ";
            };
            # upload webp
            my $webp_content_type = 'image/webp';
            $c_result = try {
                $s3->put_file($s3->build_path(static => "img/news/$webpfilename"), $webpfilepath, { 'Content-Type' => $webp_content_type });
            } catch {
                $result->{error} .= " Unable to write large image webp to the cloud. $_ ";
                undef;
            };
        }

        $p->{orgImageLarge} = $filename; # use orgImageLarge to update/insert large image data

        # Create a thumbnail of the Large Image
        if ($p->{orgImageLargeThumb} && $p->{orgImageLargeThumb} ne "nothumb.jpg") { # remove the old image if exists
            unlink($news_uploads.$p->{orgImageLargeThumb});
        }

        my $thumb = Image::Magick->new;
        $thumb->ReadImage($news_uploads.$p->{orgImageLarge});
        $thumb->Level("0,70%,.85");
        $thumb->Thumbnail("100x75");

        my $thumbfilename = $filebase."_thumb".$p->{extLarge};
        my $thumbfilepath = $news_uploads.$thumbfilename;
        my $thumbwebpfilename = $thumbfilename;
        $thumbwebpfilename =~ s/\.jpg/.webp/;
        my $thumbwebpfilepath = $news_uploads.$thumbwebpfilename;

        $thumb->Write($thumbfilepath);
        # convert to webp
        `$API::Config::Config->{cwebpExec} $thumbfilepath -metadata all -quiet -o $thumbwebpfilepath`;
        unless (-e $thumbwebpfilepath) {
            $result->{error} .= " Unable to create thumb image webp. Make sure original is a jpg image ending in .jpg ";
        }

        $p->{orgImageLargeThumb} = $thumbfilename; # use orgImageLargeThumb to update/insert large thumb image data

        # Upload logo to Rackspace cloud's static container in "/img/news"
        # ----------------------------------------------------------------
        if ( $API::Config::Config->{write_cloud} ) {
            my $jpeg_content_type = 'image/jpg';
            try {
                $s3->put_file($s3->build_path(static => "img/news/$thumbfilename"), $thumbfilepath, { 'Content-Type' => $jpeg_content_type });
            }
            catch {
                $result->{error} .= " Unable to write thumbnail image jpg to the cloud. $_";
            };

            # upload webp
            my $webp_content_type = 'image/webp';
            try {
                $s3->put_file($s3->build_path(static => "img/news/$thumbwebpfilename"), $thumbwebpfilepath, { 'Content-Type' => $webp_content_type });
            }
            catch {
                $result->{error} .= " Unable to write thumbnail image webp to the cloud. $_";
            };
        }

    }

    # API encodes all data into UTF-8, so decode it for processing:
    # Convert any special characters in input fields to their corresponding html code
    foreach my $i ($p->{title},
                   $p->{description},
                   $p->{shortDescription},
                   $p->{subtitle},
                   $p->{subtitleDescription}){
        $i = encode_entities(decode_utf8($i));
    }
    
    my $updateProducer = ECS::Endeca::Updates::NewsArticle::Producer->getUpdateProducer();

    my @newLinkedProductIDs;
    if ($p->{'type'} eq "prodland") {
        @newLinkedProductIDs = $p->{'linkedproduct'};
    } else {
        @newLinkedProductIDs = split(',', $p->{'linkedproducts'});
    }
    $p->{'productIDs'} = \@newLinkedProductIDs; # to send saved data back to template

    if ($p->{newsArticleID} eq "new") { # create/insert news
        my $dateAdded = strftime("%Y-%m-%d %H:%M:%S",localtime(time)); # get the current date format for mysql timestamp

        my $newsInsertID = ECS::SQL::insert({
            DBH => $dbh,
            table =>'newsArticle',
            columns => [qw(dateAdded dateCampaigned dateExpired type title description shortDescription subtitle subtitleDescription enabled imageSmall imageLarge imageLargeThumb alias newsCategoryID)],
            hash => {
                dateAdded => $dateAdded,
                dateCampaigned => $p->{'dateCampaigned'},
                dateExpired => $p->{'dateExpired'},
                type => $p->{type},
                title => $p->{title},
                description => $p->{description},
                shortDescription => $p->{shortDescription},
                subtitle => $p->{subtitle},
                subtitleDescription => $p->{subtitleDescription},
                enabled => $p->{enabled},
                imageSmall => $p->{orgImageSmall},
                imageLarge => $p->{orgImageLarge},
                imageLargeThumb => $p->{orgImageLargeThumb},
                alias => $p->{alias},
                newsCategoryID => $p->{category}
            }
        });

        # Link items to the created News

        for my $productID ( @newLinkedProductIDs ) {
            linkProduct({
                DBH => $dbh,
                newsArticleID => $newsInsertID,
                productID => $productID,
            });
        }

            for my $videoInfo (@{$p->{videos}}) {
                linkVideo({
                    DBH => $dbh,
                    newsArticleID => $newsInsertID,
                    videoID => $videoInfo->{videoID}
                });
            }

            for my $imageInfo (@{$p->{images}}) {

                linkImage({
                    DBH => $dbh,
                    newsArticleID => $newsInsertID,
                    imageID => $imageInfo->{imageID}
                });
            }
        $result->{newsInsertID} = $newsInsertID;
        $updateProducer->publish( {
            'newsArticleID' => $newsInsertID,
            'siteID'        => $p->{ 'siteID' },
            'operation'     => "create",
        } );
    } else { # update news
        if(!ECS::SQL::update({
            DBH => $dbh,
            table => 'newsArticle',
            key => 'newsArticleID',
            columns => [qw(type title description shortDescription subtitle subtitleDescription enabled imageSmall imageLarge imageLargeThumb alias newsCategoryID newsArticleID dateCampaigned dateExpired)],
            hash => {
                type => $p->{type},
                title => $p->{title},
                description => $p->{description},
                shortDescription => $p->{shortDescription},
                subtitle => $p->{subtitle},
                subtitleDescription => $p->{subtitleDescription},
                enabled => $p->{enabled},
                imageSmall => $p->{orgImageSmall},
                imageLarge => $p->{orgImageLarge},
                imageLargeThumb => $p->{orgImageLargeThumb},
                alias => $p->{alias},
                newsCategoryID => $p->{category},
                newsArticleID => $p->{newsArticleID},
                dateCampaigned => $p->{'dateCampaigned'},
                dateExpired => $p->{'dateExpired'},
            }
        })){
            $result->{success} = 0;
            $result->{error} .= " Unable to update newsArticle table. $! ";
            return $result;
        }

        my @prevLinkedProductIDs = getLinkedProductIDs({
            'dbh' => $dbh,
            'newsArticleID' => $p->{'newsArticleID'},
        });

        my $linkedProductsChanged = calcLinkedProductIDsChanged({
            'newProductIDs' => \@newLinkedProductIDs,
            'prevProductIDs' => \@prevLinkedProductIDs,
        });
        if ( $linkedProductsChanged ) {

            removeLinkedProducts({
                'DBH' => $dbh,
                'newsArticleID' => $p->{'newsArticleID'},
            });

            for my $productID ( @newLinkedProductIDs ) {
                linkProduct({
                    'DBH' => $dbh,
                    'newsArticleID' => $p->{'newsArticleID'},
                    'productID' => $productID,
                });
            }

            my $newsProductUpdateProducer = ECS::Endeca::Updates::NewsProduct::Producer
                ->getUpdateProducer();
            my @affectedProductIDs = List::MoreUtils::uniq(
                @newLinkedProductIDs,
                @prevLinkedProductIDs,
            );

            $newsProductUpdateProducer->publish( {
                'newsArticleID' => $p->{ 'newsArticleID' },
                'siteID' => $p->{ 'siteID' },
                'operation' => "update",
                'affectedProductIDs' => \@affectedProductIDs,
            } );
        }

        # Re-create image/video relationship
        removeLinkedImages({
            'DBH' => $dbh,
            'newsArticleID' => $p->{'newsArticleID'},
        });
        removeLinkedVideos({
            'DBH' => $dbh,
            'newsArticleID' => $p->{'newsArticleID'},
        });

        

            for my $videoInfo (@{$p->{videos}}) {
                linkVideo({
                    DBH => $dbh,
                    newsArticleID => $p->{newsArticleID},
                    videoID => $videoInfo->{videoID}
                });
            }

            for my $imageInfo (@{$p->{images}}) {
                linkImage({
                    DBH => $dbh,
                    newsArticleID => $p->{newsArticleID},
                    imageID => $imageInfo->{imageID}
                });
            }

        $updateProducer->publish( {
            'newsArticleID' => $p->{ 'newsArticleID' },
            'siteID'        => $p->{ 'siteID' },
            'operation'     => "update",
        } );
        my @success = ({message => "News Updated Successfully"});
        $p->{success} = \@success;
    }
    return $result;
}

sub linkProduct {
    my $p = shift;

    $p->{productID} =~ s/[^0-9]//g; # only read digits out of the Product ID

    my $row = ECS::SQL::insert({
        DBH => $p->{DBH},
        table =>'newsProduct',
        columns => [qw(newsArticleID productID)],
        hash => {
            newsArticleID => $p->{newsArticleID},
            productID => $p->{productID}
        }
    });

    return $row;
}

sub linkVideo {
    my $p = shift;

    my $row = ECS::SQL::insert({
        DBH => $p->{DBH},
        table =>'newsVideo',
        columns => [qw(newsArticleID videoID)],
        hash => {
            newsArticleID => $p->{newsArticleID},
            videoID => $p->{videoID}
        }
    });

    return $row;
}

sub linkImage {
    my $p = shift;
    my $row = ECS::SQL::insert({
        DBH => $p->{DBH},
        table =>'newsImage',
        columns => [qw(newsArticleID imageID)],
        hash => {
            newsArticleID => $p->{newsArticleID},
            imageID => $p->{imageID}
        }
    });

    return $row;
}

###############################################################################

sub getLinkedProductIDs {
    my $p = shift;
    my $dbh = $p->{'dbh'} or die "dbh required";
    my $newsArticleID = $p->{'newsArticleID'} or die "newsArticleID required";

    my $sth = $dbh->prepare(
        "SELECT
            productID
        FROM
            newsProduct
        WHERE
            newsArticleID = ?
        ORDER BY
            newsProductID
        "
    );

    $sth->execute(
        $newsArticleID,
    );

    my @productIDs;
    while ( my $link = $sth->fetchrow_hashref ) {
        push(@productIDs, $link->{'productID'});
    }

    return @productIDs;
}

###############################################################################

sub calcLinkedProductIDsChanged {
    my $p = shift;
    my $newProductIDs = $p->{'newProductIDs'}
        or die 'newProductIDs required';
    my $prevProductIDs = $p->{'prevProductIDs'}
        or die 'prevProductIDs required';

    return 1 if (
        scalar @{ $newProductIDs }
        != scalar @{ $prevProductIDs }
    );

    for my $lpi ( 0 .. $#{ $newProductIDs } ) {
        return 1 if (
            $newProductIDs->[ $lpi ]
            != $prevProductIDs->[ $lpi ]
        );
    }

    return;
}

###############################################################################

sub removeLinkedProducts {
    my $p = shift;
    my $sql = "DELETE FROM newsProduct WHERE newsArticleID = $p->{newsArticleID}";
    ECS::SQL::query({DBH => $p->{DBH}, sql => $sql});
    return;
}

###############################################################################

sub removeLinkedImages {
    my $p = shift;
    my $sql = "DELETE FROM newsImage WHERE newsArticleID = $p->{newsArticleID}";
    ECS::SQL::query({DBH => $p->{DBH}, sql => $sql});
    return;
}

###############################################################################

sub removeLinkedVideos {
    my $p = shift;
    my $sql = "DELETE FROM newsVideo WHERE newsArticleID = $p->{newsArticleID}";
    ECS::SQL::query({DBH => $p->{DBH}, sql => $sql});
    return;
}

###############################################################################

1;
