/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 * You may obtain a copy of the License at
 *
 *     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 io.appium.uiautomator2.handler;

import androidx.test.uiautomator.UiObject;
import androidx.test.uiautomator.UiObjectNotFoundException;
import androidx.test.uiautomator.UiSelector;

import io.appium.uiautomator2.common.exceptions.ElementNotFoundException;
import io.appium.uiautomator2.common.exceptions.InvalidArgumentException;
import io.appium.uiautomator2.handler.request.SafeRequestHandler;
import io.appium.uiautomator2.http.AppiumResponse;
import io.appium.uiautomator2.http.IHttpRequest;
import io.appium.uiautomator2.model.AndroidElement;
import io.appium.uiautomator2.model.AppiumUIA2Driver;
import io.appium.uiautomator2.model.KnownElements;

import static java.util.Objects.requireNonNull;

public class ScrollToElement extends SafeRequestHandler {

    public ScrollToElement(String mappedUri) {
        super(mappedUri);
    }

    @Override
    protected AppiumResponse safeHandle(IHttpRequest request) throws UiObjectNotFoundException {
        String[] elementIds = getElementIds(request);
        KnownElements ke = AppiumUIA2Driver.getInstance().getSessionOrThrow().getKnownElements();
        AndroidElement element = ke.getElementFromCache(elementIds[0]);
        if (element == null) {
            throw new ElementNotFoundException();
        }
        AndroidElement scrollToElement = ke.getElementFromCache(elementIds[1]);
        if (scrollToElement == null) {
            throw new ElementNotFoundException();
        }

        // attempt to get UiObjects from the container and scroll to element
        // if we can't, we have to error out, since scrollIntoView only works with UiObjects
        // (and not for example UiObject2)
        // TODO make an equivalent implementation of this method for UiObject2 if possible
        StringBuilder errorMsg = new StringBuilder();
        UiObject elementUiObject = null;
        UiObject scrollElementUiObject = null;
        try {
            elementUiObject = (UiObject) element.getUiObject();
            try {
                scrollElementUiObject = (UiObject) scrollToElement.getUiObject();
            } catch (ClassCastException e) {
                errorMsg.append("Scroll to Element");
            }
        } catch (ClassCastException e) {
            errorMsg.append("Element");
        }

        if (errorMsg.length() > 0) {
            errorMsg.append(" was not an instance of UiObject; only UiSelector is supported. " +
                    "Ensure you use the '-android uiautomator' locator strategy when " +
                    "finding elements for use with ScrollToElement");
            throw new InvalidArgumentException(errorMsg.toString());
        }

        UiScrollable uiScrollable = new UiScrollable(requireNonNull(elementUiObject).getSelector());
        if (!uiScrollable.scrollIntoView(requireNonNull(scrollElementUiObject))) {
            throw new UiObjectNotFoundException(String.format("Cannot scroll to %s element",
                    requireNonNull(scrollElementUiObject).getSelector().toString()));
        }
        return new AppiumResponse(getSessionId(request));
    }

    private static class UiScrollable extends androidx.test.uiautomator.UiScrollable {
        public UiScrollable(UiSelector container) {
            super(container);
        }

        @Override
        public boolean scrollIntoView(UiObject obj) throws UiObjectNotFoundException {
            if (obj.exists()) {
                return true;
            }

            // we will need to reset the search from the beginning to start search
            flingToBeginning(getMaxSearchSwipes());
            if (obj.exists()) {
                return true;
            }

            for (int x = 0; x < getMaxSearchSwipes(); x++) {
                if (!scrollForward()) {
                    return false;
                }

                if (obj.exists()) {
                    return true;
                }
            }

            return false;
        }

    }
}