package ru.yandex.webmaster3.core.semantic.review_business.biz.model.impl.wrapper;

import ru.yandex.webmaster3.core.semantic.review_business.biz.model.*;
import ru.yandex.webmaster3.core.semantic.review_business.model.Author;
import ru.yandex.webmaster3.core.semantic.review_business.model.LinkToOriginReview;

import java.util.*;

import static com.google.common.collect.Lists.newArrayList;

/**
 * Immutable implementation of {@link BizReview}
 * that on each modification call returns new instance.
 *
 * @author Dima Schitinin <dimas@yandex-team.ru>
 */
public class BizReviewWrapper extends BizReview {

    /**
     * Static factory.
     * This method should be used
     * for obtain instance of wrapper
     * for further manipulations.
     *
     * @return instance of BizReviewWrapper over empty BizReview
     */
    public static BizReviewWrapper newBizReview() {
        return WRAPPER_OVER_EMPTY_BIZ_REVIEW;
    }

    private final BizReview bizReview;

    public BizReviewWrapper(final BizReview bizReview) {
        this.bizReview = bizReview;
    }

    @Override
    public String getLanguage() {
        return bizReview.getLanguage();
    }

    public BizReviewWrapper setLanguage(final String language) {
        return new BizReviewWrapper(this) {
            @Override
            public String getLanguage() {
                return language;
            }
        };
    }

    @Override
    public String getId() {
        return bizReview.getId();
    }

    public BizReviewWrapper setId(final String id) {
        return new BizReviewWrapper(this) {
            @Override
            public String getId() {
                return id;
            }
        };
    }

    @Override
    public Long getBizId() {
        return bizReview.getBizId();
    }

    public BizReviewWrapper setBizId(final Long bizId) {
        return new BizReviewWrapper(this) {
            @Override
            public Long getBizId() {
                return bizId;
            }
        };
    }

    @Override
    public Long getAutoBizId() {
        return bizReview.getAutoBizId();
    }

    public BizReviewWrapper setAutoBizId(final Long autoBizId) {
        return new BizReviewWrapper(this) {
            @Override
            public Long getAutoBizId() {
                return autoBizId;
            }
        };
    }

    @Override
    public Collection<Long> getExcludedBizIds() {
        return bizReview.getExcludedBizIds();
    }

    public BizReviewWrapper setExcludedBizIds(final Collection<Long> ids) {
        return new BizReviewWrapper(this) {
            @Override
            public Collection<Long> getExcludedBizIds() {
                return Collections.unmodifiableCollection(ids);
            }
        };
    }

    public BizReviewWrapper addExcludedBizId(final Long bizId) {
        final Set<Long> ids = new LinkedHashSet<Long>(getExcludedBizIds());
        ids.add(bizId);
        return setExcludedBizIds(ids);
    }

    public BizReviewWrapper removeExcludedBizId(final Long bizId) {
        final Set<Long> ids = new LinkedHashSet<Long>(getExcludedBizIds());
        ids.remove(bizId);
        return setExcludedBizIds(ids);
    }

    @Override
    public Long getChainId() {
        return bizReview.getChainId();
    }

    public BizReviewWrapper setChainId(final Long chainId) {
        return new BizReviewWrapper(this) {
            @Override
            public Long getChainId() {
                return chainId;
            }
        };
    }

    @Override
    public BizCategory getCategory() {
        return bizReview.getCategory();
    }

    public BizReviewWrapper setCategory(final BizCategory category) {
        return new BizReviewWrapper(this) {
            @Override
            public BizCategory getCategory() {
                return category;
            }
        };
    }

    @Override
    public Collection<String> getBizRubrics() {
        return bizReview.getBizRubrics();
    }

    public BizReviewWrapper setBizRubrics(final Collection<String> rubrics) {
        return new BizReviewWrapper(this) {
            @Override
            public Collection<String> getBizRubrics() {
                return Collections.unmodifiableCollection(rubrics);
            }
        };
    }

    @Override
    public Float getRating() {
        return bizReview.getRating();
    }

    public BizReviewWrapper setRating(final Float rating) {
        return new BizReviewWrapper(this) {
            @Override
            public Float getRating() {
                return rating;
            }
        };
    }

    @Override
    public Float getBestRating() {
        return bizReview.getBestRating();
    }

    public BizReviewWrapper setBestRating(final Float bestRating) {
        return new BizReviewWrapper(this) {
            @Override
            public Float getBestRating() {
                return bestRating;
            }
        };
    }

    @Override
    public Float getWorstRating() {
        return bizReview.getWorstRating();
    }

    public BizReviewWrapper setWorstRating(final Float worstRating) {
        return new BizReviewWrapper(this) {
            @Override
            public Float getWorstRating() {
                return worstRating;
            }
        };
    }

    @Override
    public Author getReviewer() {
        return bizReview.getReviewer();
    }


    // For preserve immutability reviewer must be immutable too.
    public BizReviewWrapper setReviewer(final Author reviewer) {
        return new BizReviewWrapper(this) {
            @Override
            public Author getReviewer() {
                return reviewer;
            }
        };
    }

    @Override
    public HCard getItem() {
        return bizReview.getItem();
    }

    // For preserve immutability item must be immutable too.
    public BizReviewWrapper setItem(final HCard item) {
        return new BizReviewWrapper(this) {
            @Override
            public HCard getItem() {
                return item;
            }
        };
    }

    @Override
    public String getSummary() {
        return bizReview.getSummary();
    }

    public BizReviewWrapper setSummary(final String summary) {
        return new BizReviewWrapper(this) {
            @Override
            public String getSummary() {
                return summary;
            }
        };
    }

    @Override
    public String getDescription() {
        return bizReview.getDescription();
    }

    public BizReviewWrapper setDescription(final String description) {
        return new BizReviewWrapper(this) {
            @Override
            public String getDescription() {
                return description;
            }
        };
    }

    @Override
    public Collection<String> getPros() {
        return bizReview.getPros();
    }

    public BizReviewWrapper addPros(final String... pros) {
        return addPros(newArrayList(pros));
    }

    @SuppressWarnings("unchecked")
    public BizReviewWrapper addPros(final Collection<String> pros) {
        return new BizReviewWrapper(this) {
            @Override
            public Collection<String> getPros() {
                return join(super.getPros(), pros);
            }
        };
    }

    public BizReviewWrapper setPros(final String... pros) {
        return setPros(newArrayList(pros));
    }

    public BizReviewWrapper setPros(final Collection<String> pros) {
        return new BizReviewWrapper(this) {
            @Override
            public Collection<String> getPros() {
                return Collections.unmodifiableCollection(pros);
            }
        };
    }

    @Override
    public Collection<String> getContras() {
        return bizReview.getContras();
    }

    public BizReviewWrapper addContras(final String... contras) {
        return addContras(newArrayList(contras));
    }

    @SuppressWarnings("unchecked")
    public BizReviewWrapper addContras(final Collection<String> contras) {
        return new BizReviewWrapper(this) {
            @Override
            public Collection<String> getContras() {
                return join(super.getContras(), contras);
            }
        };
    }

    public BizReviewWrapper setContras(final String... contras) {
        return setContras(newArrayList(contras));
    }

    public BizReviewWrapper setContras(final Collection<String> contras) {
        return new BizReviewWrapper(this) {
            @Override
            public Collection<String> getContras() {
                return Collections.unmodifiableCollection(contras);
            }
        };
    }

    @Override
    public Integer getUsefulCount() {
        return bizReview.getUsefulCount();
    }

    public BizReviewWrapper setUsefulCount(final Integer usefulCount) {
        return new BizReviewWrapper(this) {
            @Override
            public Integer getUsefulCount() {
                return usefulCount;
            }
        };
    }

    @Override
    public Integer getUselessCount() {
        return bizReview.getUselessCount();
    }

    public BizReviewWrapper setUselessCount(final Integer uselessCount) {
        return new BizReviewWrapper(this) {
            @Override
            public Integer getUselessCount() {
                return uselessCount;
            }
        };
    }

    @Override
    public Integer getLikesCount() {
        return bizReview.getLikesCount();
    }

    public BizReviewWrapper setLikesCount(final int likesCount) {
        return new BizReviewWrapper(this) {
            @Override
            public Integer getLikesCount() {
                return likesCount;
            }
        };
    }

    @Override
    public Integer getCommentsCount() {
        return bizReview.getCommentsCount();
    }

    public BizReviewWrapper setCommentsCount(final int commentsCount) {
        return new BizReviewWrapper(this) {
            @Override
            public Integer getUselessCount() {
                return commentsCount;
            }
        };
    }

    @Override
    public Date getReviewedDate() {
        return bizReview.getReviewedDate();
    }

    public BizReviewWrapper setReviewedDate(final Date reviewedDate) {
        return new BizReviewWrapper(this) {
            @Override
            public Date getReviewedDate() {
                return reviewedDate == null ? null : new Date(reviewedDate.getTime());
            }
        };
    }

    @Override
    public String getVisitedDate() {
        return bizReview.getVisitedDate();
    }

    public BizReviewWrapper setVisitedDate(final String visitedDate) {
        return new BizReviewWrapper(this) {
            @Override
            public String getVisitedDate() {
                return visitedDate;
            }
        };
    }

    @Override
    public String getReviewsUrl() {
        return bizReview.getReviewsUrl();
    }

    public BizReviewWrapper setReviewsUrl(final String reviewsUrl) {
        return new BizReviewWrapper(this) {
            @Override
            public String getReviewsUrl() {
                return reviewsUrl;
            }
        };
    }

    @Override
    public String getCommentsUrl() {
        return bizReview.getCommentsUrl();
    }

    public BizReviewWrapper setCommentsUrl(final String commentsUrl) {
        return new BizReviewWrapper(this) {
            @Override
            public String getCommentsUrl() {
                return commentsUrl;
            }
        };
    }

    @Override
    public Collection<Tag> getTags() {
        return bizReview.getTags();
    }

    public BizReviewWrapper setTags(final Tag... tags) {
        return setTags(newArrayList(tags));
    }

    // For preserve immutability each tag must be immutable.
    public BizReviewWrapper setTags(final Collection<Tag> tags) {
        return new BizReviewWrapper(this) {
            @Override
            public Collection<Tag> getTags() {
                return Collections.unmodifiableCollection(tags);
            }
        };
    }

    public BizReviewWrapper addTags(final Tag... tags) {
        return addTags(newArrayList(tags));
    }

    @SuppressWarnings("unchecked")
    public BizReviewWrapper addTags(final Collection<Tag> tags) {
        return new BizReviewWrapper(this) {
            @Override
            public Collection<Tag> getTags() {
                return join(bizReview.getTags(), tags);
            }
        };
    }


    @Override
    public Collection<Tag> getFacts() {
        return bizReview.getFacts();
    }

    public BizReviewWrapper setFacts(final Tag... tags) {
        return setFacts(newArrayList(tags));
    }

    // For preserve immutability each tag must be immutable.
    public BizReviewWrapper setFacts(final Collection<Tag> tags) {
        return new BizReviewWrapper(this) {
            @Override
            public Collection<Tag> getFacts() {
                return Collections.unmodifiableCollection(tags);
            }
        };
    }

    public BizReviewWrapper addFacts(final Tag... tags) {
        return addFacts(newArrayList(tags));
    }

    @SuppressWarnings("unchecked")
    public BizReviewWrapper addFacts(final Collection<Tag> tags) {
        return new BizReviewWrapper(this) {
            @Override
            public Collection<Tag> getFacts() {
                return join(bizReview.getFacts(), tags);
            }
        };
    }

    @Override
    public String getUrl() {
        return bizReview.getUrl();
    }

    public BizReviewWrapper setUrl(final String url) {
        return new BizReviewWrapper(this) {
            @Override
            public String getUrl() {
                return url;
            }
        };
    }

    @Override
    public LinkToOriginReview getOriginReview() {
        return bizReview.getOriginReview();
    }

    public BizReviewWrapper setOriginReview(final LinkToOriginReview link) {
        return new BizReviewWrapper(this) {
            @Override
            public LinkToOriginReview getOriginReview() {
                return link;
            }
        };
    }

    @Override
    public Long getSourceId() {
        return bizReview.getSourceId();
    }

    @Override
    public String getPermanentId() {
        return bizReview.getPermanentId();
    }

    public BizReviewWrapper setSourceId(final Long sourceId) {
        return new BizReviewWrapper(this) {
            @Override
            public Long getSourceId() {
                return sourceId;
            }
        };
    }

    public BizReviewWrapper setPermanentId(final String permanentId) {
        return new BizReviewWrapper(this) {
            @Override
            public String getPermanentId() {
                return permanentId;
            }
        };
    }

    @Override
    public String getVersionId() {
        return bizReview.getVersionId();
    }

    public BizReviewWrapper setVersionId(final String versionId) {
        return new BizReviewWrapper(this) {
            @Override
            public String getVersionId() {
                return versionId;
            }
        };
    }

    @Override
    public boolean isDeleted() {
        return bizReview.isDeleted();
    }

    public BizReviewWrapper setDeleted(final boolean isDeleted) {
        return new BizReviewWrapper(this) {
            @Override
            public boolean isDeleted() {
                return isDeleted;
            }
        };
    }

    @Override
    public List<Moderation2> getModerationHistory() {
        return bizReview.getModerationHistory();
    }

    public BizReviewWrapper setModerationHistory(final List<Moderation2> history) {
        return new BizReviewWrapper(this) {
            @Override
            public List<Moderation2> getModerationHistory() {
                return Collections.unmodifiableList(history);
            }
        };
    }

    public BizReviewWrapper setModerationHistory(final Moderation2... history) {
        return setModerationHistory(newArrayList(history));
    }

    public BizReviewWrapper addModerationHistory(final List<Moderation2> history) {
        return new BizReviewWrapper(this) {
            @Override
            public List<Moderation2> getModerationHistory() {
                return join(bizReview.getModerationHistory(), history);
            }
        };
    }

    public BizReviewWrapper addModerationHistory(final Moderation2... history) {
        return addModerationHistory(newArrayList(history));
    }


    public BizReviewWrapper setGeo(final Geo geo) {
        return new BizReviewWrapper(this) {
            @Override
            public Geo getGeo() {
                return geo;
            }
        };
    }

    @Override
    public Date getWatchTime() {
        return bizReview.getWatchTime();
    }

    public BizReviewWrapper setWatchTime(final Date watchTime) {
        return new BizReviewWrapper(this) {
            @Override
            public Date getWatchTime() {
                return watchTime == null ? null : new Date(watchTime.getTime());
            }
        };
    }

    @Override
    public String getSpecialistId() {
        return bizReview.getSpecialistId();
    }

    public BizReviewWrapper setSpecialistId(final String specialistId) {
        return new BizReviewWrapper(this) {
            @Override
            public String getSpecialistId() {
                return specialistId;
            }
        };
    }

    @Override
    public boolean isVerified() {
        return bizReview.isVerified();
    }

    @Override
    public Geo getGeo() {
        return bizReview.getGeo();
    }


    public BizReviewWrapper setVerified(final boolean isVerified) {
        return new BizReviewWrapper(this) {
            @Override
            public boolean isVerified() {
                return isVerified;
            }
        };
    }


    /**
     * Join collections to new unmodifiable collection.
     * Given collections stays untouched.
     *
     * @param parts collections to be joined
     * @param <T>   type of elements
     * @return new unmodifiable collection that
     *         contains all elements from given collections
     */
    @SuppressWarnings("unchecked")
    public static <T> List<T> join(final Collection<? extends T>... parts) {
        int size = 0;
        for (final Collection<? extends T> part : parts) {
            size += part.size();
        }
        final List<T> result = new ArrayList<T>(size);
        for (final Collection<? extends T> part : parts) {
            result.addAll(part);
        }
        return result;
    }

    private static final BizReviewWrapper WRAPPER_OVER_EMPTY_BIZ_REVIEW =
            new BizReviewWrapper(
                    new BizReview() {
                        @Override
                        public String getId() {
                            return null;
                        }

                        @Override
                        public String getLanguage() {
                            return null;
                        }

                        @Override
                        public Long getBizId() {
                            return null;
                        }

                        @Override
                        public Long getAutoBizId() {
                            return null;
                        }

                        @Override
                        public Collection<Long> getExcludedBizIds() {
                            return Collections.emptyList();
                        }

                        @Override
                        public Long getChainId() {
                            return null;
                        }

                        @Override
                        public BizCategory getCategory() {
                            return null;
                        }

                        @Override
                        public Collection<String> getBizRubrics() {
                            return Collections.emptyList();
                        }

                        @Override
                        public Float getRating() {
                            return null;
                        }

                        @Override
                        public Float getBestRating() {
                            return null;
                        }

                        @Override
                        public Float getWorstRating() {
                            return null;
                        }

                        @Override
                        public Author getReviewer() {
                            return null;
                        }

                        @Override
                        public HCard getItem() {
                            return null;
                        }

                        @Override
                        public String getSummary() {
                            return null;
                        }

                        @Override
                        public String getDescription() {
                            return null;
                        }

                        @Override
                        public Collection<String> getPros() {
                            return Collections.emptyList();
                        }

                        @Override
                        public Collection<String> getContras() {
                            return Collections.emptyList();
                        }

                        @Override
                        public Integer getUsefulCount() {
                            return null;
                        }

                        @Override
                        public Integer getUselessCount() {
                            return null;
                        }

                        @Override
                        public Integer getLikesCount() {
                            return null;
                        }

                        @Override
                        public Integer getCommentsCount() {
                            return null;
                        }

                        @Override
                        public Date getReviewedDate() {
                            return null;
                        }

                        @Override
                        public String getVisitedDate() {
                            return null;
                        }

                        @Override
                        public String getReviewsUrl() {
                            return null;
                        }

                        @Override
                        public String getCommentsUrl() {
                            return null;
                        }

                        @Override
                        public Collection<Tag> getTags() {
                            return Collections.emptyList();
                        }

                        @Override
                        public Collection<Tag> getFacts() {
                            return Collections.emptyList();
                        }

                        @Override
                        public String getUrl() {
                            return null;
                        }

                        @Override
                        public LinkToOriginReview getOriginReview() {
                            return null;
                        }

                        @Override
                        public Long getSourceId() {
                            return null;
                        }

                        @Override
                        public String getPermanentId() {
                            return null;
                        }

                        @Override
                        public String getVersionId() {
                            return null;
                        }

                        @Override
                        public boolean isDeleted() {
                            return false;
                        }

                        @Override
                        public List<Moderation2> getModerationHistory() {
                            return Collections.emptyList();
                        }

                        @Override
                        public Date getWatchTime() {
                            return null;
                        }

                        @Override
                        public String getSpecialistId() {
                            return null;
                        }

                        @Override
                        public boolean isVerified() {
                            return false;
                        }

                        @Override
                        public Geo getGeo() {
                            return null;
                        }
                    }
            );

}

