/*
 * Copyright (C) 2010 Teleal GmbH, Switzerland
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.teleal.lemma.reader.javadoc;

import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.Doc;
import com.sun.javadoc.ExecutableMemberDoc;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.PackageDoc;
import com.sun.javadoc.RootDoc;
import org.teleal.common.xhtml.XHTML;
import org.teleal.lemma.Constants;
import org.teleal.lemma.anchor.AnchorAddress;
import org.teleal.lemma.anchor.CitationAnchor;
import org.teleal.lemma.anchor.Scheme;
import org.teleal.lemma.pipeline.Context;
import org.teleal.lemma.reader.AbstractReader;

import java.util.logging.Logger;

/**
 * Looks up the Javadoc <code>RootDoc</code> from the context.
 *
 * @author Christian Bauer
 */
public abstract class AbstractJavadocReader extends AbstractReader {

    final private Logger log = Logger.getLogger(AbstractJavadocReader.class.getName());

    final public static String CONTEXT_ROOT_DOC = " JavadocReader.rootDoc";

    public XHTML read(CitationAnchor citation, Context context) {
        RootDoc rootDoc = (RootDoc) context.get(CONTEXT_ROOT_DOC);
        if (rootDoc == null) {
            throw new IllegalStateException("Missing root Javadoc in context, can't read Javadoc");
        }
        return read(citation, context, rootDoc);
    }

    protected abstract XHTML read(CitationAnchor citation, Context context, RootDoc rootDoc);

    protected Doc findTargetDoc(CitationAnchor citation, RootDoc rootDoc) {

        if (!(citation.getAddress().getScheme().equals(Scheme.JAVADOC) ||
                citation.getAddress().getScheme().equals(Scheme.JAVACODE))) {
            throw new RuntimeException("TODO: NO SUPPORT FOR file://some/JavaClass.java ADDRESSES!");
        }

        // Try package name first
        Doc targetDoc = rootDoc.packageNamed(citation.getAddress().getPath());
        if (targetDoc == null) {

            // Now try class name
            targetDoc = rootDoc.classNamed(citation.getAddress().getPath());

            // Get method doc for signature (both qualified and flat are attempted)
            // TODO: This might not guarantee a hit because we only check qualified names syntactially, not semantically
            String fragment = citation.getAddress().getFragment();
            if (targetDoc != null && fragment != null) {

                MethodDoc[] methodDocs = ((ClassDoc) targetDoc).methods();
                targetDoc = null;

                log.finest("Trying to find matching signature for citation target fragment: " + fragment);
                for (MethodDoc methodDoc : methodDocs) {
                    String qualifiedSignature = methodDoc.name() + methodDoc.signature();
                    String unqualifiedSignature = methodDoc.name() + methodDoc.flatSignature();
                    if (qualifiedSignature.equals(fragment) || unqualifiedSignature.equals(fragment)) {
                        log.finest("Found method with matching signature: " + methodDoc.position());
                        targetDoc = methodDoc;
                        break;
                    }
                }

            }
        }

        if (targetDoc == null) {
            throw new IllegalArgumentException("Target not found in Javadoc unit: " + citation);
        }
        return targetDoc;
    }

    protected XHTML resolveThisReferences(Context context, Doc targetDoc, XHTML input) {
        CitationAnchor[] anchors = CitationAnchor.findCitationAnchors(getXPath(), input, Constants.TYPE_CITATION);
        for (CitationAnchor citation : anchors) {
            if (citation.getAddress().getPath().equals(AnchorAddress.PATH_THIS)) {

                AnchorAddress resolvedAddress;

                if (targetDoc instanceof ClassDoc) {
                    resolvedAddress = AnchorAddress.valueOf(
                            citation.getAddress().getScheme(),
                            (ClassDoc)targetDoc,
                            citation.getAddress().getFragment()
                    );
                } else if (targetDoc instanceof PackageDoc) {
                    resolvedAddress = AnchorAddress.valueOf(
                            citation.getAddress().getScheme(),
                            (PackageDoc)targetDoc
                    );
                } else if (targetDoc instanceof MethodDoc) {
                    resolvedAddress = AnchorAddress.valueOf(
                            citation.getAddress().getScheme(),
                            (MethodDoc)targetDoc
                    );
                } else {
                    throw new IllegalArgumentException(
                        "Unknown doc type/reference, unable to resolve 'this' reference: " + citation
                    );
                }

                log.fine("Replacing 'this' reference with anchor address: " + resolvedAddress);
                citation.setAttribute(XHTML.ATTR.href, resolvedAddress.toString());
            }
        }

        return input;
    }

}
