// $Id:
// Exp $
//
// FORESTER -- software libraries and applications
// for evolutionary biology research and applications.
//
// Copyright (C) 2009-2010 Christian M. Zmasek
// Copyright (C) 2009-2010 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: www.phylosoft.org/forester/

package org.forester.io.parsers.nexus;

import java.io.BufferedReader;
import java.io.IOException;

import org.forester.evoinference.matrix.character.BasicCharacterStateMatrix;
import org.forester.evoinference.matrix.character.CharacterStateMatrix;
import org.forester.evoinference.matrix.character.CharacterStateMatrix.BinaryStates;
import org.forester.io.parsers.util.ParserUtils;
import org.forester.io.parsers.util.PhylogenyParserException;

public class NexusBinaryStatesMatrixParser {

    private Object                             _nexus_source;
    private CharacterStateMatrix<BinaryStates> _matrix;
    private int                                _nchar;
    private int                                _ntax;

    public CharacterStateMatrix<BinaryStates> getMatrix() {
        return _matrix;
    }

    public int getNChar() {
        return _nchar;
    }

    private Object getNexusSource() {
        return _nexus_source;
    }

    public int getNTax() {
        return _ntax;
    }

    public void parse() throws IOException {
        reset();
        final BufferedReader reader = ParserUtils.createReader( getNexusSource() );
        String line;
        boolean in_matrix = false;
        int identifier_index = 0;
        int max_character_index = -1;
        while ( ( line = reader.readLine() ) != null ) {
            line = line.trim();
            if ( ( line.length() > 0 ) && !line.startsWith( "#" ) && !line.startsWith( ">" ) ) {
                if ( line.toLowerCase().indexOf( NexusConstants.NCHAR.toLowerCase() ) >= 0 ) {
                    final int i = line.toLowerCase().indexOf( NexusConstants.NCHAR.toLowerCase() );
                    String s = line.toLowerCase().substring( i + 6 );
                    s = s.replace( ';', ' ' ).trim();
                    setNChar( Integer.parseInt( s ) );
                }
                else if ( line.toLowerCase().indexOf( NexusConstants.NTAX.toLowerCase() ) >= 0 ) {
                    final int i = line.toLowerCase().indexOf( NexusConstants.NTAX.toLowerCase() );
                    String s = line.toLowerCase().substring( i + 5 );
                    s = s.replace( ';', ' ' ).trim();
                    setNTax( Integer.parseInt( s ) );
                }
                else if ( line.toLowerCase().startsWith( NexusConstants.MATRIX.toLowerCase() ) ) {
                    in_matrix = true;
                    if ( getNTax() < 1 ) {
                        throw new NexusFormatException( "did not encounter " + NexusConstants.NTAX );
                    }
                    if ( getNChar() < 1 ) {
                        throw new NexusFormatException( "did not encounter " + NexusConstants.NCHAR );
                    }
                    if ( getMatrix() != null ) {
                        throw new NexusFormatException( "more than one matrix present" );
                    }
                    setMatrix( new BasicCharacterStateMatrix<BinaryStates>( getNTax(), getNChar() ) );
                }
                else if ( line.toLowerCase().startsWith( NexusConstants.END.toLowerCase() ) ) {
                    in_matrix = false;
                }
                else if ( in_matrix ) {
                    final String[] line_ary = line.split( "\\s+" );
                    final String label = line_ary[ 0 ].trim();
                    String states_str = line_ary[ 1 ].trim();
                    if ( states_str.endsWith( ";" ) ) {
                        in_matrix = false;
                        states_str = states_str.substring( 0, states_str.length() - 1 );
                    }
                    final char[] states = states_str.toCharArray();
                    getMatrix().setIdentifier( identifier_index, label );
                    int character_index = 0;
                    for( final char state : states ) {
                        if ( state == BinaryStates.PRESENT.toChar() ) {
                            try {
                                getMatrix().setState( identifier_index, character_index, BinaryStates.PRESENT );
                            }
                            catch ( final ArrayIndexOutOfBoundsException ex ) {
                                throw new NexusFormatException( "problem at line " + line + " [" + ex + "]" );
                            }
                        }
                        else if ( state == BinaryStates.ABSENT.toChar() ) {
                            try {
                                getMatrix().setState( identifier_index, character_index, BinaryStates.ABSENT );
                            }
                            catch ( final ArrayIndexOutOfBoundsException ex ) {
                                throw new NexusFormatException( "problem at line " + line + " [" + ex + "]" );
                            }
                        }
                        else {
                            throw new NexusFormatException( "illegal state " + state );
                        }
                        ++character_index;
                    }
                    if ( ( max_character_index > 0 ) && ( max_character_index != character_index ) ) {
                        throw new NexusFormatException( "unequal number of characters at line " + line );
                    }
                    max_character_index = character_index;
                    ++identifier_index;
                }
            }
        }
    }

    private void reset() {
        setMatrix( null );
        setNChar( -1 );
        setNTax( -1 );
    }

    private void setMatrix( final CharacterStateMatrix<BinaryStates> matrix ) {
        _matrix = matrix;
    }

    private void setNChar( final int nchar ) {
        _nchar = nchar;
    }

    private void setNTax( final int ntax ) {
        _ntax = ntax;
    }

    public void setSource( final Object nexus_source ) throws PhylogenyParserException, IOException {
        if ( nexus_source == null ) {
            throw new PhylogenyParserException( getClass() + ": attempt to parse null object." );
        }
        _nexus_source = nexus_source;
    }
}
