/*
 * 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
 * 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 org.apache.weex.ui.component;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewPager;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import org.apache.weex.WXSDKInstance;
import org.apache.weex.common.WXThread;
import org.apache.weex.ui.ComponentCreator;
import org.apache.weex.ui.action.BasicComponentData;
import org.apache.weex.ui.view.BaseFrameLayout;
import org.apache.weex.ui.view.WXCircleIndicator;
import org.apache.weex.ui.view.WXCirclePageAdapter;
import org.apache.weex.ui.view.WXCircleViewPager;
import org.apache.weex.utils.WXUtils;
import org.apache.weex.utils.WXViewUtils;

import java.lang.reflect.InvocationTargetException;
import java.util.List;

/**
 * Known Issus: In auto play mode, neighbor view not scaled or aplhaed rarely.
 *
 * Created by xingjiu on 16/8/18.
 */
public class WXSliderNeighbor extends WXSlider {
    public static final String NEIGHBOR_SCALE = "neighborScale"; // the init scale of neighbor page
    public static final String NEIGHBOR_ALPHA = "neighborAlpha"; // the init alpha of neighbor page
    public static final String NEIGHBOR_SPACE = "neighborSpace"; // the init space of neighbor page
    public static final String CURRENT_ITEM_SCALE = "currentItemScale"; // the scale of middle item

    private static final int DEFAULT_NEIGHBOR_SPACE = 25;
    private static final float DEFAULT_NEIGHBOR_SCALE = 0.8F;
    private static final float DEFAULT_NEIGHBOR_ALPHA = 0.6F;
    private static final float DEFAULT_CURRENT_ITEM_SCALE = 0.9F;

    private float mNeighborScale = DEFAULT_NEIGHBOR_SCALE;
    private float mNeighborAlpha = DEFAULT_NEIGHBOR_ALPHA;
    private float mNeighborSpace = DEFAULT_NEIGHBOR_SPACE;
    private float mCurrentItemScale = DEFAULT_CURRENT_ITEM_SCALE;

    private ZoomTransformer mCachedTransformer;

    public WXSliderNeighbor(
        WXSDKInstance instance, WXVContainer parent, BasicComponentData basicComponentData) {
        super(instance, parent, basicComponentData);
    }

    public static class Creator implements ComponentCreator {
        public WXComponent createInstance(WXSDKInstance instance, WXVContainer parent, BasicComponentData basicComponentData) throws IllegalAccessException, InvocationTargetException, InstantiationException {
            return new WXSliderNeighbor(instance, parent, basicComponentData);
        }
    }

    @Override
    public void bindData(WXComponent component) {
        super.bindData(component);
    }

    @Override
    protected BaseFrameLayout initComponentHostView(@NonNull Context context) {
        BaseFrameLayout view = new BaseFrameLayout(context);

        // init view pager
        FrameLayout.LayoutParams pagerParams = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
        pagerParams.gravity = Gravity.CENTER;
        mViewPager = new WXCircleViewPager(getContext());
        mViewPager.setLayoutParams(pagerParams);

        // init adapter
        mAdapter = new WXCirclePageAdapter();
        mViewPager.setAdapter(mAdapter);

        // add to parent
        view.addView(mViewPager);
        mViewPager.addOnPageChangeListener(mPageChangeListener);

        mViewPager.setOverScrollMode(View.OVER_SCROLL_NEVER);
        registerActivityStateListener();

        mViewPager.setPageTransformer(false, createTransformer());

        return view;
    }

    ZoomTransformer createTransformer() {
        if(mCachedTransformer == null) {
            mCachedTransformer = new ZoomTransformer();
        }
        return mCachedTransformer;
    }

    @Override
    public void addSubView(View view, final int index) {
        if (view == null || mAdapter == null) {
            return;
        }

        if (view instanceof WXCircleIndicator) {
            return;
        }

        FrameLayout wrapper = new FrameLayout(getContext());
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        params.gravity = Gravity.CENTER;
        view.setLayoutParams(params);
        wrapper.addView(view);
        super.addSubView(wrapper,index);

        updateAdapterScaleAndAlpha(mNeighborAlpha, mNeighborScale); // we need to set neighbor view status when added.

        mViewPager.postDelayed(WXThread.secure(new Runnable() {
            @Override
            public void run() {
                try {
                    if(mViewPager.getRealCount() > 0 && index > 2) { // index > 2 mean more than two times, then need a fake drag
                        // prevent a bug of init status. ZoomTransformer no called as excepted.
                        mViewPager.beginFakeDrag();
                        mViewPager.fakeDragBy(1); // must be 1
                    }
                }catch (IndexOutOfBoundsException e){
                    // do nothing
                } finally {
                    try {
                        mViewPager.endFakeDrag();
                    }catch (Exception e) {
                        // do nothing
                    }
                }
            }
        }), 50);
    }

    private void updateScaleAndAlpha(View view, float alpha, float scale) {
        if(null == view) {
            return;
        }
        if(alpha >= 0 && view.getAlpha() != alpha) {
            view.setAlpha(alpha);
        }
        if(scale >= 0 && view.getScaleX() != scale) {
            view.setScaleX(scale);
            view.setScaleY(scale);
        }
    }

    private void updateAdapterScaleAndAlpha(final float alpha, final float scale) {
        final List<View> pageViews = mAdapter.getViews();
        final int curPos = mViewPager.getCurrentItem();

        if(pageViews.size() > 0) {
            final View currentPage = pageViews.get(curPos);
            updateScaleAndAlpha(((ViewGroup)currentPage).getChildAt(0), 1.0F, mCurrentItemScale);

            if(pageViews.size() < 2) {
                return;
            }
            //make sure View's width & height are measured.
            currentPage.postDelayed(WXThread.secure(new Runnable() {
                @Override
                public void run() {
                    //change left and right page's translation
                    updateNeighbor(currentPage, alpha, scale);

                }
            }), 17);

            // make sure only display view current, left, right.
            int left = (curPos == 0) ? pageViews.size()-1 : curPos-1;
            int right = (curPos == pageViews.size()-1) ? 0 : curPos+1;
            for(int i =0; i<mAdapter.getRealCount(); i++) {
                if(i != left && i != curPos && i != right) {
                    ((ViewGroup)pageViews.get(i)).getChildAt(0).setAlpha(0F);
                }
            }
        }
    }

    private void updateNeighbor(View currentPage, final float alpha, final float scale) {
        final List<View> pageViews = mAdapter.getViews();
        final int curPos = mViewPager.getCurrentItem();

        float translation = calculateTranslation(currentPage);
        int left = (curPos == 0) ? pageViews.size()-1 : curPos-1;
        View leftPage = pageViews.get(left);
        int right = (curPos == pageViews.size()-1) ? 0 : curPos+1;
        View rightPage = pageViews.get(right);

        if(pageViews.size() == 2) {
            if(curPos == 0) {
                moveRight(rightPage, translation, alpha, scale);
            }else if(curPos == 1) {
                moveLeft(leftPage, translation, alpha, scale);
            }
        } else {
            moveLeft(leftPage, translation, alpha, scale);
            moveRight(rightPage, translation, alpha, scale);
        }
    }

    private void moveLeft(View page, float translation, float alpha, float scale) {
        updateScaleAndAlpha(((ViewGroup)page).getChildAt(0), alpha, scale);
        page.setTranslationX(translation);
        ((ViewGroup)page).getChildAt(0).setTranslationX(translation);
    }
    private void moveRight(View page, float translation, float alpha, float scale) {
        moveLeft(page, -translation, alpha, scale);
    }

    @WXComponentProp(name = NEIGHBOR_SCALE)
    public void setNeighborScale(String input) {
        float neighborScale = DEFAULT_NEIGHBOR_SCALE;
        if (!TextUtils.isEmpty(input)) {
            try {
                neighborScale = Float.parseFloat(input);
            } catch (NumberFormatException e) {
            }
        }

        // addSubView is called before setProperty, so we need to modify the neighbor view in mAdapter.
        if(this.mNeighborScale != neighborScale) {
            this.mNeighborScale = neighborScale;
            updateAdapterScaleAndAlpha(-1, neighborScale);
        }
    }

    @WXComponentProp(name = NEIGHBOR_ALPHA)
    public void setNeighborAlpha(String input) {
        float neighborAlpha = DEFAULT_NEIGHBOR_ALPHA;
        if (!TextUtils.isEmpty(input)) {
            try {
                neighborAlpha = Float.parseFloat(input);
            } catch (NumberFormatException e) {
            }
        }

        // The same work as setNeighborScale()
        if(this.mNeighborAlpha != neighborAlpha) {
            this.mNeighborAlpha = neighborAlpha;
            updateAdapterScaleAndAlpha(neighborAlpha, -1);
        }
    }

    @WXComponentProp(name = NEIGHBOR_SPACE)
    @SuppressWarnings("unused")
    public void setNeighborSpace(String input) {
        float neighborSpace = DEFAULT_NEIGHBOR_SPACE;
        if (!TextUtils.isEmpty(input)) {
            try {
                neighborSpace = Float.parseFloat(input);
            } catch (NumberFormatException e) {
            }
        }

        if(this.mNeighborSpace != neighborSpace) {
            this.mNeighborSpace = neighborSpace;
        }
    }

    @WXComponentProp(name = CURRENT_ITEM_SCALE)
    @SuppressWarnings("unused")
    public void setCurrentItemScale(String input) {
        float currentItemScale = DEFAULT_CURRENT_ITEM_SCALE;
        if (!TextUtils.isEmpty(input)) {
            try {
                currentItemScale = Float.parseFloat(input);
            } catch (NumberFormatException e) {
            }
        }

        if(this.mCurrentItemScale != currentItemScale) {
            this.mCurrentItemScale = currentItemScale;
            updateAdapterScaleAndAlpha(-1, -1);
        }
    }

    @Override
    protected boolean setProperty(String key, Object param) {
        String input;
        switch (key) {
            case NEIGHBOR_SCALE:
                input = WXUtils.getString(param, null);
                if (input != null) {
                    setNeighborScale(input);
                }
                return true;
            case NEIGHBOR_ALPHA:
                input = WXUtils.getString(param, null);
                if (input != null) {
                    setNeighborAlpha(input);
                }
                return true;
            case NEIGHBOR_SPACE:
                input = WXUtils.getString(param, null);
                if (input != null) {
                    setNeighborSpace(input);
                }
                return true;
            case CURRENT_ITEM_SCALE:
                input = WXUtils.getString(param, null);
                if (input != null) {
                    setCurrentItemScale(input);
                }
                return true;
        }
        return super.setProperty(key, param);
    }

    /**
     * we need add translation for left and right card view.
     * */
    private float calculateTranslation(@NonNull View hostPage) {
        if(!(hostPage instanceof ViewGroup)) {
            return 0;
        }
        View realView = ((ViewGroup)hostPage).getChildAt(0);
        float translation = (hostPage.getMeasuredWidth()-realView.getMeasuredWidth()*mNeighborScale)/4;
        translation += ((hostPage.getMeasuredWidth()-realView.getMeasuredWidth() * mCurrentItemScale)/2 - WXViewUtils.getRealPxByWidth(mNeighborSpace, getInstance().getInstanceViewPortWidth()))/2 ;
        return translation;
    }

    // Here is the trick.
     class ZoomTransformer implements ViewPager.PageTransformer {
        @Override
        public void transformPage(View page, float position) {
            int pagePosition = mAdapter.getPagePosition(page);
            int curPosition = mViewPager.getCurrentItem();

            int realCount = mAdapter.getRealCount();

            boolean ignore = false;
            if(curPosition != 0 && curPosition != realCount - 1 && Math.abs(pagePosition - curPosition) > 1)  {
                ignore = true;
            }
            if(curPosition == 0 && pagePosition < realCount - 1 && pagePosition > 1) {
                ignore = true;
            }
            if(curPosition == realCount - 1 && pagePosition < realCount - 2 && pagePosition > 0) {
                ignore = true;
            }
            // just transfer the neighbor(left & right) page.
            if(ignore) {
                return;
            }

            View realView = ((ViewGroup)page).getChildAt(0);
            if(realView == null){
                return;
            }
            float alpha, scale;

            if(position <= (-realCount + 1)) {
                position = position + realCount;
            }
            if(position >= realCount - 1) {
                position = position - realCount;
            }

            if (position >= -1 && position <= 1) {
                float factor = Math.abs(Math.abs(position) - 1);
                scale = mNeighborScale + factor * (mCurrentItemScale - mNeighborScale);
                alpha = (1- mNeighborAlpha) * factor + mNeighborAlpha;

                float translation = calculateTranslation(page);
                if(position > 0){
                    translation = (position*translation);
                    realView.setTranslationX(-translation);
                    page.setTranslationX(-translation);
                }else if(position == 0){
                    page.setTranslationX(0);
                    realView.setTranslationX(0);
                    updateAdapterScaleAndAlpha(mNeighborAlpha, mNeighborScale);
                }else{
                    if(realCount == 2 && Math.abs(position) == 1) {
                        return;
                    }
                    translation = (-position*translation);
                    realView.setTranslationX(translation);
                    page.setTranslationX(translation);
                }
                realView.setScaleX(scale);
                realView.setScaleY(scale);
                realView.setAlpha(alpha);
            }

        }
    }

}
