/*
 * Copyright (c) 2015-2019 Snowplow Analytics Ltd. All rights reserved.
 *
 * This program is licensed to you under the Apache License Version 2.0,
 * and you may not use this file except in compliance with the Apache License Version 2.0.
 * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Apache License Version 2.0 is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
 */

package com.snowplowanalytics.snowplow.tracker.events;

import android.app.Activity;
import android.app.Fragment;

import com.snowplowanalytics.snowplow.tracker.constants.Parameters;
import com.snowplowanalytics.snowplow.tracker.constants.TrackerConstants;
import com.snowplowanalytics.snowplow.tracker.tracker.ScreenState;
import com.snowplowanalytics.snowplow.tracker.utils.Logger;
import com.snowplowanalytics.snowplow.tracker.utils.Preconditions;
import com.snowplowanalytics.snowplow.tracker.payload.SelfDescribingJson;
import com.snowplowanalytics.snowplow.tracker.payload.TrackerPayload;
import com.snowplowanalytics.snowplow.tracker.utils.Util;

import java.lang.reflect.Field;

public class ScreenView extends AbstractEvent {

    private final static String TAG = ScreenView.class.getSimpleName();

    private final String name;
    private final String id;
    private String type;
    private String transitionType;
    private String previousName;
    private String previousId;
    private String previousType;
    private String fragmentClassName;
    private String fragmentTag;
    private String activityClassName;
    private String activityTag;

    public static abstract class Builder<T extends Builder<T>> extends AbstractEvent.Builder<T> {

        private String name;
        private String id;
        private String type;
        private String transitionType;
        private String previousName;
        private String previousId;
        private String previousType;
        private String fragmentClassName;
        private String fragmentTag;
        private String activityClassName;
        private String activityTag;

        /**
         * @param name The name of the screen view event
         * @return itself
         */
        public T name(String name) {
            this.name = name;
            return self();
        }

        /**
         * @param id Screen view ID
         * @return itself
         */
        public T id(String id) {
            this.id = id;
            return self();
        }

        /**
         * @param type The type of the screen view event
         * @return itself
         */
        public T type(String type) {
            this.type = type;
            return self();
        }

        /**
         * @param name The name from the previous screen view event
         * @return itself
         */
        public T previousName(String name) {
            this.previousName = name;
            return self();
        }

        /**
         * @param type The type from the previous screen view event
         * @return itself
         */
        public T previousType(String type) {
            this.previousType = type;
            return self();
        }

        /**
         * @param id The id from the previous screen view event
         * @return itself
         */
        public T previousId(String id) {
            this.previousId = id;
            return self();
        }

        /**
         * @param transitionType The transition type of the screen view event
         * @return itself
         */
        public T transitionType(String transitionType) {
            this.transitionType = transitionType;
            return self();
        }

        public T fragmentClassName(String fragmentClassName) {
            this.fragmentClassName = fragmentClassName;
            return self();
        }

        public T fragmentTag(String fragmentTag) {
            this.fragmentTag = fragmentTag;
            return self();
        }

        public T activityClassName(String activityClassName) {
            this.activityClassName = activityClassName;
            return self();
        }

        public T activityTag(String activityTag) {
            this.activityTag = activityTag;
            return self();
        }

        public ScreenView build() {
            return new ScreenView(this);
        }
    }

    private static class Builder2 extends Builder<Builder2> {
        @Override
        protected Builder2 self() {
            return this;
        }
    }

    public static Builder<?> builder() {
        return new Builder2();
    }

    public static ScreenView buildWithActivity(Activity activity) {
        String activityClassName = activity.getLocalClassName();
        String activityTag = getSnowplowScreenId(activity);
        String name = getValidName(activityClassName, activityTag);
        return ScreenView.builder()
                .activityClassName(activityClassName)
                .activityTag(activityTag)
                .fragmentClassName(null)
                .fragmentTag(null)
                .name(name)
                .type(activityClassName)
                .transitionType(null)
                .build();
    }

    public static ScreenView buildWithFragment(Fragment fragment) {
        String fragmentClassName = fragment.getClass().getSimpleName();
        String fragmentTag = fragment.getTag();
        String name = getValidName(fragmentClassName, fragmentTag);
        return ScreenView.builder()
                .activityClassName(null)
                .activityTag(null)
                .fragmentClassName(fragment.getClass().getSimpleName())
                .fragmentTag(fragment.getTag())
                .name(name)
                .type(fragmentClassName)
                .transitionType(null)
                .build();
    }

    protected ScreenView(Builder<?> builder) {
        super(builder);

        if (builder.id != null) {
            Preconditions.checkArgument(Util.isUUIDString(builder.id));
            id = builder.id;
        } else {
            id = Util.getUUIDString();
        }

        this.name = builder.name;
        this.type = builder.type;
        this.previousId = builder.previousId;
        this.previousName = builder.previousName;
        this.previousType = builder.previousType;
        this.transitionType = builder.transitionType;
        this.fragmentClassName = builder.fragmentClassName;
        this.fragmentTag = builder.fragmentTag;
        this.activityClassName = builder.activityClassName;
        this.activityTag = builder.activityTag;
    }

    /**
     * Update the passed screen state with the data related to
     * the current ScreenView event.
     * @apiNote ScreenState updates back the previous screen fields
     * in the ScreenView event (if `previousId` not already set).
     * @param screenState The screen state to update.
     */
    public synchronized void updateScreenState(ScreenState screenState) {
        if (screenState == null) return;
        screenState.updateScreenState(id, name, type, transitionType, fragmentClassName, fragmentTag, activityClassName, activityTag);
        if (previousId == null) {
            previousId = screenState.getPreviousId();
            previousName = screenState.getPreviousName();
            previousType = screenState.getPreviousType();
        }
    }

    /**
     * Returns a TrackerPayload which can be stored into
     * the local database.
     *
     * @return the payload to be sent.
     */
    public TrackerPayload getData() {
        TrackerPayload payload = new TrackerPayload();
        payload.add(Parameters.SV_NAME, this.name);
        payload.add(Parameters.SV_ID, this.id);
        payload.add(Parameters.SV_TYPE, this.type);
        payload.add(Parameters.SV_PREVIOUS_ID, this.previousId);
        payload.add(Parameters.SV_PREVIOUS_NAME, this.previousName);
        payload.add(Parameters.SV_PREVIOUS_TYPE, this.previousType);
        payload.add(Parameters.SV_TRANSITION_TYPE, this.transitionType);
        return payload;
    }

    /**
     * Return the payload wrapped into a SelfDescribingJson.
     *
     * @return the payload as a SelfDescribingJson.
     */
    public SelfDescribingJson getPayload() {
        return new SelfDescribingJson(TrackerConstants.SCHEMA_SCREEN_VIEW, getData());
    }

    private static String getSnowplowScreenId(Activity activity) {
        Class<? extends Activity> activityClass = activity.getClass();
        try {
            Field field = activityClass.getField("snowplowScreenId");
            Object reflectedValue = field.get(activity);
            if (reflectedValue instanceof String) {
                return (String) reflectedValue;
            } else {
                Logger.e(TAG,String.format("The value of field `snowplowScreenId` on Activity `%s` has to be a String.", activityClass.getSimpleName()));
            }
        } catch (NoSuchFieldException e) {
            Logger.d(TAG, String.format("Field `snowplowScreenId` not found on Activity `%s`.", activityClass.getSimpleName()), e);
        } catch (Exception e) {
            Logger.e(TAG, "Error retrieving value of field `snowplowScreenId`: " + e.getMessage(), e);
        }
        return null;
    }

    private static String getValidName(String s1, String s2) {
        if (s1 != null && s1.length() > 0) {
            return s1;
        }
        if (s2 != null && s2.length() > 0) {
            return s2;
        }
        return "Unknown";
    }

}