package org.farng.mp3.filename;

import java.util.Iterator;

import org.farng.mp3.AbstractMP3Tag;
import org.farng.mp3.TagOptionSingleton;
import org.farng.mp3.id3.AbstractFrameBodyTextInformation;
import org.farng.mp3.id3.AbstractFrameBodyUrlLink;
import org.farng.mp3.id3.AbstractID3v2Frame;
import org.farng.mp3.id3.AbstractID3v2FrameBody;
import org.farng.mp3.id3.FrameBodyCOMM;
import org.farng.mp3.id3.ID3v2_4;
import org.farng.mp3.id3.ID3v2_4Frame;

/**
 * This composite subclass is the leaf of the tree. It contains the actual strings found in the filename.
 *
 * @author Eric Farng
 * @version $Revision: 1.7 $
 */
@SuppressWarnings({ "rawtypes" })
public class FilenameToken extends AbstractFilenameComposite {

    /**
     * what information this object represents.
     */
    private Class id3v2FrameBodyClass = null;
    /**
     * token that this object represents
     */
    private String token = null;

    /**
     * Creates a new FilenameToken object.
     */
    public FilenameToken() {
        super();
    }

    /**
     * Creates a new FilenameToken object.
     */
    public FilenameToken(final FilenameToken copyObject) {
        super(copyObject);
        try {
            id3v2FrameBodyClass = id3v2FrameBodyClass.newInstance().getClass();
        } catch (IllegalAccessException ex) {
            throw new NullPointerException("IllegalAccessException: No access to run constructor to create copy " +
                                           ex.getMessage());
        } catch (InstantiationException ex) {
            throw new NullPointerException("InstantiationException: Unable to instantiate constructor to copy " +
                                           ex.getMessage());
        }
        token = copyObject.token;
    }

    public void setFrame(final AbstractID3v2Frame frame) {
        if (id3v2FrameBodyClass != null && id3v2FrameBodyClass.equals(frame.getBody().getClass())) {
            //todo add support for more tag matches. only doing text
            //      information and URL links right now because i'm lazy
            if (AbstractFrameBodyTextInformation.class.isInstance(frame.getBody())) {
                token = ((AbstractFrameBodyTextInformation) frame.getBody()).getText();
            } else if (AbstractFrameBodyUrlLink.class.isInstance(frame.getBody())) {
                token = ((AbstractFrameBodyUrlLink) frame.getBody()).getUrlLink();
            }
        }
    }

    /**
     * Sets the ID3v2 frame body that this token represents
     *
     * @param id3v2FrameBodyClass the ID3v2 frame body that this token represents
     */
    public void setId3v2FrameBodyClass(final Class id3v2FrameBodyClass) {
        this.id3v2FrameBodyClass = id3v2FrameBodyClass;
    }

    /**
     * Returns the ID3v2 frame body that this token represents
     *
     * @return the ID3v2 frame body that this token represents
     */
    public Class getId3v2FrameBodyClass() {
        return id3v2FrameBodyClass;
    }

    /**
     * Sets the token that this class contains
     *
     * @param token the token that this class contains
     */
    public void setToken(final String token) {
        this.token = token.trim();
    }

    /**
     * Return the token that this class contains
     *
     * @return the token that this class contains
     */
    public String getToken() {
        return token;
    }

    /**
     * Reconstruct the filename that is represented by this composite.
     *
     * @return the filename that is represented by this composite.
     */
    public String composeFilename() {
        return token;
    }

    public ID3v2_4 createId3Tag() {
        final ID3v2_4 newTag = new ID3v2_4();
        if (id3v2FrameBodyClass != null) {
            try {
                final AbstractID3v2FrameBody body = (AbstractID3v2FrameBody) id3v2FrameBodyClass.newInstance();

                //todo need to add support for more frame bodies here
                if (body instanceof AbstractFrameBodyTextInformation) {
                    ((AbstractFrameBodyTextInformation) body).setText(token);
                    ((AbstractFrameBodyTextInformation) body).setTextEncoding((byte) 0);
                } else if (body instanceof AbstractFrameBodyUrlLink) {
                    ((AbstractFrameBodyUrlLink) body).setUrlLink(token);
                } else if (body instanceof FrameBodyCOMM) {
                    ((FrameBodyCOMM) body).setText(token);
                    ((FrameBodyCOMM) body).setDescription("");
                    ((FrameBodyCOMM) body).setLanguage(TagOptionSingleton.getInstance().getLanguage());
                    ((FrameBodyCOMM) body).setTextEncoding(TagOptionSingleton.getInstance().getTextEncoding());
                }
                final ID3v2_4Frame frame = new ID3v2_4Frame();
                frame.setBody(body);
                newTag.setFrame(frame);
            } catch (IllegalAccessException ex) {
                // catch and dont' create the frame
            } catch (InstantiationException ex) {
                // catch and dont' create the frame
            }
        }
        return newTag;
    }

    /**
     * Returns an iterator through each <code>FilenameToken</code> in this composite in the correct order for the file
     * name.
     *
     * @return an iterator through each <code>FilenameToken</code> in this composite
     */
    public Iterator iterator() {
        return new FilenameTokenIterator(this);
    }

    /**
     * Match all elements of this composite against the keywords for this class type found in
     * <code>TagOptionSingleton</code>. If the <code>FilenameToken</code> matches the keyword, the token's class is
     * set.
     *
     * @param matchId3v2FrameBodyClass Class of keywords to match against.
     */
    public void matchAgainstKeyword(final Class matchId3v2FrameBodyClass) {
        if (AbstractID3v2FrameBody.class.isAssignableFrom(matchId3v2FrameBodyClass)) {
            if (TagOptionSingleton.getInstance().isCompositeMatchOverwrite() || id3v2FrameBodyClass == null) {
                final Iterator iterator = TagOptionSingleton.getInstance()
                        .getKeywordListIterator(matchId3v2FrameBodyClass);
                final String lowerCaseToken = token.toLowerCase();
                while (iterator.hasNext()) {
                    final String matchString = ((String) iterator.next()).toLowerCase();
                    if (matchString.equals(lowerCaseToken) ||
                        matchString.indexOf(lowerCaseToken) >= 0 ||
                        lowerCaseToken.indexOf(matchString) >= 0) {
                        setId3v2FrameBodyClass(matchId3v2FrameBodyClass);
                        break;
                    }
                }
            }
        }
    }

    /**
     * Match all elements of this composite against the given tag. If any element of <code>matchTag</code> matches any
     * element of this tag's composite, then this tag's composite leaf node's class is set.
     *
     * @param matchTag Tag to match against
     */
    public void matchAgainstTag(final AbstractMP3Tag matchTag) {
        if (TagOptionSingleton.getInstance().isCompositeMatchOverwrite() || id3v2FrameBodyClass == null) {
            final ID3v2_4 tag;
            if (matchTag instanceof ID3v2_4) {
                tag = (ID3v2_4) matchTag;
            } else {
                tag = new ID3v2_4(matchTag);
            }
            final Iterator iterator = tag.iterator();
            AbstractID3v2Frame frame;
            AbstractID3v2FrameBody body;
            String matchString = null;
            final String lowerCaseToken = token.toLowerCase();
            while (iterator.hasNext()) {
                frame = (ID3v2_4Frame) iterator.next();
                body = (AbstractID3v2FrameBody) frame.getBody();
                //todo add support for more tag matches. only doing text
                //      information and URL links right now because i'm lazy
                if (body instanceof AbstractFrameBodyTextInformation) {
                    matchString = ((AbstractFrameBodyTextInformation) body).getText();
                    if (matchString != null) {
                        matchString = matchString.toLowerCase();
                    }
                } else if (body instanceof AbstractFrameBodyUrlLink) {
                    matchString = ((AbstractFrameBodyUrlLink) body).getUrlLink();
                    if (matchString != null) {
                        matchString = matchString.toLowerCase();
                    }
                } else if (body instanceof FrameBodyCOMM) {
                    matchString = ((FrameBodyCOMM) body).getText();
                }
                if (lowerCaseToken.equals(matchString) ||
                    matchString != null &&
                    (matchString.indexOf(lowerCaseToken) >= 0 || lowerCaseToken.indexOf(matchString) >= 0)) {
                    setId3v2FrameBodyClass(body.getClass());
                    break;
                }
            }
        }
    }

    /**
     * Returns a string containing debug information about this class
     *
     * @return a string containing debug information about this class
     */
    public String toString() {
        return id3v2FrameBodyClass + ": " + token;
    }
}