001package net.filebot.web;
002
003import java.util.List;
004import java.util.Objects;
005import java.util.function.BiFunction;
006import java.util.function.Function;
007import java.util.stream.Stream;
008
009public class MappedEpisode extends Episode {
010
011        protected Episode original;
012        protected Episode mapping;
013
014        public MappedEpisode() {
015                // used by deserializer
016        }
017
018        public MappedEpisode(Episode original, Episode mapping) {
019                super(original);
020                this.original = unmap(original, MappedEpisode::getOriginal);
021                this.mapping = unmap(mapping, MappedEpisode::getMapping);
022        }
023
024        public Episode getOriginal() {
025                return original;
026        }
027
028        public Episode getMapping() {
029                return mapping;
030        }
031
032        private <T> T getFirst(Function<Episode, T> getter) {
033                T value = getter.apply(mapping);
034                if (value != null) {
035                        return value;
036                }
037                return getter.apply(original);
038        }
039
040        @Override
041        public String getSeriesName() {
042                return getFirst(Episode::getSeriesName);
043        }
044
045        @Override
046        public Integer getEpisode() {
047                return mapping.getEpisode(); // always use mapped episode number
048        }
049
050        @Override
051        public Integer getSeason() {
052                return mapping.getSeason(); // always use mapped season number
053        }
054
055        @Override
056        public String getTitle() {
057                return mapping.getTitle(); // always use mapped episode title
058        }
059
060        @Override
061        public Integer getAbsolute() {
062                return mapping.getAbsolute(); // always use mapped absolute number
063        }
064
065        @Override
066        public Integer getSpecial() {
067                return mapping.getSpecial(); // always use mapped special number
068        }
069
070        @Override
071        public SimpleDate getAirdate() {
072                return mapping.getAirdate(); // always use mapped episode airdate
073        }
074
075        @Override
076        public Integer getId() {
077                return getFirst(Episode::getId);
078        }
079
080        @Override
081        public List<String> getSeriesNames() {
082                return getFirst(Episode::getSeriesNames);
083        }
084
085        @Override
086        public boolean equals(Object obj) {
087                return super.equals(obj); // use original episode object for episode comparison
088        }
089
090        @Override
091        public int hashCode() {
092                return super.hashCode(); // use original episode object for episode comparison
093        }
094
095        @Override
096        public MappedEpisode clone() {
097                return new MappedEpisode(original, mapping);
098        }
099
100        @Override
101        public MappedEpisode derive(Integer season, Integer episode) {
102                return new MappedEpisode(original, mapping.derive(season, episode));
103        }
104
105        @Override
106        public MappedEpisode derive(String seriesName, Integer season, Integer episode, String title, Integer absolute, SimpleDate airdate) {
107                return new MappedEpisode(original, mapping.derive(seriesName, season, episode, title, absolute, airdate));
108        }
109
110        public MappedEpisode reverse() {
111                return new MappedEpisode(mapping, original);
112        }
113
114        public MappedEpisode flatten() {
115                return new MappedEpisode(mapping, mapping);
116        }
117
118        @Override
119        public String toString() {
120                return mapping + " => " + original;
121        }
122
123        public static Stream<Episode> generate(Episode original, Episode[] mapping, BiFunction<Episode, Episode, Episode> wrapper) {
124                // use null as identity mapping
125                if (mapping == null) {
126                        return Stream.of(original);
127                }
128
129                // deduplicate by object identity
130                for (int i = 0; i < mapping.length; i++) {
131                        for (int c = i + 1; c < mapping.length; c++) {
132                                if (mapping[i] == mapping[c]) {
133                                        mapping[c] = null;
134                                }
135                        }
136                }
137
138                return Stream.of(mapping).filter(Objects::nonNull).map(m -> {
139                        // ignore identity mapping
140                        if (original == m) {
141                                return original;
142                        }
143                        return wrapper.apply(original, m);
144                });
145        }
146
147        public static Episode map(Episode original, Episode mapping) {
148                // pass through already mapped episode objects (e.g. reverse() or flatten() usage)
149                if (mapping instanceof MappedEpisode) {
150                        return mapping;
151                }
152                return new MappedEpisode(original, mapping);
153        }
154
155        public static Episode unmap(Episode episode, Function<MappedEpisode, Episode> original) {
156                if (episode instanceof MappedEpisode) {
157                        MappedEpisode mapping = (MappedEpisode) episode;
158                        episode = original.apply(mapping);
159                }
160                return episode;
161        }
162
163}