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

import org.json.JSONException;
import ru.yandex.webmaster3.core.semantic.review_business.ModelUtils;
import ru.yandex.webmaster3.core.semantic.review_business.biz.model.impl.json.BizReviewJsonConversions;
import ru.yandex.webmaster3.core.semantic.review_business.model.Author;
import ru.yandex.webmaster3.core.semantic.review_business.model.LinkToOriginReview;

import java.util.*;

/**
 * Represents review on the business.
 * We want that our representation were as close as possible to
 * hReview microformat.
 * For details see http://microformats.org/wiki/hreview
 * <p/>
 * Getters of this class are used to count hashCode, so to keep contract (see first point in {@link Object#hashCode()})
 * they should return objects which have same hashCode value during application run time
 * In general - implementation should be immutable and return references which are also immutable
 * <p/>
 *
 * @author Dima Schitinin <dimas@yandex-team.ru>
 */
public abstract class BizReview {
    public static final float NORMALIZED_WORST_RATING = 1.0f;
    public static final float NORMALIZED_BEST_RATING = 5.0f;

    /**
     * Language of the review according to "ISO 639-3" standard.
     * It may be constructor argument of java.util.Locale
     */
    public abstract String getLanguage();

    public abstract String getId();

    /**
     * Type of the review.
     * For biz reviews it always should be "business"
     *
     * @return "business"
     */
    public String getType() {
        return "business";
    }


    public abstract Long getBizId();

    /**
     * Export-id from Addresses BackOffice (backa.yandex.ru)
     * that has been obtained by matching with Backa.
     *
     * @return export-id from Backa if review
     *         has been successfully matched or null otherwise
     */
    public abstract Long getAutoBizId();

    public abstract Collection<Long> getExcludedBizIds();

    public abstract Long getChainId();

    /**
     * @return Business category. For example, "restaurant" or "hotel"
     */
    public abstract BizCategory getCategory();

    /**
     * @return Collection of rubric IDs of biz under review
     */
    public abstract Collection<String> getBizRubrics();

    /**
     * Rating from user.
     * By default it's float number between 1 and 5.
     *
     * @return rating from user
     *         or null if rating is undefined
     */
    public abstract Float getRating();

    /**
     * Upper bound of possible rating value.
     * Typically it should be used for scale rating.
     *
     * @return possible max rating value
     */
    public abstract Float getBestRating();

    /**
     * Lower bound of possible rating value.
     * Typically it should be used for scale rating.
     *
     * @return possible min rating value
     */
    public abstract Float getWorstRating();

    public Float getNormalizedRating() {
        if (getRating() == null) {
            return null;
        } else {
            float worstRating = getWorstRating() != null ? getWorstRating() : NORMALIZED_WORST_RATING;
            float bestRating = getBestRating() != null ? getBestRating() : NORMALIZED_BEST_RATING;

            return getNormalizedRating(getRating(), bestRating, worstRating);
        }
    }


    public static Float getNormalizedRating(Float rating, Float best, Float worst) {
        Float result = null;
        if (rating >= worst && rating <= best)
            result = (rating - worst + 1) * (BizReview.NORMALIZED_BEST_RATING - BizReview.NORMALIZED_WORST_RATING + 1) /
                    (best - worst + 1);
        return result;
    }

    /**
     * Information about review author.
     *
     * @return info about author
     *         or null if one is unavailable
     */
    public abstract Author getReviewer();

    /**
     * Information about review item
     * (e.g. object that review is about)
     * presented in hCard format (http://microformats.org/wiki/hcard)
     *
     * @return info about review object
     *         or null if one is unavailable
     */
    public abstract HCard getItem();

    /**
     * This optional property serves as a short synopsis,
     * title, or name of the review.
     *
     * @return non-empty string or
     *         null if doesn't specified
     */
    public abstract String getSummary();

    /**
     * This optional property serves as a more detailed synopsis of the review
     * (i.e. full text representing the written opinion of the reviewer)
     * than that provided by summary.
     *
     * @return non-empty string or
     *         null if description doesn't specified
     */
    public abstract String getDescription();

    /**
     * Qualities of object under review.
     *
     * @return number of strings
     *         or empty collection if no pro is specified
     */
    public abstract Collection<String> getPros();

    /**
     * Shortcomings of object under review.
     *
     * @return number of strings
     *         or empty collection if no contra is specified
     */
    public abstract Collection<String> getContras();

    /**
     * Number of users considered review is useful.
     */
    public abstract Integer getUsefulCount();

    /**
     * Number of users considered review is useless.
     */
    public abstract Integer getUselessCount();

    /**
     * Number of likes of review.
     */
    public abstract Integer getLikesCount();

    /**
     * Number of comments on review.
     */
    public abstract Integer getCommentsCount();

    /**
     * Date of review creation
     */
    public abstract Date getReviewedDate();

    /**
     * Date of visit business under review.
     */
    public abstract String getVisitedDate();

    /**
     * Optional hub page that contains more reviews on the same object
     */
    public abstract String getReviewsUrl();

    /**
     * Optional of page with comments on given review.
     */
    public abstract String getCommentsUrl();

    /**
     * Tags is extension point of review.
     * It may contain individual ratings by object's features.
     *
     * @return number of tags
     *         or empty collection if no tags specified
     */
    public abstract Collection<Tag> getTags();

    /**
     * Facts is extension point of review.
     * It may contain individual ratings by object's features (extracted from review text).
     *
     * @return number of facts
     *         or empty collection if no tags specified
     */
    public abstract Collection<Tag> getFacts();

    /**
     * @return URL of the page that contains review
     */
    public abstract String getUrl();

    public abstract LinkToOriginReview getOriginReview();

    /**
     * Identity of the source-origin of review.
     * This ID assigned to every review that has been added to the system.
     *
     * @return source ID from the system
     */
    public abstract Long getSourceId();

    /**
     * Permanent ID is identifier of review which is not changed between versions
     *
     * @return permanent ID of review (32 symbols hash)
     */
    public abstract String getPermanentId();

    /**
     * Version ID is identifier of review which is changed during review modification
     *
     * @return version ID of review (32 symbols hash)
     */
    public abstract String getVersionId();

    /**
     * Indicates whether review deleted or not.
     * Mostly used by ugc reviews. There is special field for this purpose in MySql table named 'is_deleted'.
     */
    public abstract boolean isDeleted();

    /**
     * History of review's moderation. May be empty or null.
     */
    public abstract List<Moderation2> getModerationHistory();

    /**
     * Indicates has review moderation or not.
     * Simply check moderation history for emptiness.
     */
    public boolean hasModeration() {
        final List<Moderation2> history = getModerationHistory();
        return (history != null) && (!history.isEmpty());
    }

    /**
     * @return last (actual) moderation of review or null if there's no moderation.
     */
    public Moderation2 getLastModeration() {
        final List<Moderation2> history = getModerationHistory();
        if (history == null || history.isEmpty())
            return null;
        return history.get(history.size() - 1);
    }

    public abstract Date getWatchTime();

    public abstract String getSpecialistId();

    public abstract boolean isVerified();

    /**
     * Detected geo-coordinates of a place, where the review
     * is supposed to have been written.
     */
    public abstract Geo getGeo();

    @Override
    public boolean equals(final Object o) {
        if (this == o) return true;
        if (!(o instanceof BizReview)) return false;

        final BizReview bizReview = (BizReview) o;

        return hashCode() == bizReview.hashCode()
                && ModelUtils.equals(getBizId(), bizReview.getBizId())
                && ModelUtils.equals(getChainId(), bizReview.getChainId())
                && ModelUtils.equals(getRating(), bizReview.getRating())
                && ModelUtils.equals(getBestRating(), bizReview.getBestRating())
                && ModelUtils.equals(getWorstRating(), bizReview.getWorstRating())
                && ModelUtils.equals(getReviewer(), bizReview.getReviewer())
                && ModelUtils.equals(getItem(), bizReview.getItem())
                && ModelUtils.equals(getLanguage(), bizReview.getLanguage())
                && ModelUtils.equals(getSummary(), bizReview.getSummary())
                && ModelUtils.equals(getDescription(), bizReview.getDescription())
                && ModelUtils.equals(getPros(), bizReview.getPros())
                && ModelUtils.equals(getContras(), bizReview.getContras())
                && ModelUtils.equals(getUsefulCount(), bizReview.getUsefulCount())
                && ModelUtils.equals(getUselessCount(), bizReview.getUselessCount())
                && ModelUtils.equals(getLikesCount(), bizReview.getLikesCount())
                && ModelUtils.equals(getCommentsCount(), bizReview.getCommentsCount())
                && ModelUtils.equals(getVisitedDate(), bizReview.getVisitedDate())
                && ModelUtils.equals(getReviewedDate(), bizReview.getReviewedDate())
                && ModelUtils.equals(getReviewsUrl(), bizReview.getReviewsUrl())
                && ModelUtils.equals(getCommentsUrl(), bizReview.getCommentsUrl())
                && ModelUtils.equals(getTags(), bizReview.getTags())
                && ModelUtils.equals(getUrl(), bizReview.getUrl())
                && ModelUtils.equals(getSourceId(), bizReview.getSourceId())
                && ModelUtils.equals(getPermanentId(), bizReview.getPermanentId())
                && ModelUtils.equals(getVersionId(), bizReview.getVersionId())
                && ModelUtils.equals(getModerationHistory(), bizReview.getModerationHistory())
                && ModelUtils.equals(getWatchTime(), bizReview.getWatchTime())
                && ModelUtils.equals(getSpecialistId(), bizReview.getSpecialistId())
                && ModelUtils.equals(isVerified(), bizReview.isVerified())
                && ModelUtils.equals(getGeo(), bizReview.getGeo())
                && ModelUtils.equals(getFacts(), bizReview.getFacts())
                ;
    }

    @Override
    public int hashCode() {
        return ModelUtils.hashCode(
                getBizId()
                , getChainId()
                , getRating()
                , getBestRating()
                , getWorstRating()
                , getReviewer()
                , getItem()
                , getLanguage()
                , getSummary()
                , getDescription()
                , getPros()
                , getContras()
                , getUsefulCount()
                , getUselessCount()
                , getLikesCount()
                , getCommentsCount()
                , getVisitedDate()
                , getReviewedDate()
                , getReviewsUrl()
                , getCommentsUrl()
                , getTags()
                , getUrl()
                , getSourceId()
                , getPermanentId()
                , getVersionId()
                , getModerationHistory()
                , getWatchTime()
                , getSpecialistId()
                , isVerified()
                , getGeo()
                , getFacts()
        );
    }

    /**
     * Construct string representation of BizReview.
     */
    @Override
    public String toString() {
        try {
            return BizReviewJsonConversions.toJson(this).toString(1);
        } catch (JSONException e) {
            throw new AssertionError("Not expected to be thrown");
        }
    }
}
