/*
 * Copyright 2014 astamuse company,Ltd.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * 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 com.astamuse.asta4d.render.test;

import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.commons.lang3.tuple.Pair;
import org.jsoup.nodes.Element;

import com.astamuse.asta4d.render.AttributeSetter;
import com.astamuse.asta4d.render.Renderer;
import com.astamuse.asta4d.render.RendererTestHelper;
import com.astamuse.asta4d.render.RendererType;
import com.astamuse.asta4d.render.transformer.Transformer;
import com.astamuse.asta4d.util.collection.ListConvertUtil;
import com.astamuse.asta4d.util.collection.RowConvertor;

public class RendererTester {
    private Renderer renderer;

    private RendererTester(Renderer renderer) {
        this.renderer = renderer;
    }

    public final static RendererTester forRenderer(Renderer renderer) {
        return new RendererTester(renderer);
    }

    public final static List<RendererTester> forRendererList(List<Renderer> rendererList) {

        return ListConvertUtil.transform(rendererList, new RowConvertor<Renderer, RendererTester>() {
            @Override
            public RendererTester convert(int rowIndex, Renderer obj) {
                return new RendererTester(obj);
            }
        });

    }

    private List<List<Transformer<?>>> retrieveNonAttrTransformer(String selector) {

        List<List<Transformer<?>>> rtnList = new LinkedList<>();

        List<Renderer> rendererList = renderer.asUnmodifiableList();
        for (Renderer r : rendererList) {
            if (r.getSelector().equals(selector)) {
                List<Transformer<?>> list = r.getTransformerList();
                if (list.isEmpty()) {
                    rtnList.add(list);
                } else {
                    Transformer<?> t = list.get(0);
                    if (t.getContent() instanceof AttributeSetter) {
                        continue;
                    } else {
                        rtnList.add(list);
                    }
                }
            }
        }
        return rtnList;
    }

    private List<Transformer<?>> retrieveSingleTransformerListOnSelector(String selector) {
        List<List<Transformer<?>>> list = retrieveNonAttrTransformer(selector);
        if (list.isEmpty()) {
            throw new RendererTestException("There is no value to be rendered for selector:[" + selector + "]");
        } else if (list.size() > 1) {
            throw new RendererTestException(
                    "There are more than one value to be rendered for selector:[" + selector + "], maybe it is rendered multiple times?");
        } else {
            return list.get(0);
        }
    }

    // following are simple render value accessores for simple test cases
    private Transformer<?> retrieveSingleTransformerOnSelector(String selector) {
        List<Transformer<?>> list = retrieveSingleTransformerListOnSelector(selector);

        if (list.isEmpty()) {
            throw new RendererTestException(
                    "There is no value to be rendered for selector:[" + selector + "], maybe it is rendered as a empty list?");
        } else if (list.size() > 1) {

            throw new RendererTestException(
                    "There are more than one value to be rendered for selector:[" + selector + "], maybe it is rendered as a list?");
        } else {
            return list.get(0);
        }
    }

    /**
     * 
     * @return true - the current Renderer will do nothing
     */
    public boolean noOp() {
        return renderer.asUnmodifiableList().size() == 1 && RendererTestHelper.getRendererType(renderer) == RendererType.GO_THROUGH;
    }

    /**
     * 
     * 
     * @param selector
     * @return true - there is no actual rendering operation on the given selector
     */
    public boolean noOp(String selector) {
        List<List<Transformer<?>>> list = retrieveNonAttrTransformer(selector);
        return list.isEmpty();
    }

    /**
     * 
     * 
     * @param selector
     * @param attr
     * @return true - there is no actual rendering operation on the given selector and attribute
     */
    public boolean noOp(String selector, String attr) {
        List list = (List) getAttrAsObject(selector, attr, true, true);
        return list.isEmpty();
    }

    /**
     * 
     * @param selector
     * @return the rendered operation on given selector
     * @throws RendererTestException
     *             if there is no operation or more than one operation on given selector, RendererTestException will be thrown
     */
    @SuppressWarnings("rawtypes")
    public Object get(String selector) throws RendererTestException {
        Transformer t = retrieveSingleTransformerOnSelector(selector);
        Object content = retrieveTestContentFromTransformer(t);
        return content;
    }

    /**
     * 
     * @param selector
     * @return the rendered operation list on given selector
     * @throws RendererTestException
     *             if there is no operation on given selector, RendererTestException will be thrown
     */
    public List<Object> getAsList(String selector) throws RendererTestException {
        return getAsList(selector, Object.class);
    }

    public List<RendererTester> getAsRendererTesterList(String selector) throws RendererTestException {
        return getAsList(selector, Renderer.class).stream().map(r -> RendererTester.forRenderer(r)).collect(Collectors.toList());
    }


    /**
     * 
     * @param selector
     * @param targetCls
     * @return the rendered operation list on given selector
     * @throws RendererTestException
     *             if there is no operation on given selector, RendererTestException will be thrown
     */
    public <T> List<T> getAsList(String selector, final Class<T> targetCls) throws RendererTestException {
        List<Transformer<?>> list = retrieveSingleTransformerListOnSelector(selector);
        return ListConvertUtil.transform(list, new RowConvertor<Transformer<?>, T>() {
            @SuppressWarnings("unchecked")
            @Override
            public T convert(int rowIndex, Transformer<?> transformer) {
                Object content = retrieveTestContentFromTransformer(transformer);
                return (T) content;
            }
        });
    }

    private Object retrieveTestContentFromTransformer(Transformer<?> transformer) {
        Object content = null;
        if (transformer instanceof TestableRendering) {
            content = ((TestableRendering) transformer).retrieveTestableData();
        }

        if (content != null && content instanceof TestableRendering) {
            content = ((TestableRendering) content).retrieveTestableData();
        }

        if (content != null && content instanceof Element) {
            content = new TestableElementWrapper((Element) content);
        }
        return content;
    }

    /**
     * 
     * @param selector
     * @param attr
     * @return
     * @throws RendererTestException
     *             if there is no operation or more than one operation on given selector and attr, RendererTestException will be thrown
     */
    public Object getAttr(String selector, String attr) throws RendererTestException {
        return getAttrAsObject(selector, attr, false, false);
    }

    /**
     * This method is only for retrieving rendered value of "+class" and "-class" attr action
     * 
     * @param selector
     * @param attr
     * @return
     * @throws RendererTestException
     *             if there is no operation on given selector and attr, RendererTestException will be thrown
     */
    @SuppressWarnings("unchecked")
    public List<String> getAttrAsList(String selector, String attr) throws RendererTestException {
        if (attr.equals("+class") || attr.equals("-class")) {
            return (List<String>) getAttrAsObject(selector, attr, true, false);
        } else {
            throw new RendererTestException("This method is only for retrieving rendered value of \"+class\" and \"-class\" attr action");
        }

    }

    private Object getAttrAsObject(String selector, String attr, boolean allowList, boolean allowEmpty) {
        List<Renderer> rendererList = renderer.asUnmodifiableList();
        List<Object> valueList = new LinkedList<>();
        for (Renderer r : rendererList) {
            if (r.getSelector().equals(selector)) {
                List<Transformer<?>> list = r.getTransformerList();
                if (list.isEmpty()) {
                    continue;
                } else {
                    Transformer<?> t = list.get(0);
                    if (t.getContent() instanceof AttributeSetter) {
                        AttributeSetter setter = (AttributeSetter) t.getContent();
                        @SuppressWarnings("unchecked")
                        Pair<String, Object> data = (Pair<String, Object>) setter.retrieveTestableData();
                        if (data.getKey().equals(attr)) {
                            Object obj = data.getValue();
                            valueList.add(obj);
                        }
                    } else {
                        continue;
                    }
                }
            }
        }

        if (!allowEmpty && valueList.isEmpty()) {
            throw new RendererTestException("There is no value to be rendered for attr:[" + attr + "] of selector:[" + selector + "]");
        } else if (valueList.size() > 1) {
            if (allowList) {
                return valueList;
            } else {
                throw new RendererTestException("There are more than one values(" + valueList.toString() + ") to be rendered for attr:[" +
                        attr + "] of selector:[" + selector + "]");
            }
        } else {
            if (allowList) {
                return valueList;
            } else {
                return valueList.get(0);
            }
        }

    }

}
