package com.christopherfebles.magic.form.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.christopherfebles.magic.enums.Type;
import com.christopherfebles.magic.model.MagicCard;
import com.christopherfebles.magic.model.Mana;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;

public class DisplayMagicCard extends MagicCard implements Serializable {
    
    /**
     * This class is used as a session variable, and must be Serializable
     */
    private static final long serialVersionUID = -3849403055166884565L;

    private static final Logger LOG = LoggerFactory.getLogger( DisplayMagicCard.class );
    
    private List<DisplayMagicCard> alternateVersions;
    //The ID that represents the first issuance of this specific card.
    private Integer parentMultiverseId;

    private DisplayMagicCard() {
        setAlternateVersions( new ArrayList<DisplayMagicCard>() );
    }
    
    /**
     * Create a new display-ready MagicCard
     * 
     * @param card    The MagicCard for Display
     */
    public DisplayMagicCard( MagicCard card ) {
        this();
        this.resetMagicCardValues( card );
        this.setParentMultiverseId( this.getMultiverseId() );
    }
    
    /**
     * Convert the given list of MagicCard to DisplayableMagicCards
     * 
     * @param cardList  The list of MagicCards to convert
     * @return          A list of DisplayMagicCards, or empty list if cardList is empty
     */
    public static List<DisplayMagicCard> convertAllToDisplayMagicCards( List<MagicCard> cardList ) {
        List<DisplayMagicCard> newList = new ArrayList<>();
        
        for( MagicCard card : cardList ) {
            DisplayMagicCard displayCard = new DisplayMagicCard( card );
            if ( newList.contains( displayCard ) ) {
                //Set the main version as the card with the lowest ID
                DisplayMagicCard mainCard = newList.get( newList.indexOf( displayCard ) );
                mainCard.addAlternateVersion( displayCard );
            } else {
                newList.add( displayCard );
            }
        }
        
        return newList;
    }
    
    /**
     * Generates a JSON view of this DisplayMagicCard
     * 
     * @return    A JSON Object generated by Jackson
     */
    @JsonIgnore
    public String getJSONString() {

        ObjectMapper mapper = new ObjectMapper();
        ObjectWriter ow = mapper.writer();
        String json = null;
        try {
            json = ow.writeValueAsString( this );
            json = StringEscapeUtils.escapeEcmaScript( json.trim() );
        } catch ( JsonProcessingException e ) {
            LOG.error( "Error generating JSON for DisplayMagicCard with id: {}", this.getMultiverseId(), e );
        }

        return json;
    }
    
    /**
     * Add the alternate card to the internal list. If the added card has a lower Id than this card, 
     *     set the lower card as the parent.
     * 
     * @param alternate        An equivalent card to this card to add to the list
     */
    public void addAlternateVersion( DisplayMagicCard alternate ) {
        Validate.notNull( alternate, "Alternate card cannot be null." );
        Validate.isTrue( this.equals( alternate ), "Alternate card must be equal to this card." );
        Validate.isTrue( !this.getMultiverseId().equals( alternate.getMultiverseId() ), "Alternate card cannot have the same ID as this card.");
        
        DisplayMagicCard alternateCard = alternate;
        
        if ( this.getMultiverseId() > alternateCard.getMultiverseId() ) {
            //Set the alternate as the new parent
            DisplayMagicCard newChild = new DisplayMagicCard( this );
            this.resetMagicCardValues( alternateCard );
            alternateCard = newChild;
        }
        this.getAlternateVersions().add( alternateCard );
        
        //Reset parent values
        for( DisplayMagicCard card : this.getAlternateVersions() ) {
            card.setParentMultiverseId( this.getMultiverseId() );
        }
        this.setParentMultiverseId( this.getMultiverseId() );
    }
    
    /**
     * Get a displayable list of all unique expansions for alternates to this card 
     * 
     * @return    A displayable String
     */
    public String getAlternateExpansions() {
        String retVal = "";
        
        for ( MagicCard card : this.getAlternateVersions() ) {
            String expansion = card.getExpansion().trim();
            if ( !retVal.contains( expansion ) && !this.getExpansion().trim().equals( expansion ) ) {
                retVal += ",&nbsp;" + expansion;
            }
        }
        
        return retVal;
    }
    
    @Override
    public boolean equals( Object o ) {
        if ( !(o instanceof MagicCard) ) {
            return false;
        }
        MagicCard other = (MagicCard)o;
        
        return other != null && this.getName().equalsIgnoreCase( other.getName() );
    }
    
    @Override
    public int hashCode() {
        return this.getName().hashCode();
    }
    
    @Override
    public String getExpansion() {
        String retVal = super.getExpansion();
        if ( retVal != null ) {
            //Replace all trailing and non-breaking spaces (&nbsp;)
            retVal = retVal.replaceAll("\\u00A0", " ").trim();
        }
        return retVal;
    }
    
    @Override
    /**
     * If this card is a Planeswalker, return null for power
     */
    public String getPower() {
        
        if ( this.getTypes().contains( Type.PLANESWALKER ) ) {
            return null;
        }
        
        return super.getPower();
    }
    
    @Override
    /**
     * If this card is NOT a Planeswalker, return null for Loyalty
     */
    public String getLoyalty() {
        
        if ( this.getTypes().contains( Type.PLANESWALKER ) ) {
            return super.getLoyalty();
        }
        
        return null;
    }
    
    /**
     * Get an HTML-displayable version of the text of this card
     * 
     * @return    A string to embed in HTML, including img links.
     */
    @Override
    public String getText() {
        return this.convertToHTML( super.getText() );
    }
    
    /**
     * Get an HTML-displayable version of the flavor text of this card
     * Only really useful for Unhinged/Unglued cards
     * 
     * @return    A string to embed in HTML, including img links.
     */
    @Override
    public String getFlavorText() {
        return this.convertToHTML( super.getFlavorText() );
    }
    
    /**
     * Get an HTML-displayable version of the given text 
     * 
     * @param     text    The text to convert
     * @return    A string to embed in HTML, including img links.
     */
    private String convertToHTML( String text ) {
        
        if ( StringUtils.isEmpty( text ) ) {
            return text;
        }
        
        String mutableText = text;
        String outputText = text;
        
        //Add line breaks
        outputText = outputText.replace( "...", "…");
        outputText = outputText.replace( ". . .", "…");
        outputText = outputText.replaceAll( "\\..", "$0<br>" );
        
        //See: http://stackoverflow.com/questions/16907843/java-split-regex-split-string-using-any-text-between-curly-brackets-and-keep-th
        //It's easier to change the string to match the regex than to change the regex 
        mutableText = mutableText.replace( "{", " {" );
        mutableText = mutableText.replace( "}", "} " );
        String[] textAr = mutableText.split( "\\s(?=\\{)|(?<=\\})\\s" );
        
        for ( String textSegment : textAr ) {
            if ( textSegment.contains( "{" ) ) {
                String imgTag = "<img src=\"images/" + this.getFilenameForManaString( textSegment ) + "\" class=\"manaImg\" />";
                outputText = outputText.replace( textSegment, imgTag );
            }
        }
        
        return outputText;
    }
    
    /**
     * Gets the filenames of the images representing the Mana cost of this MagicCard
     * 
     * @see #getManaCost()
     * @return    A list of filenames that correspond to the Mana cost of this card.
     */
    public List<String> getManaFilename() {
        
        List<String> filenames = new ArrayList<>();
        
        for ( Mana mana : this.getManaCost() ) {
            
            String fileName = this.getFilenameForManaString( mana.toString() );
            filenames.add( fileName );
        }

        return filenames;
    }
    
    /**
     * Get the filename of the image file corresponding to the given Mana string
     * 
     * @param mana    The mana to load an image
     * @return        The filename of the image
     */
    private String getFilenameForManaString( String mana ) {
        
        String fileName = mana.replace( "/", "" ).replace( "{", "" ).replace( "}", "" );
        String extension = ".gif";
        if ( fileName.contains( "P" ) ) {
            extension = ".png";
        }
        fileName = fileName + extension;
        
        //Hardcode Planechase icons
        if ( fileName.toLowerCase().contains( "chaos" ) ) {
            fileName = "chaos.png";
        } else if ( fileName.toLowerCase().contains( "planeswalker" ) ) {
            fileName = "planeswalker.png";
        }
        
        return fileName;
    }

    public List<DisplayMagicCard> getAlternateVersions() {
        Collections.sort( alternateVersions );
        return alternateVersions;
    }

    private void setAlternateVersions( List<DisplayMagicCard> alternateVersions ) {
        this.alternateVersions = alternateVersions;
    }

    public Integer getParentMultiverseId() {
        return parentMultiverseId;
    }

    public void setParentMultiverseId( Integer parentMultiverseId ) {
        this.parentMultiverseId = parentMultiverseId;
    }
}
