/*
 * Copyright (C) 2009 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 android.pim.vcard;

import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.EntityIterator;
import android.net.Uri;
import android.pim.vcard.VCardComposer;
import android.pim.vcard.VCardConfig;
import android.pim.vcard.VCardEntryConstructor;
import android.pim.vcard.VCardInterpreter;
import android.pim.vcard.VCardInterpreterCollection;
import android.pim.vcard.VCardParser;
import android.pim.vcard.VCardParser_V21;
import android.pim.vcard.VCardParser_V30;
import android.pim.vcard.exception.VCardException;
import android.test.AndroidTestCase;
import android.test.mock.MockContext;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Arrays;

/* package */ class CustomMockContext extends MockContext {
    final ContentResolver mResolver;
    public CustomMockContext(ContentResolver resolver) {
        mResolver = resolver;
    }

    @Override
    public ContentResolver getContentResolver() {
        return mResolver;
    }
}

/* package */ class VCardVerifier {
    private class VCardVerifierInternal implements VCardComposer.OneEntryHandler {
        public boolean onInit(Context context) {
            return true;
        }
        public boolean onEntryCreated(String vcard) {
            verifyOneVCard(vcard);
            return true;
        }
        public void onTerminate() {
        }
    }

    private final AndroidTestCase mTestCase;
    private final VCardVerifierInternal mVCardVerifierInternal;
    private int mVCardType;
    private boolean mIsV30;
    private boolean mIsDoCoMo;

    // Only one of them must be non-empty.
    private ExportTestResolver mExportTestResolver;
    private InputStream mInputStream;

    // To allow duplication, use list instead of set.
    // When null, we don't need to do the verification.
    private PropertyNodesVerifier mPropertyNodesVerifier;
    private LineVerifier mLineVerifier;
    private ContentValuesVerifier mContentValuesVerifier;
    private boolean mInitialized;
    private boolean mVerified = false;

    public VCardVerifier(AndroidTestCase androidTestCase) {
        mTestCase = androidTestCase;
        mVCardVerifierInternal = new VCardVerifierInternal();
        mExportTestResolver = null;
        mInputStream = null;
        mInitialized = false;
        mVerified = false;
    }

    public void initForExportTest(int vcardType) {
        if (mInitialized) {
            mTestCase.fail("Already initialized");
        }
        mExportTestResolver = new ExportTestResolver(mTestCase);
        mVCardType = vcardType;
        mIsV30 = VCardConfig.isV30(vcardType);
        mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
        mInitialized = true;
    }

    public void initForImportTest(int vcardType, int resId) {
        if (mInitialized) {
            mTestCase.fail("Already initialized");
        }
        mVCardType = vcardType;
        mIsV30 = VCardConfig.isV30(vcardType);
        mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
        setInputResourceId(resId);
        mInitialized = true;
    }

    private void setInputResourceId(int resId) {
        InputStream inputStream = mTestCase.getContext().getResources().openRawResource(resId);
        if (inputStream == null) {
            mTestCase.fail("Wrong resId: " + resId);
        }
        setInputStream(inputStream);
    }

    private void setInputStream(InputStream inputStream) {
        if (mExportTestResolver != null) {
            mTestCase.fail("addInputEntry() is called.");
        } else if (mInputStream != null) {
            mTestCase.fail("InputStream is already set");
        }
        mInputStream = inputStream;
    }

    public ContactEntry addInputEntry() {
        if (!mInitialized) {
            mTestCase.fail("Not initialized");
        }
        if (mInputStream != null) {
            mTestCase.fail("setInputStream is called");
        }
        return mExportTestResolver.addInputContactEntry();
    }

    public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
        if (!mInitialized) {
            mTestCase.fail("Not initialized");
        }
        if (mPropertyNodesVerifier == null) {
            mPropertyNodesVerifier = new PropertyNodesVerifier(mTestCase);
        }
        PropertyNodesVerifierElem elem =
                mPropertyNodesVerifier.addPropertyNodesVerifierElem();
        elem.addExpectedNodeWithOrder("VERSION", (mIsV30 ? "3.0" : "2.1"));

        return elem;
    }

    public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithEmptyName() {
        if (!mInitialized) {
            mTestCase.fail("Not initialized");
        }
        PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem();
        if (mIsV30) {
            elem.addExpectedNodeWithOrder("N", "").addExpectedNodeWithOrder("FN", "");
        } else if (mIsDoCoMo) {
            elem.addExpectedNodeWithOrder("N", "");
        }
        return elem;
    }

    public LineVerifierElem addLineVerifierElem() {
        if (!mInitialized) {
            mTestCase.fail("Not initialized");
        }
        if (mLineVerifier == null) {
            mLineVerifier = new LineVerifier(mTestCase, mVCardType);
        }
        return mLineVerifier.addLineVerifierElem();
    }

    public ContentValuesVerifierElem addContentValuesVerifierElem() {
        if (!mInitialized) {
            mTestCase.fail("Not initialized");
        }
        if (mContentValuesVerifier == null) {
            mContentValuesVerifier = new ContentValuesVerifier();
        }

        return mContentValuesVerifier.addElem(mTestCase);
    }

    private void verifyOneVCard(final String vcard) {
        // Log.d("@@@", vcard);
        final VCardInterpreter builder;
        if (mContentValuesVerifier != null) {
            final VNodeBuilder vnodeBuilder = mPropertyNodesVerifier;
            final VCardEntryConstructor vcardDataBuilder =
                    new VCardEntryConstructor(mVCardType);
            vcardDataBuilder.addEntryHandler(mContentValuesVerifier);
            if (mPropertyNodesVerifier != null) {
                builder = new VCardInterpreterCollection(Arrays.asList(
                        mPropertyNodesVerifier, vcardDataBuilder));
            } else {
                builder = vnodeBuilder;
            }
        } else {
            if (mPropertyNodesVerifier != null) {
                builder = mPropertyNodesVerifier;
            } else {
                return;
            }
        }

        final VCardParser parser =
                (mIsV30 ? new VCardParser_V30(true) : new VCardParser_V21());
        InputStream is = null;
        try {
            String charset =
                (VCardConfig.usesShiftJis(mVCardType) ? "SHIFT_JIS" : "UTF-8");
            is = new ByteArrayInputStream(vcard.getBytes(charset));
            mTestCase.assertEquals(true, parser.parse(is, null, builder));
        } catch (IOException e) {
            mTestCase.fail("Unexpected IOException: " + e.getMessage());
        } catch (VCardException e) {
            mTestCase.fail("Unexpected VCardException: " + e.getMessage());
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                }
            }
        }
    }

    public void verify() {
        if (!mInitialized) {
            mTestCase.fail("Not initialized.");
        }
        if (mVerified) {
            mTestCase.fail("verify() was called twice.");
        }
        if (mInputStream != null) {
            try {
                verifyForImportTest();
            } catch (IOException e) {
                mTestCase.fail("IOException was thrown: " + e.getMessage());
            } catch (VCardException e) {
                mTestCase.fail("VCardException was thrown: " + e.getMessage());
            }
        } else if (mExportTestResolver != null){
            verifyForExportTest();
        } else {
            mTestCase.fail("No input is determined");
        }
        mVerified = true;
    }

    private void verifyForImportTest() throws IOException, VCardException {
        if (mLineVerifier != null) {
            mTestCase.fail("Not supported now.");
        }
        if (mContentValuesVerifier != null) {
            mContentValuesVerifier.verify(mInputStream, mVCardType);
        }
    }

    public static EntityIterator mockGetEntityIteratorMethod(
            final ContentResolver resolver,
            final Uri uri, final String selection,
            final String[] selectionArgs, final String sortOrder) {
        final ContentProvider provider =
            resolver.acquireContentProviderClient(uri).getLocalContentProvider();
        return ((ExportTestProvider)provider).queryEntities(
                uri, selection, selectionArgs, sortOrder);
    }

    private Method getMockGetEntityIteratorMethod()
            throws SecurityException, NoSuchMethodException {
        return this.getClass().getMethod("mockGetEntityIteratorMethod",
                ContentResolver.class, Uri.class, String.class, String[].class, String.class);
    }

    private void verifyForExportTest() {
       final VCardComposer composer =
            new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType);
        composer.addHandler(mLineVerifier);
        composer.addHandler(mVCardVerifierInternal);
        if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) {
            mTestCase.fail("init() failed. Reason: " + composer.getErrorReason());
        }
        mTestCase.assertFalse(composer.isAfterLast());
        try {
            while (!composer.isAfterLast()) {
                try {
                    final Method mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod();
                    mTestCase.assertTrue(
                            composer.createOneEntry(getMockGetEntityIteratorMethod()));
                } catch (Exception e) {
                    e.printStackTrace();
                    mTestCase.fail();
                }
            }
        } finally {
            composer.terminate();
        }
    }
}
