/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.streams.facebook.serializer;

import org.apache.streams.data.util.ActivityUtil;
import org.apache.streams.exceptions.ActivitySerializerException;
import org.apache.streams.facebook.Cover;
import org.apache.streams.facebook.Datum;
import org.apache.streams.facebook.Like;
import org.apache.streams.facebook.Location;
import org.apache.streams.facebook.Page;
import org.apache.streams.facebook.Place;
import org.apache.streams.facebook.Post;
import org.apache.streams.facebook.To;
import org.apache.streams.jackson.StreamsJacksonMapper;
import org.apache.streams.pojo.extensions.ExtensionUtil;
import org.apache.streams.pojo.json.Activity;
import org.apache.streams.pojo.json.ActivityObject;
import org.apache.streams.pojo.json.Image;
import org.apache.streams.pojo.json.Provider;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * FacebookActivityUtil helps convert facebook data to activity formats.
 */
public class FacebookActivityUtil {
  private static final Logger LOGGER = LoggerFactory.getLogger(FacebookActivityUtil.class);

  /**
   * Updates the given Activity object with the values from the Page.
   * @param page the object to use as the source
   * @param activity the target of the updates.  Will receive all values from the Page.
   * @throws ActivitySerializerException
   */
  public static void updateActivity(Page page, Activity activity) throws ActivitySerializerException {
    activity.setActor(buildActor(page));
    activity.setId(null);
    activity.setProvider(getProvider());
  }

  /**
   * Updates the given Activity object with the values from the Post.
   * @param post post
   * @param activity activity
   * @throws ActivitySerializerException
   */
  public static void updateActivity(Post post, Activity activity) throws ActivitySerializerException {
    activity.setActor(buildActor(post));
    activity.setId(formatId(post.getId()));
    activity.setProvider(getProvider());
    activity.setUpdated(post.getUpdatedTime());
    activity.setPublished(post.getCreatedTime());

    if (post.getLink() != null && post.getLink().length() > 0) {
      List<String> links = new ArrayList<>();
      links.add(post.getLink());
      activity.setLinks(links);
    }

    activity.setContent(post.getMessage());

    activity.setVerb("post");
    activity.setObject(buildObject(post));
    buildExtensions(activity, post);
  }

  /**
   * Builds out the {@link ActivityObject} from the given {@link Post}.
   * @param post
   * @return {@link ActivityObject}
   */
  public static ActivityObject buildObject(Post post) {
    ActivityObject activityObject = new ActivityObject();

    try {
      activityObject.setContent(post.getMessage());
      activityObject.setPublished(post.getCreatedTime());
      activityObject.setUpdated(post.getUpdatedTime());
      activityObject.setDisplayName(post.getFrom().getName());
      activityObject.setId(formatId(post.getId()));
      activityObject.setObjectType(post.getType());
      activityObject.setUrl(post.getLink());

      if (activityObject.getObjectType().equals("photo")) {
        Image image = new Image();
        image.setUrl(activityObject.getUrl());
        activityObject.setImage(image);
      }
    } catch (Exception ex) {
      LOGGER.error("Exception while trying to build Activity object for post: {}, exception: {}", post, ex);
    }

    return activityObject;
  }

  /**
   * Gets the common facebook {@link Provider} object.
   * @return a provider object representing Facebook
   */
  public static Provider getProvider() {
    Provider provider = new Provider();
    provider.setId("id:providers:facebook");
    provider.setDisplayName("Facebook");

    return provider;
  }

  /**
   * Builds the activity {@link ActivityObject} actor from the Page.
   * @param page the object to use as the source
   * @return a valid Actor populated from the Page
   */
  public static ActivityObject buildActor(Page page) {
    ActivityObject actor = new ActivityObject();
    actor.setId(formatId(
        Optional.ofNullable(Optional.ofNullable(page.getId())
            .orElseGet(Optional.of(page.getId())::get)).orElse(null)
    ));

    actor.setDisplayName(page.getName());
    actor.setAdditionalProperty("handle", page.getUsername());
    actor.setSummary(page.getAbout());

    if (page.getLink() != null) {
      actor.setUrl(page.getLink());
    }

    Image profileImage = new Image();
    Cover cover = page.getCover();

    if (cover != null) {
      profileImage.setUrl(cover.getSource());
    }
    actor.setImage(profileImage);

    buildExtensions(actor, page);

    return actor;
  }

  /**
   * Builds an {@link ActivityObject} object from the {@link Post}.
   * @param post post
   * @return {@link ActivityObject}
   */
  public static ActivityObject buildActor(Post post) {
    ActivityObject actor = new ActivityObject();

    try {
      actor.setId(formatId(
          Optional.ofNullable(Optional.ofNullable(post.getFrom().getId())
              .orElseGet(Optional.of(post.getFrom().getId())::get)).orElse(null)
      ));

      actor.setDisplayName(post.getFrom().getName());
      actor.setAdditionalProperty("handle", post.getFrom().getName());
    } catch (Exception ex) {
      LOGGER.error("Exception trying to build actor for Post: {}, {}", post, ex);
    }

    return actor;
  }

  /**
   * Builds the actor extensions given the page object.
   * @param actor actor
   * @param page page
   */
  public static void buildExtensions(ActivityObject actor, Page page) {
    Map<String, Object> extensions = new HashMap<>();
    Location location = page.getLocation();

    if (location != null) {
      extensions.put("location", page.getLocation().toString());
    }

        extensions.put("favorites", page.getTalkingAboutCount());
        extensions.put("followers", page.getFanCount());

        extensions.put("facebook", page);

        actor.setAdditionalProperty("extensions", extensions);
    }

    /**
     * Fills out the extensions attribute of the passed in {@link Activity}
     * @param activity
     * @param post
     */
    @SuppressWarnings("unchecked")
    public static void buildExtensions(Activity activity, Post post) {
        ObjectMapper mapper = StreamsJacksonMapper.getInstance();
        Map<String, Object> extensions = ExtensionUtil.getInstance().ensureExtensions(activity);

        if(post.getLikes() != null && post.getLikes().size() > 0) {
            Map<String, Object> likes = new HashMap<>();
            Like like = post.getLikes().get(0);

            if(like.getAdditionalProperties().containsKey("data")) {
                extensions.put("likes", likes);
            }
        }

        if(post.getShares() != null) {
            Map<String, Object> shares = new HashMap<>();
            shares.put("count", ((Map<String, Object>)post.getShares()).get("count"));
            extensions.put("rebroadcasts", shares);
        }

        if(post.getTo() != null) {
            To to = post.getTo();
            List<Datum> data = to.getData();
            extensions.put("user_mentions", new ArrayList<>());

            for(Datum d : data) {
                Map<String, String> mention = new HashMap<>();

                mention.put("id", d.getId());
                mention.put("displayName", d.getName());
                mention.put("handle", d.getName());

                ((List<Map<String,String>>)extensions.get("user_mentions")).add(mention);
            }
        }

        if(post.getPlace() != null) {
            Place place = post.getPlace();
            if(place.getAdditionalProperties().containsKey("location")) {
                extensions.put(ActivityUtil.LOCATION_EXTENSION, place.getAdditionalProperties().get("location"));
            }
        }

        extensions.put("facebook", mapper.convertValue(post, ObjectNode.class));
    }

    /**
     * Formats the ID to conform with the Apache Streams activity ID convention
     * @param idparts the parts of the ID to join
     * @return a valid Activity ID in format "id:facebook:part1:part2:...partN"
     */
    public static String formatId(String... idparts) {
        return String.join(":",
            Stream.concat(Arrays.stream(new String[]{"id:facebook"}), Arrays.stream(idparts)).collect(Collectors.toList()));
    }
}