// $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
// Copyright (C) 2000-2001 Washington University School of Medicine
// and Howard Hughes Medical Institute
// 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: www.phylosoft.org/forester

package org.forester.phylogeny.data;

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

import org.forester.io.parsers.phyloxml.PhyloXmlMapping;
import org.forester.util.ForesterUtil;

public class Property implements PhylogenyData {

    private String          _value;
    private final String    _ref;
    private final String    _unit;
    private final String    _datatype;
    private final AppliesTo _applies_to;
    private final String    _id_ref;

    public Property( final String ref,
                     final String value,
                     final String unit,
                     final String datatype,
                     final AppliesTo applies_to ) {
        this( ref, value, unit, datatype, applies_to, "" );
    }

    // Only used by method createFromNhxString.
    private Property( final String ref,
                      final String value,
                      final String unit,
                      final String datatype,
                      final AppliesTo applies_to,
                      final boolean dummy ) {
        _ref = ref;
        _unit = unit;
        _datatype = datatype;
        _applies_to = applies_to;
        _id_ref = "";
        setValue( value );
    }

    public Property( final String ref,
                     final String value,
                     final String unit,
                     final String datatype,
                     final AppliesTo applies_to,
                     final String id_ref ) {
        if ( !ForesterUtil.isEmpty( ref ) && ( ref.indexOf( ":" ) < 1 ) ) {
            throw new IllegalArgumentException( "property reference [" + ref
                    + "] is not in the expected format (missing a \":\")" );
        }
        if ( !ForesterUtil.isEmpty( unit ) && ( unit.indexOf( ":" ) < 1 ) ) {
            throw new IllegalArgumentException( "property unit [" + unit
                    + "] is not in the expected format (missing a \":\")" );
        }
        if ( !ForesterUtil.isEmpty( datatype ) && ( datatype.indexOf( ":" ) < 1 ) ) {
            throw new IllegalArgumentException( "property datatype [" + unit
                    + "] is not in the expected format (missing a \":\")" );
        }
        _ref = ref;
        _unit = unit;
        _datatype = datatype;
        _applies_to = applies_to;
        _id_ref = id_ref;
        setValue( value );
    }

    @Override
    public StringBuffer asSimpleText() {
        return new StringBuffer( getValue() );
    }

    @Override
    public StringBuffer asText() {
        final StringBuffer sb = new StringBuffer();
        sb.append( getRef() );
        sb.append( ": " );
        sb.append( getValue() );
        if ( !ForesterUtil.isEmpty( getUnit() ) ) {
            sb.append( " " );
            sb.append( getUnit() );
        }
        return sb;
    }

    @Override
    public PhylogenyData copy() {
        return new Property( getRef(), getValue(), getUnit(), getDataType(), getAppliesTo(), getIdRef() );
    }

    public AppliesTo getAppliesTo() {
        return _applies_to;
    }

    public String getDataType() {
        return _datatype;
    }

    public String getIdRef() {
        return _id_ref;
    }

    public String getRef() {
        return _ref;
    }

    public String getUnit() {
        return _unit;
    }

    public String getValue() {
        return _value;
    }

    @Override
    public boolean isEqual( final PhylogenyData data ) {
        if ( data == null ) {
            return false;
        }
        return ( ( Property ) data ).getValue().equals( getValue() )
                && ( ( Property ) data ).getUnit().equals( getUnit() )
                && ( ( Property ) data ).getRef().equals( getRef() );
    }

    public void setValue( final String value ) {
        _value = value;
    }

    @Override
    public StringBuffer toNHX() {
        final StringBuffer nhx = new StringBuffer();
        nhx.append( ":X" );
        switch ( getAppliesTo() ) {
            case CLADE:
                nhx.append( "C=" );
                break;
            case NODE:
                nhx.append( "N=" );
                break;
            case PARENT_BRANCH:
                nhx.append( "B=" );
                break;
            case PHYLOGENY:
                nhx.append( "P=" );
                break;
            case ANNOTATION:
                nhx.append( "S=" );
                break;
            default:
                nhx.append( "O=" );
                break;
        }
        if ( !getDataType().equals( "" ) ) {
            if ( getDataType().equals( "xsd:string" ) ) {
                nhx.append( "S=" );
            }
            else if ( getDataType().equals( "xsd:long" ) ) {
                nhx.append( "L=" );
            }
            else if ( getDataType().equals( "xsd:decimal" ) ) {
                nhx.append( "D=" );
            }
            else if ( getDataType().equals( "xsd:boolean" ) ) {
                nhx.append( "B=" );
            }
            else if ( getDataType().equals( "xsd:anyUR" ) ) {
                nhx.append( "U=" );
            }
        }
        nhx.append( getRef() );
        nhx.append( "=" );
        nhx.append( getValue() );
        if ( !getUnit().equals( "" ) ) {
            nhx.append( "=" );
            nhx.append( getUnit() );
        }
        return nhx;
    }

    @Override
    public void toPhyloXML( final Writer writer, final int level, final String indentation ) throws IOException {
        PhylogenyDataUtil.appendElement( writer,
                                         PhyloXmlMapping.PROPERTY,
                                         getValue(),
                                         PhyloXmlMapping.PROPERTY_REF,
                                         getRef(),
                                         PhyloXmlMapping.PROPERTY_UNIT,
                                         getUnit(),
                                         PhyloXmlMapping.PROPERTY_DATATYPE,
                                         getDataType(),
                                         PhyloXmlMapping.PROPERTY_APPLIES_TO,
                                         getAppliesTo().toString(),
                                         PhyloXmlMapping.ID_REF,
                                         getIdRef(),
                                         indentation );
    }

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

    public static Property createFromNhxString( final String nhx ) throws IllegalArgumentException {
        final StringTokenizer st = new StringTokenizer( nhx, "=" );
        final int tokens = st.countTokens();
        final String error = "error in NHX property tag format: "
                + "expected: X[N|B|C|S|T|P|O]=<datatype>=<ref>=<value>[=<unit>], got: \"" + nhx + "\" instead";
        if ( ( tokens != 4 ) && ( tokens != 5 ) ) {
            throw new IllegalArgumentException( error );
        }
        final String first = st.nextToken();
        AppliesTo applies_to = null;
        if ( first.equals( "XN" ) ) {
            applies_to = AppliesTo.NODE;
        }
        else if ( first.equals( "XB" ) ) {
            applies_to = AppliesTo.PARENT_BRANCH;
        }
        else if ( first.equals( "XC" ) ) {
            applies_to = AppliesTo.CLADE;
        }
        else if ( first.equals( "XS" ) ) {
            applies_to = AppliesTo.ANNOTATION;
        }
        else if ( first.equals( "XT" ) ) {
            applies_to = AppliesTo.OTHER;
        }
        else if ( first.equals( "XP" ) ) {
            applies_to = AppliesTo.PHYLOGENY;
        }
        else if ( first.equals( "XO" ) ) {
            applies_to = AppliesTo.OTHER;
        }
        else {
            throw new IllegalArgumentException( error );
        }
        String datatype = st.nextToken();
        if ( datatype.equals( "S" ) ) {
            datatype = "xsd:string";
        }
        else if ( datatype.equals( "L" ) ) {
            datatype = "xsd:long";
        }
        else if ( datatype.equals( "D" ) ) {
            datatype = "xsd:decimal";
        }
        else if ( datatype.equals( "B" ) ) {
            datatype = "xsd:boolean";
        }
        else if ( datatype.equals( "U" ) ) {
            datatype = "xsd:anyURI";
        }
        final String ref = st.nextToken();
        final String value = st.nextToken();
        String unit = "";
        if ( tokens == 5 ) {
            unit = st.nextToken();
        }
        return new Property( ref, value, unit, datatype, applies_to, true );
    }

    public static enum AppliesTo {
        PHYLOGENY {

            @Override
            public String toString() {
                return "phylogeny";
            }
        },
        CLADE {

            @Override
            public String toString() {
                return "clade";
            }
        },
        NODE {

            @Override
            public String toString() {
                return "node";
            }
        },
        ANNOTATION {

            @Override
            public String toString() {
                return "annotation";
            }
        },
        PARENT_BRANCH {

            @Override
            public String toString() {
                return "parent_branch";
            }
        },
        OTHER {

            @Override
            public String toString() {
                return "other";
            }
        }
    }
}
