/*
 * Copyright (C) 2011 Google Inc.
 * Licensed to The Android Open Source Project.
 *
 * 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.android.mailcommon;

import android.database.DataSetObserver;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListAdapter;
import android.widget.SpinnerAdapter;

import java.util.Arrays;
import java.util.List;

/**
 * An adapter that combines items from multiple provided adapters into a single list.
 *
 * @param <T> the class of each constituent adapter
 */
public class MergedAdapter<T extends MergedAdapter.ListSpinnerAdapter> extends BaseAdapter {

    private List<T> mAdapters;
    private final DataSetObserver mObserver;

    /**
     * A Mergeable adapter must implement both ListAdapter and SpinnerAdapter to be useful in lists
     * and spinners.
     */
    public interface ListSpinnerAdapter extends ListAdapter, SpinnerAdapter {
    }

    public static class LocalAdapterPosition<T extends ListSpinnerAdapter> {
        public final T mAdapter;
        public final int mLocalPosition;

        public LocalAdapterPosition(T adapter, int offset) {
            mAdapter = adapter;
            mLocalPosition = offset;
        }
    }

    public MergedAdapter() {
        mObserver = new DataSetObserver() {
            @Override
            public void onChanged() {
                notifyDataSetChanged();
            }
        };
    }

    public void setAdapters(T... adapters) {
        if (mAdapters != null) {
            for (T adapter : mAdapters) {
                adapter.unregisterDataSetObserver(mObserver);
            }
        }

        mAdapters = Arrays.asList(adapters);

        for (T adapter : mAdapters) {
            adapter.registerDataSetObserver(mObserver);
        }
    }

    public int getSubAdapterCount() {
        return mAdapters.size();
    }

    public T getSubAdapter(int index) {
        return mAdapters.get(index);
    }

    @Override
    public int getCount() {
        int count = 0;
        for (T adapter : mAdapters) {
            count += adapter.getCount();
        }
        return count;
        // TODO: cache counts until next onChanged
    }

    /**
     * For a given merged position, find the corresponding Adapter and local position within that
     * Adapter by iterating through Adapters and summing their counts until the merged position is
     * found.
     *
     * @param position a merged (global) position
     * @return the matching Adapter and local position, or null if not found
     */
    public LocalAdapterPosition<T> getAdapterOffsetForItem(final int position) {
        final int adapterCount = mAdapters.size();
        int i = 0;
        int count = 0;

        while (i < adapterCount) {
            T a = mAdapters.get(i);
            int newCount = count + a.getCount();
            if (position < newCount) {
                return new LocalAdapterPosition<T>(a, position - count);
            }
            count = newCount;
            i++;
        }
        return null;
    }

    @Override
    public Object getItem(int position) {
        LocalAdapterPosition<T> result = getAdapterOffsetForItem(position);
        if (result == null) {
            return null;
        }
        return result.mAdapter.getItem(result.mLocalPosition);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public int getViewTypeCount() {
        int count = 0;
        for (T adapter : mAdapters) {
            count += adapter.getViewTypeCount();
        }
        return count;
    }

    @Override
    public int getItemViewType(int position) {
        LocalAdapterPosition<T> result = getAdapterOffsetForItem(position);
        int otherViewTypeCount = 0;
        for (T adapter : mAdapters) {
            if (adapter == result.mAdapter) {
                break;
            }
            otherViewTypeCount += adapter.getViewTypeCount();
        }
        int type = result.mAdapter.getItemViewType(result.mLocalPosition);
        // Headers (negative types) are in a separate global namespace and their values should not
        // be affected by preceding adapter view types.
        if (type >= 0) {
            type += otherViewTypeCount;
        }
        return type;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        LocalAdapterPosition<T> result = getAdapterOffsetForItem(position);
        return result.mAdapter.getView(result.mLocalPosition, convertView, parent);
    }

    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        LocalAdapterPosition<T> result = getAdapterOffsetForItem(position);
        return result.mAdapter.getDropDownView(result.mLocalPosition, convertView, parent);
    }

    @Override
    public boolean areAllItemsEnabled() {
        boolean enabled = true;
        for (T adapter : mAdapters) {
            enabled &= adapter.areAllItemsEnabled();
        }
        return enabled;
    }

    @Override
    public boolean isEnabled(int position) {
        LocalAdapterPosition<T> result = getAdapterOffsetForItem(position);
        return result.mAdapter.isEnabled(result.mLocalPosition);
    }

}