#!usr/bin/perl
use feature qw(say);
use JSON::XS qw(encode_json decode_json);
use Selenium::Firefox::Profile;
use Data::Dumper;
use HTML::TreeBuilder::XPath;
use File::Slurp qw(read_file write_file);
use Selenium::Remote::WDKeys;
use Selenium::Firefox;
use Cwd;
use Getopt::Long qw(GetOptions);
use File::Basename;
use List::Util 'shuffle';
use Selenium::Waiter qw/wait_until/;

my (%config, $driver_profile, $driver);

sub Init {	
	%config = (
		'xpath' => {
			'input_username_classic' => '//*[@id="id_username"]',
			'input_password_classic' => '//*[@id="id_enc_password"]',
			'button_login_classic' => '//*[@id="login-form"]/p[3]/input',
			'button_addtohomescreen' => '/html/body/div[4]/div/div/div/div[3]/button[1][text()="Add to Home screen"]',
			'button_addtohomescreen_cancel' => '/html/body/div[4]/div/div/div/div[3]/button[2][text()="Cancel"]',
			'button_newpost' => '//div[contains(@data-testid, "new-post-button")]',
			'input_newpostupload' => '//*[@id="react-root"]/section/nav[2]/div/div/form/input',
			'button_expand' => '//span[text()="Expand"]',
			'button_next' => '//button[text()="Next"]',
			'button_share' => '//button[text()="Share"]',
			'textarea_caption' => '//textarea[contains(@placeholder,"Write a caption")]',
		},
		'profile' => {
			'adobebestprices' => {
								'DBFilepath' => cwd() . '/profiles/adobebestprices/db/db_adobebestprices.dat',
								'imageDir' => cwd() . '/profiles/adobebestprices/images',
								'filename_as_title' => 1,
								'tags' => ['#Beats', '#FLStudio20', '#Producer', '#Ableton', '#Beatmaker', '#Studio', '#ProTools', '#Music', '#DAW', '#LogicPro', '#VST', '#VSTplugins', '#NativeInstruments', '#Drums', '#MIDI', '#Omnisphere', '#Keyscape', '#Trilian', '#Logic', '#Waves', '#Antares', '#AutoTune', '#Kontakt', '#SoundToys', '#FABFilter', '#Arturia', '#Windows', '#MAC'],
								},
		},
		'dumpToFile' => cwd() . '/get_page_source',
		'pyautogui_cmd' => 'python3 ' . cwd() . '/autogui.py',
		'profile_selected' => undef,
		'wipe_after' => 0,
	);
	
	&CheckParameter();
	&UndumpFromFile();
	&SelectImage();

	$driver_profile = Selenium::Firefox::Profile->new;
	$driver_profile->set_preference(
		'intl.accept_languages' => 'en-US',
		# 'general.useragent.override' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15',
		'general.useragent.override' => 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16',
	);
	# $driver_profile->set_boolean_preference("options.headless" => 1);
	$driver = Selenium::Remote::Driver->new(	'browser_name' => 'firefox',
												'platform' => 'LINUX',
												'remote_server_addr' => 'localhost',
												'port' => 4444,
												'firefox_profile' => $driver_profile,
												'extra_capabilities' => {
																			'moz:firefoxOptions' => {
																				#"args" => [ "--headless" ],
																			},
																		},
												);

	# $driver = Selenium::Firefox->new(	'browser_name' => 'firefox',
	# 									'platform' => 'LINUX',
	# 									'binary' => '/usr/local/bin/geckodriver',
	# 									'firefox_profile' => $driver_profile,
	# 									'extra_capabilities' => {
	# 																'moz:firefoxOptions' => {
	# 																	#"args" => [ "--headless" ],
	# 																},
	# 															},
	# 									);
												
	$driver->set_implicit_wait_timeout(5000);
	$driver->set_window_size(812, 375);					

	say 'starting...';
	say $driver->get_title();
	say $driver->get_user_agent();	
}


&Init();
&Login();
&NewPost();
&Finish();

sub RndHashtagString {
	my $i = shift;
	
	my $RndArr = &RndArrItems($config{profile}{'adobebestprices'}{tags}, $i);	
	my $hastags = join(' ', @$RndArr[0..$#{$RndArr}]);
	
	return $hastags;
}

sub RndArrItems {
	my ($arr_ref, $num_picks) = @_;
	
	# Shuffled list of indexes into @deck
	my @shuffled_indexes = shuffle(0..$#{$arr_ref});

	# Get just N of them.
	my @pick_indexes = @shuffled_indexes[ 0 .. $num_picks - 1 ];

	# Pick cards from @deck
	my @picks;
	for my $index (@pick_indexes) {
		push(@picks, $$arr_ref[$index]);
	}
	
	return \@picks;
}

sub SelectImage {
	&DirectoryListing();
	&FindNewDataset();
	$config{image_backslash} = $config{image_selected};
	# $config{image_backslash} =~ s/\//\\/g;
	print Dumper \%config;
}

sub Login {
	say 'Logging in...';	
	
	$driver->get('https://instagram.com/accounts/login/?force_classic_login');
	my $tree = HTML::TreeBuilder::XPath->new_from_content($driver->get_page_source());
	&Rndwait(1,3);	

	if( $tree->exists($config{xpath}{input_username_classic}) && $tree->exists($config{xpath}{input_password_classic}) ) {
		say 'Found: Login elements.';
		
		# Username & password
		$driver->find_element($config{xpath}{input_username_classic})->click;
		$driver->send_keys_to_active_element('adobebestprices');
		&Rndwait(1,3);	
		$driver->find_element($config{xpath}{input_password_classic})->click();
		$driver->send_keys_to_active_element('vst#1337');
		&Rndwait(1,3);
		
		# Click login button
		$driver->find_element($config{xpath}{button_login_classic})->click();
	}
	else {
		die 'Not found: Login elements.';
	}
}

sub NewPost {
	&Rndwait(5,10); # We see the home screen

	my $tree = HTML::TreeBuilder::XPath->new_from_content($driver->get_page_source());
	
	# add to homescreen popup?	
	&ClickXpath($config{xpath}{button_addtohomescreen}, $tree, 0); # click cancel button
	
	&ClickXpath($config{xpath}{button_newpost}, $tree); # click new post button
	&Rndwait(5,10);
	
	# Bypass file dialog using pyautogui
	my $cmd = $config{pyautogui_cmd} . ' "' . $config{image_backslash} . '"'; # image path needs to use backslash for python
	my $ret = system($cmd) or die "system $cmd failed: $?";
	# print `$cmd`;
	# system($cmd);
	# &Rndwait(30,45);
	
	&FillNewPost();
}

sub FillNewPost {
	# Wait for page reload after selecting an image
	wait_until { $driver->find_element($config{xpath}{button_next}) };
	
	my $tree = HTML::TreeBuilder::XPath->new_from_content($driver->get_page_source());
	
	# Expand button
	&ClickXpath($config{xpath}{button_expand}, $tree, 0); # click expand button to show image in full size
	&Rndwait(2,4);
	
	&ClickXpath($config{xpath}{button_next}, $tree); # click next button
	&Rndwait(5,10);
	
	# Caption
	my $tree2 = HTML::TreeBuilder::XPath->new_from_content($driver->get_page_source());
	my $profile = $config{profile_selected};
	my $caption;
	my $hastags = &RndHashtagString(9);
	if ($config{profile}{$profile}{'filename_as_title'}) {
        my $filename = basename($config{image_selected});
        $filename =~ s/(NO INSTALL)|(SymLink Installer)//g;
        $filename =~ s/( , )|(\.[^.]+$)//g;
        $caption = "$filename\n\n" . $hastags;
    }	
	&ClickXpath($config{xpath}{textarea_caption}, $tree2);
	&Rndwait(5,6);
	$driver->send_keys_to_active_element($caption);
	&Rndwait(5,10);
	
	# Share
	&ClickXpath($config{xpath}{button_share}, $tree2);
	&Rndwait(20,40);
}

sub Finish {
	say 'Sleep and then quit';
	&Rndwait(5,5);
	
	&AddImageToDB();
	&WipeImage() if $config{wipe_after};
	$driver->quit();
}

sub ClickXpath {
	my $die = 1;
	my ($xpath, $tree, $die) = @_;

	if( $tree->exists($xpath) ) {
		say "Found: $xpath";
		$driver->find_element($xpath)->click();
	}
	else {
		die "Not found: $xpath" if $die;
		warn "Not found: $xpath" if !$die;
	}
}

sub Rndwait {
	my ($minimum, $maximum) = @_;
	
	# $seconds = $minimum + int(rand($maximum - $minimum));
	$seconds = $minimum + rand($maximum - $minimum);
	$milliseconds = int($seconds * 1000);
	
	say "Sleeping for ${milliseconds}ms now...";
	$driver->pause($milliseconds);
}

sub dumpToFile {
    # &Delimiter((caller(0))[3]);
    my ($filepath, $ref) = @_;
    
    # DATA DUMPER UTF8 HACK
    # no warnings 'redefine';
    local *Data::Dumper::qquote = sub { qq["${\(shift)}"] };
    local $Data::Dumper::Useperl = 1;
    
    open my $FILE, '>:encoding(UTF-8)', $filepath;
    print $FILE Dumper $ref;
    close $FILE;
}

sub Delimiter {
    my $SubName = shift;
    print "\n" . "-" x 80 . "\nSUB " . $SubName . "\n" . '-' x 80 . "\n";
}

sub CheckParameter {
    &Delimiter((caller(0))[3]);
    
    GetOptions ('profile=s' => \$config{'profile_selected'}) or die &PrintUsage();
    die &PrintUsage() if !$config{'profile_selected'};
	
	my $profile = $config{'profile_selected'};
    if (!exists $config{'profile'}{$profile}) {
        print "Profile '$config{'profile'}' does not exist.\n";
        &PrintUsage();
        die;
    }
}

sub UndumpFromFile {
    &Delimiter((caller(0))[3]);
    
	my $profile = $config{profile_selected};
    if (-e $config{profile}{$profile}{'DBFilepath'}) {
        my $json = read_file($config{profile}{$profile}{'DBFilepath'}, { binmode => ':raw' });
        if (!$json) {
            warn "DB file $config{'profile'}{$profile}{'DBFilepath'} is empty.\n";
            return;
        }
        %{$config{db}} = %{ decode_json $json };
        print "INFO: $config{'profile'}{$profile}{'DBFilepath'} has " . scalar(keys(%{$config{db}})) . " keys.\n";
    }
    elsif ( !-e $config{'profile'}{$profile}{'DBFilepath'} ) {
        print "INFO: NO DB file found at $config{'profile'}{$profile}{'DBFilepath'}. Creating now... ";
        write_file( $config{'profile'}{$profile}{'DBFilepath'}, '' );
        &UndumpFromFile();
    }
}

sub PrintUsage {
	print "Usage: $0 --profile *name*\n";
	print "Following profiles are available:\n";
	print "* $_\n" for keys( %{$config{'profile'}} );
}

sub DirectoryListing {
    &Delimiter((caller(0))[3]);
    
	my $profile = $config{profile_selected};
    my @files = glob ( "$config{profile}{$profile}{imageDir}/*" );
    %{$config{images_available}} = map { $_ => { 'FILEPATH' => "$_" } } @files;
}

sub FindNewDataset {
    &Delimiter((caller(0))[3]);
    
    my $i = 0;
    for my $key (keys %{$config{images_available}}) {
        if (exists $config{db}{$key}) {
            print "OLD: $key\n";
        }
        elsif (!exists $config{db}{$key}) {
            print "NEW: $key\n";
            $config{image_selected} = $key;
            last;
        }
        $i++;
    }

    if ($i == scalar(keys(%{$config{images_available}}))) {
        die "\nNO NEW FILES AVAILABLE.\n";
    }
}

sub AddImageToDB {
    &Delimiter((caller(0))[3]);
    
	my $profile = $config{profile_selected};
	my $selected = $config{image_selected};
    $config{'images_available'}{$selected}{'TIMESTAMP_UPLOADED'} = sprintf ( "%04d%02d%02d_%02d%02d%02d", $year+1900,$mon+1,$mday,$hour,$min,$sec);
    $config{db}{$selected} = $config{images_available}{$selected};
    my $json = encode_json(\%{$config{db}});
    write_file($config{profile}{$profile}{'DBFilepath'}, { binmode => ':raw' }, $json);
}

sub WipeImage {
    &Delimiter((caller(0))[3]);
    
	my $image = $config{image_selected};
    print "Deleting $image...";
    unlink($image) or die "Could not delete $image!\n";
    print " done.\n";
}


# SERVER:
# java -Dwebdriver.gecko.driver=C:\Users\vstbestprices\Seafile\INSTAFEED_SYNC\selenium\geckodriver.exe -jar C:\Users\vstbestprices\Seafile\INSTAFEED_SYNC\selenium\selenium-server-standalone-3.141.59.jar
# java -jar /home/pi/projects/instafeed/selenium/selenium-server-standalone-3.141.59.jar 