package moviescraper.doctord.controller.amalgamation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import moviescraper.doctord.model.Movie;
import moviescraper.doctord.model.dataitem.Actor;
import moviescraper.doctord.model.dataitem.Director;
import moviescraper.doctord.model.dataitem.Genre;
import moviescraper.doctord.model.dataitem.ID;
import moviescraper.doctord.model.dataitem.MPAARating;
import moviescraper.doctord.model.dataitem.MovieDataItem;
import moviescraper.doctord.model.dataitem.OriginalTitle;
import moviescraper.doctord.model.dataitem.Outline;
import moviescraper.doctord.model.dataitem.Plot;
import moviescraper.doctord.model.dataitem.Rating;
import moviescraper.doctord.model.dataitem.ReleaseDate;
import moviescraper.doctord.model.dataitem.Runtime;
import moviescraper.doctord.model.dataitem.Set;
import moviescraper.doctord.model.dataitem.SortTitle;
import moviescraper.doctord.model.dataitem.Studio;
import moviescraper.doctord.model.dataitem.Tag;
import moviescraper.doctord.model.dataitem.Tagline;
import moviescraper.doctord.model.dataitem.Thumb;
import moviescraper.doctord.model.dataitem.Title;
import moviescraper.doctord.model.dataitem.Top250;
import moviescraper.doctord.model.dataitem.Trailer;
import moviescraper.doctord.model.dataitem.Votes;
import moviescraper.doctord.model.dataitem.Year;
/**
* Collection of all the {@link Movie} objects for a given file that have been scraped along with the
* ranked preferences of what sites we prefer to use data from when amalgamating
*
*/
public class MovieScrapeResultGroup {
List<Movie> scrapedMovieObjectsForFile;
//This preference applies to all data items in a given movie. If we want a custom ordering per item, we look at the DataItemSourceAmalgationPreference within the data item itself
//DataItemSourceAmalgamationPreference amalgamationPreferenceOrderForEntireMovieGroup;
ScraperGroupAmalgamationPreference amalgamationPreferenceOrderForEntireMovieGroup;
/**
* Constructor
* @param scrapedMovieObjectsForFile - all the movies you wish to amalgamate
* @param amalgamationPreferenceOrder - the preference of which field you prefer. Items earlier in the list have a higher preference to being picked when amalgamating,
*/
public MovieScrapeResultGroup(List<Movie> scrapedMovieObjectsForFile, ScraperGroupAmalgamationPreference amalgamationPreferenceOrder)
{
this.scrapedMovieObjectsForFile = scrapedMovieObjectsForFile;
this.amalgamationPreferenceOrderForEntireMovieGroup = amalgamationPreferenceOrder;
}
private ArrayList<?> getPreferredMovieDataItemAsArrayList(Class<?> classOfMovieDataItem) throws SecurityException, IllegalAccessException, IllegalArgumentException
{
ArrayList<?> arrayList;
arrayList = getPreferredMovieDataItem(classOfMovieDataItem).length > 0 ? (ArrayList<?>) getPreferredMovieDataItem(classOfMovieDataItem)[0] : new ArrayList<>();
System.out.println("Amalgamated " + classOfMovieDataItem.toString().replace("class moviescraper.doctord.model.dataitem.", "") + " is " + arrayList);
return arrayList;
}
private MovieDataItem getPreferredMovieDataItemAsMovieDataItem(Class<?> classOfMovieDataItem) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
MovieDataItem result = getPreferredMovieDataItem(classOfMovieDataItem).length > 0 ? (MovieDataItem) getPreferredMovieDataItem(classOfMovieDataItem)[0] : null;
if(result == null)
{
//call the default constructor
Constructor<?>[] ctors = classOfMovieDataItem.getDeclaredConstructors();
for(Constructor<?> currentConstructor : ctors)
{
if(currentConstructor.getParameterTypes().length == 0)
{
//We are the default zero param constructor
return (MovieDataItem)currentConstructor.newInstance();
}
}
throw new NoSuchMethodException("Didn't find default constructor of class = " + classOfMovieDataItem);
}
System.out.println("Amalgamated " + classOfMovieDataItem.toString().replace("class moviescraper.doctord.model.dataitem.", "") + " is " + result);
return result;
}
@SuppressWarnings("unchecked")
public Movie amalgamateMovie()
{
if(scrapedMovieObjectsForFile == null || scrapedMovieObjectsForFile.size() == 0)
return null;
if(scrapedMovieObjectsForFile.size() == 1)
{
System.out.println("Skipping amalgamation process as there is only one movie");
Movie amalgamatedMovie = scrapedMovieObjectsForFile.get(0);
if(scrapedMovieObjectsForFile != null && scrapedMovieObjectsForFile.size() > 0){
amalgamatedMovie.setFileName(scrapedMovieObjectsForFile.get(0).getFileName());
}
return amalgamatedMovie;
}
System.out.println("Amalgamating a movie between " + scrapedMovieObjectsForFile.size() + " Movie objects with preference order = " + amalgamationPreferenceOrderForEntireMovieGroup.toString());
try {
callAmalgamateActorOnAllTuples(scrapedMovieObjectsForFile);
ArrayList<Actor> actors = (ArrayList<Actor>) getPreferredMovieDataItemAsArrayList(Actor.class);
ArrayList<Director> directors = (ArrayList<Director>) getPreferredMovieDataItemAsArrayList(Director.class);
ArrayList<Genre> genres = (ArrayList<Genre>) getPreferredMovieDataItemAsArrayList(Genre.class);
ArrayList<Tag> tags = (ArrayList<Tag>) getPreferredMovieDataItemAsArrayList(Tag.class);
Thumb[] fanart = getPreferredArrayMovieDataItem("fanart");
Thumb[] extraFanart = getPreferredArrayMovieDataItem("extraFanart");
Thumb[] posters = getPreferredArrayMovieDataItem("posters");
ID id = (ID) getPreferredMovieDataItemAsMovieDataItem(ID.class);
MPAARating mpaa = (MPAARating) getPreferredMovieDataItemAsMovieDataItem(MPAARating.class);
OriginalTitle originalTitle = (OriginalTitle) getPreferredMovieDataItemAsMovieDataItem(OriginalTitle.class);
Outline outline = (Outline) getPreferredMovieDataItemAsMovieDataItem(Outline.class);
Plot plot = (Plot) getPreferredMovieDataItemAsMovieDataItem(Plot.class);
Rating rating = (Rating) getPreferredMovieDataItemAsMovieDataItem(Rating.class);
ReleaseDate releaseDate = (ReleaseDate) getPreferredMovieDataItemAsMovieDataItem(ReleaseDate.class);
Runtime runtime = (Runtime) getPreferredMovieDataItemAsMovieDataItem(Runtime.class);
Set set = (Set) getPreferredMovieDataItemAsMovieDataItem(Set.class);
SortTitle sortTitle = (SortTitle) getPreferredMovieDataItemAsMovieDataItem(SortTitle.class);
Studio studio = (Studio) getPreferredMovieDataItemAsMovieDataItem(Studio.class);
Tagline tagline = (Tagline) getPreferredMovieDataItemAsMovieDataItem(Tagline.class);
Title title = (Title) getPreferredMovieDataItemAsMovieDataItem(Title.class);
Top250 top250 = (Top250) getPreferredMovieDataItemAsMovieDataItem(Top250.class);
Trailer trailer = (Trailer) getPreferredMovieDataItemAsMovieDataItem(Trailer.class);
Votes votes = (Votes) getPreferredMovieDataItemAsMovieDataItem(Votes.class);
Year year = (Year) getPreferredMovieDataItemAsMovieDataItem(Year.class);
Movie amalgamatedMovie = new Movie(actors, directors, fanart,
extraFanart, genres, tags, id, mpaa, originalTitle, outline,
plot, posters, rating, releaseDate, runtime, set, sortTitle, studio,
tagline, title, top250, trailer, votes, year);
//The all titles at this point is just the file name, which is the same for all so we can just use the first one
if(scrapedMovieObjectsForFile != null && scrapedMovieObjectsForFile.size() > 0){
amalgamatedMovie.setFileName(scrapedMovieObjectsForFile.get(0).getFileName());
}
return amalgamatedMovie;
} catch (NoSuchMethodException | SecurityException
| InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException | NoSuchFieldException e) {
e.printStackTrace();
}
return null;
}
/**
* Helper method to call amalgamateActor on all possible tuples (where order matters) of our scraped movie list
* @param allScrapedMovies - the scraped movie list
*/
private void callAmalgamateActorOnAllTuples(List<Movie> allScrapedMovies)
{
if(allScrapedMovies != null && allScrapedMovies.size() >= 2)
{
for(int i = 0; i < allScrapedMovies.size(); i++){
for(int j = i + 1; j < allScrapedMovies.size(); j++)
{
Movie tupleValue1 = allScrapedMovies.get(i);
Movie tupleValue2 = allScrapedMovies.get(j);
amalgamateActor(tupleValue1, tupleValue2);
amalgamateActor(tupleValue2, tupleValue1);
}
}
}
}
/**
* try to fill in any holes in thumbnails from the sourceMovie by looking through movieToGetExtraInfoFrom and see if it has them
* @param sourceMovie - movie that has missing actor images
* @param movieToGetExtraInfoFrom - other movie to try to get the missing actor images from
* @return
*/
private ArrayList<Actor> amalgamateActor(Movie sourceMovie, Movie movieToGetExtraInfoFrom)
{
ArrayList<Actor> amalgamatedActorList = new ArrayList<>();
boolean changeMade = false;
if(sourceMovie.getActors() != null && movieToGetExtraInfoFrom.getActors() != null)
{
for(Actor currentActor : sourceMovie.getActors())
{
if(currentActor.getThumb() == null || currentActor.getThumb().getThumbURL().getPath().length() < 1)
{
//Found an actor with no thumbnail in sourceMovie
for(Actor extraMovieActor: movieToGetExtraInfoFrom.getActors())
{
//scan through other movie and find actor with same name as the one we are currently on
if(currentActor.getName().equals(extraMovieActor.getName()) && (extraMovieActor.getThumb() != null) && extraMovieActor.getThumb().getThumbURL().getPath().length() > 1)
{
currentActor.setThumb(extraMovieActor.getThumb());
changeMade = true;
}
}
}
amalgamatedActorList.add(currentActor);
}
}
if(changeMade)
{
return amalgamatedActorList;
}
else return sourceMovie.getActors(); // we didn't find any changes needed so just return the source movie's actor list
}
private Object[] getPreferredMovieDataItem(@SuppressWarnings("rawtypes") Class classOfMovieDataItem)
throws SecurityException,
IllegalAccessException,
IllegalArgumentException {
Object[] preferredValueOrder = new Object[amalgamationPreferenceOrderForEntireMovieGroup
.getOverallAmalgamationPreference()
.getAmalgamationPreferenceOrder().size()];
boolean fieldIsArray = false;
boolean fieldIsArrayList = false;
boolean fieldIsMovieDataItem = false;
// For each movie, find the field that matches the class passed in
// for that Field, put it in the index matching the preference order
for (Movie currentMovie : scrapedMovieObjectsForFile) {
for (Field field : currentMovie.getClass().getDeclaredFields()) {
field.setAccessible(true);
Object currentFieldValue = field.get(currentMovie);
if (currentFieldValue != null) {
// Case for MovieDataItem
if (currentFieldValue.getClass().equals(
classOfMovieDataItem)) {
Object item = (MovieDataItem) currentFieldValue;
DataItemSourceAmalgamationPreference amalgamationPrefToUse = amalgamationPreferenceOrderForEntireMovieGroup
.getAmalgamationPreference(field);
for (int i = 0; i < amalgamationPrefToUse
.getAmalgamationPreferenceOrder().size(); i++) {
if (((MovieDataItem) item)
.getDataItemSource()
.toString()
.equals(amalgamationPrefToUse
.getAmalgamationPreferenceOrder()
.get(i).toString()) && ((MovieDataItem) item).isStringValueEmpty()) {
preferredValueOrder[i] = item;
fieldIsMovieDataItem = true;
}
}
}
// Case for ArrayList
else if (currentFieldValue.getClass().equals(
ArrayList.class)) {
ParameterizedType paramType = (ParameterizedType) field
.getGenericType();
Class<?> arrayListClass = (Class<?>) paramType
.getActualTypeArguments()[0];
@SuppressWarnings("unchecked")
ArrayList<Object> arrayList = (ArrayList<Object>) currentFieldValue;
if (arrayListClass.equals(classOfMovieDataItem)) {
// The data item itself has a preference it wants to
// amalgamate by, so we will use it instead of the
// Movie's one
DataItemSourceAmalgamationPreference amalgamationPrefToUse = amalgamationPreferenceOrderForEntireMovieGroup
.getAmalgamationPreference(field);
for (int i = 0; i < amalgamationPrefToUse
.getAmalgamationPreferenceOrder().size(); i++) {
if (arrayList.size() > 0) {
MovieDataItem firstItem = (MovieDataItem) arrayList
.get(0);
if (firstItem
.getDataItemSource()
.toString()
.equals(amalgamationPrefToUse
.getAmalgamationPreferenceOrder()
.get(i).toString())) {
preferredValueOrder[i] = arrayList;
fieldIsArrayList = true;
}
}
}
}
}
}
}
}
//MovieDataItem return
//the first non null item in the list is the highest preferred value
if(fieldIsMovieDataItem)
{
for(int j = 0; j< preferredValueOrder.length; j++)
{
if(preferredValueOrder[j] != null)
{
MovieDataItem[] returnValue = {(MovieDataItem)preferredValueOrder[j]};
return returnValue;
}
}
}
else if(fieldIsArrayList)
{
//ArrayList Return
//the first non null item in the list is the highest preferred value
for(int i = 0; i< preferredValueOrder.length; i++)
{
if(preferredValueOrder[i] != null)
{
@SuppressWarnings("rawtypes")
Object[] returnValue = {(ArrayList)preferredValueOrder[i]};
return returnValue;
}
}
}
else if(fieldIsArray)
{
//do nothing for now - this is handled in another method
//System.out.println("need to return that array...");
}
//nothing found otherwise
return new Object[0];
}
//used for thumb arrays and such
private Thumb[] getPreferredArrayMovieDataItem(String fieldName) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
ArrayList<Thumb[]> preferredValueOrder = new ArrayList<>(amalgamationPreferenceOrderForEntireMovieGroup.getOverallAmalgamationPreference().getAmalgamationPreferenceOrder().size());
for(int i = 0; i < amalgamationPreferenceOrderForEntireMovieGroup.getAmalgamationPreference(null).getAmalgamationPreferenceOrder().size(); i++)
{
preferredValueOrder.add(null);
}
for(Movie currentMovie : scrapedMovieObjectsForFile)
{
Field fieldByName = currentMovie.getClass().getDeclaredField(fieldName);
fieldByName.setAccessible(true);
Thumb[] value = (Thumb [])fieldByName.get(currentMovie);
if(value != null)
{
DataItemSourceAmalgamationPreference amalgamationPrefToUse = amalgamationPreferenceOrderForEntireMovieGroup.getAmalgamationPreference(fieldByName);
for (int i = 0; i < amalgamationPrefToUse.getAmalgamationPreferenceOrder().size(); i++)
{
if(value.length > 0 && value[0].getDataItemSource().toString().equals(amalgamationPrefToUse.getAmalgamationPreferenceOrder().get(i).toString()))
{
preferredValueOrder.set(i, value);
}
}
}
}
for(int j = 0; j< preferredValueOrder.size(); j++)
{
if(preferredValueOrder.get(j) != null)
{
Thumb[] returnValue = preferredValueOrder.get(j);
System.out.println("Amalgamated " + fieldName + " is " + Arrays.toString(returnValue));
return returnValue;
}
}
//nothing found otherwise
Thumb[] emptyThumb = new Thumb[0];
System.out.println("Amalgamated " + fieldName + " is " + Arrays.toString(emptyThumb));
return emptyThumb;
}
}