// $Id:
// FORESTER -- software libraries and applications
// for evolutionary biology research and applications.
//
// Copyright (C) 2008-2009 Christian M. Zmasek
// Copyright (C) 2008-2009 Burnham Institute for Medical Research
// All rights reserved
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
//
// Contact: phylosoft @ gmail . com
// WWW: https://sites.google.com/site/cmzmasek/home/software/forester

package org.forester.phylogeny.data;

import java.io.IOException;
import java.io.Writer;
import java.util.StringTokenizer;

import org.forester.io.parsers.nhx.NHXFormatException;
import org.forester.io.parsers.nhx.NHXtags;
import org.forester.io.parsers.phyloxml.PhyloXmlMapping;
import org.forester.io.writers.PhylogenyWriter;
import org.forester.util.ForesterUtil;

public class Event implements PhylogenyData {

    public final static int     DEFAULT_VALUE = -1;
    private static final String NHX_SEPARATOR = ">";
    private int                 _duplications;
    private int                 _speciations;
    private int                 _gene_losses;
    private EventType           _event_type;
    private Confidence          _confidence;

    public Event() {
        _duplications = DEFAULT_VALUE;
        _speciations = DEFAULT_VALUE;
        _gene_losses = DEFAULT_VALUE;
        _event_type = EventType.unassigned;
    }

    public Event( final EventType type ) {
        _duplications = DEFAULT_VALUE;
        _speciations = DEFAULT_VALUE;
        _gene_losses = DEFAULT_VALUE;
        _event_type = type;
    }

    public Event( final int duplications, final int speciations, final int gene_losses ) {
        _duplications = duplications;
        _speciations = speciations;
        _gene_losses = gene_losses;
        _event_type = EventType.mixed;
    }

    public Event( final int duplications, final int speciations, final int gene_losses, final String type ) {
        _duplications = duplications;
        _speciations = speciations;
        _gene_losses = gene_losses;
        _event_type = EventType.valueOf( type );
    }

    public Event( final String nhx ) throws NHXFormatException {
        if ( ForesterUtil.isEmpty( nhx ) ) {
            _duplications = DEFAULT_VALUE;
            _speciations = DEFAULT_VALUE;
            _gene_losses = DEFAULT_VALUE;
            _event_type = EventType.unassigned;
        }
        else {
            final StringTokenizer st = new StringTokenizer( nhx, NHX_SEPARATOR );
            if ( st.countTokens() != 4 ) {
                throw new NHXFormatException( "malformed NHX format for event [" + nhx + "]" );
            }
            final String duplications = ( String ) st.nextElement();
            final String speciations = ( String ) st.nextElement();
            final String losses = ( String ) st.nextElement();
            final String event_type = ( String ) st.nextElement();
            int d = 0;
            int s = 0;
            int l = 0;
            try {
                d = Integer.parseInt( duplications );
                s = Integer.parseInt( speciations );
                l = Integer.parseInt( losses );
                _duplications = d;
                _speciations = s;
                _gene_losses = l;
                _event_type = EventType.valueOf( event_type );
            }
            catch ( final Exception e ) {
                throw new NHXFormatException( "malformed NHX format for event [" + nhx + "]:" + e.getMessage() );
            }
        }
    }

    @Override
    public StringBuffer asSimpleText() {
        final StringBuffer sb = new StringBuffer();
        if ( isUnassigned() ) {
        }
        else if ( isSpeciationOrDuplication() ) {
            sb.append( "?" );
        }
        else if ( isOther() || isRoot() || isTransfer() || isFusion() ) {
            sb.append( getEventType().toString() );
        }
        else {
            if ( getNumberOfDuplications() > 0 ) {
                if ( getNumberOfDuplications() > 1 ) {
                    sb.append( getNumberOfDuplications() );
                }
                sb.append( "D" );
            }
            if ( getNumberOfSpeciations() > 0 ) {
                if ( getNumberOfSpeciations() > 1 ) {
                    sb.append( getNumberOfSpeciations() );
                }
                sb.append( "S" );
            }
            if ( getNumberOfGeneLosses() > 0 ) {
                if ( getNumberOfGeneLosses() > 1 ) {
                    sb.append( getNumberOfGeneLosses() );
                }
                sb.append( "L" );
            }
        }
        return sb;
    }

    @Override
    public StringBuffer asText() {
        final StringBuffer sb = new StringBuffer();
        if ( isUnassigned() || isSpeciationOrDuplication() || isOther() || isRoot() || isTransfer() || isFusion() ) {
            sb.append( getEventType().toString() );
        }
        else {
            if ( isDuplication() ) {
                if ( getNumberOfDuplications() == 1 ) {
                    sb.append( "duplication" );
                }
                else {
                    sb.append( "duplications [" + getNumberOfDuplications() + "]" );
                }
            }
            else if ( isSpeciation() ) {
                if ( getNumberOfSpeciations() == 1 ) {
                    sb.append( "speciation" );
                }
                else {
                    sb.append( "speciations [" + getNumberOfSpeciations() + "]" );
                }
            }
            else if ( isGeneLoss() ) {
                if ( getNumberOfGeneLosses() == 1 ) {
                    sb.append( "gene-loss" );
                }
                else {
                    sb.append( "gene-losses [" + getNumberOfGeneLosses() + "]" );
                }
            }
            else {
                sb.append( "duplications [" + getNumberOfDuplications() + "] " );
                sb.append( "speciations [" + getNumberOfSpeciations() + "] " );
                sb.append( "gene-losses [" + getNumberOfGeneLosses() + "]" );
            }
        }
        return sb;
    }

    @Override
    public PhylogenyData copy() {
        if ( isUnassigned() ) {
            return new Event();
        }
        else if ( _event_type != EventType.mixed ) {
            return new Event( _event_type );
        }
        else {
            return new Event( _duplications, _speciations, _gene_losses );
        }
    }

    public Confidence getConfidence() {
        return _confidence;
    }

    public EventType getEventType() {
        return _event_type;
    }

    public int getNumberOfDuplications() {
        return _duplications;
    }

    public int getNumberOfGeneLosses() {
        return _gene_losses;
    }

    public int getNumberOfSpeciations() {
        return _speciations;
    }

    /**
     * Returns true if this event contains one or more duplications events only
     *
     * @return true if this event contains one or more duplications events only
     */
    public boolean isDuplication() {
        return ( _duplications > 0 ) && ( _gene_losses < 1 ) && ( _speciations < 1 );
    }

    @Override
    public boolean isEqual( final PhylogenyData event ) {
        if ( ( event == null ) || !( event instanceof Event ) ) {
            return false;
        }
        final Event e = ( Event ) event;
        if ( getEventType().compareTo( e.getEventType() ) != 0 ) {
            return false;
        }
        if ( getNumberOfDuplications() != e.getNumberOfDuplications() ) {
            return false;
        }
        if ( getNumberOfSpeciations() != e.getNumberOfSpeciations() ) {
            return false;
        }
        if ( getNumberOfGeneLosses() != e.getNumberOfGeneLosses() ) {
            return false;
        }
        return true;
    }

    public boolean isFusion() {
        return _event_type == EventType.fusion;
    }

    /**
     * Returns true if this event contains one or more gene loss events only
     *
     * @return true if this event contains one or more gene loss events only
     */
    public boolean isGeneLoss() {
        return ( _duplications < 1 ) && ( _gene_losses > 0 ) && ( _speciations < 1 );
    }

    public boolean isOther() {
        return _event_type == EventType.other;
    }

    public boolean isRoot() {
        return _event_type == EventType.root;
    }

    /**
     * Returns true if this event contains one or more speciation events only
     *
     * @return true if this event contains one or more speciation events only
     */
    public boolean isSpeciation() {
        return ( _duplications < 1 ) && ( _gene_losses < 1 ) && ( _speciations > 0 );
    }

    public boolean isSpeciationOrDuplication() {
        return _event_type == EventType.speciation_or_duplication;
    }

    public boolean isTransfer() {
        return _event_type == EventType.transfer;
    }

    public boolean isUnassigned() {
        return ( _duplications == DEFAULT_VALUE ) && ( _event_type == EventType.unassigned );
    }

    public void setConfidence( final Confidence confidence ) {
        _confidence = confidence;
    }

    public void setDuplications( final int duplications ) {
        _duplications = duplications;
        _event_type = EventType.mixed;
    }

    public void setGeneLosses( final int gene_losses ) {
        _gene_losses = gene_losses;
        _event_type = EventType.mixed;
    }

    public void setSpeciations( final int speciations ) {
        _speciations = speciations;
        _event_type = EventType.mixed;
    }

    @Override
    public StringBuffer toNHX() {
        final StringBuffer sb = new StringBuffer();
        if ( !isUnassigned() && ( isSpeciationOrDuplication() || isDuplication() || isSpeciation() ) ) {
            sb.append( ":" );
            sb.append( NHXtags.IS_DUPLICATION );
            if ( isSpeciationOrDuplication() ) {
                sb.append( "?" );
            }
            else if ( isDuplication() ) {
                sb.append( "Y" );
            }
            else if ( isSpeciation() ) {
                sb.append( "N" );
            }
        }
        return sb;
    }

    @Override
    public void toPhyloXML( final Writer writer, final int level, final String indentation ) throws IOException {
        writer.write( ForesterUtil.LINE_SEPARATOR );
        writer.write( indentation );
        PhylogenyDataUtil.appendOpen( writer, PhyloXmlMapping.EVENTS );
        if ( ( getEventType() != EventType.unassigned ) && ( getEventType() != EventType.mixed ) ) {
            PhylogenyDataUtil
            .appendElement( writer, PhyloXmlMapping.EVENT_TYPE, getEventType().toString(), indentation );
        }
        if ( getNumberOfDuplications() > 0 ) {
            PhylogenyDataUtil.appendElement( writer,
                                             PhyloXmlMapping.EVENT_DUPLICATIONS,
                                             getNumberOfDuplications() + "",
                                             indentation );
        }
        if ( getNumberOfSpeciations() > 0 ) {
            PhylogenyDataUtil.appendElement( writer,
                                             PhyloXmlMapping.EVENT_SPECIATIONS,
                                             getNumberOfSpeciations() + "",
                                             indentation );
        }
        if ( getNumberOfGeneLosses() > 0 ) {
            PhylogenyDataUtil.appendElement( writer,
                                             PhyloXmlMapping.EVENT_LOSSES,
                                             getNumberOfGeneLosses() + "",
                                             indentation );
        }
        if ( getConfidence() != null ) {
            getConfidence().toPhyloXML( writer, level, indentation + PhylogenyWriter.PHYLO_XML_INTENDATION_BASE );
        }
        writer.write( ForesterUtil.LINE_SEPARATOR );
        writer.write( indentation );
        PhylogenyDataUtil.appendClose( writer, PhyloXmlMapping.EVENTS );
    }

    @Override
    public String toString() {
        return asText().toString();
    }

    public static Event createSingleDuplicationEvent() {
        return new Event( 1, 0, 0 );
    }

    public static Event createSingleSpeciationEvent() {
        return new Event( 0, 1, 0 );
    }

    public static Event createSingleSpeciationOrDuplicationEvent() {
        return new Event( EventType.speciation_or_duplication );
    }

    public static enum EventType {
        transfer, fusion, root, speciation_or_duplication, other, mixed, unassigned
    }
}
