function model = mttFetchBondgraph(filename)

mttAssert(mttFileExists(filename),...
    ['File "',filename,'" not found']) ;

mttNotify(['   ...processing ',filename]) ;
mttWriteNewLine ;

model.bondgraph = filename ;
content = mttReadFile(filename) ;

L = length(content) ;    
N = 1 ;

processing = (N<=L) ;
while processing
    jump = 1 ;
    line = mttClipText(content{N}) ;
    
    if ~isempty(line)
        no_string_terminator = isempty(findstr('\001',line)) ;
        
        if no_string_terminator
            if mttIsNumericText(line)
                numbers = round(str2num(line)) ;    	% only interested in integer values
                
                if numbers(1)==2						% ... line definition
                    forward_arrow = numbers(14)~=0 ;
                    reverse_arrow = numbers(15)~=0 ;
                    arrow = (forward_arrow | reverse_arrow) ;
                    
                    depth = numbers(7) ;
                    is_visible = rem(depth,10)==0 ;
                    
                    if is_visible
                        mttAssert(~arrow,...
                            ['Line ',num2str(N),': Arrows are not recognised in bond graphs']) ;
                        
                        number_of_points = numbers(16) ;

                        coordinate_string = [] ;
                        M = 0 ;
                        
                        matching = 1 ;
                        while matching
                            M = M + 1 ;
                            if isempty(coordinate_string)
                                coordinate_string = content{N+M} ;
                            else
                                coordinate_string = [coordinate_string,' ',content{N+M}] ;
                            end
                            
                            mttAssert(mttIsNumericText(coordinate_string),...
                                ['Line ',num2str(N+M),': Coordinates not recognised']) ;
                            
                            coordinates = str2num(coordinate_string) ;
                            
                            matching = length(coordinates)<2*number_of_points ;
                        end
                        
                        mttAssert(length(coordinates)==2*number_of_points,...
                            ['Line ',num2str(N+1),': Wrong number of coordinates']) ;
                        
                        jump = jump + M ; 
                        
                        if number_of_points==2
                            model = create_line(model,coordinates) ;
                        elseif number_of_points>2
                            model = create_bond(model,coordinates) ;
                        end
                    else
                        jump = jump + forward_arrow + reverse_arrow ;
                    end
                end
            end
        else
            [last_word,first_part] = mttDetachText(line,' ') ;
            
            if mttIsNumericText(first_part)
                numbers = round(str2num(first_part)) ;    	% only interested in integer values
                if numbers(1)==4							% ... text definition
                    depth = numbers(4) ;
                    is_visible = rem(depth,10)==0 ;
                    
                    if is_visible
                        coordinates = numbers(12:13) ;
                        text = mttCompressText(mttCutText(last_word,'\001')) ;
                        
                        identifier = mttCutText(text,'[') ;
                        qualifier = mttExtractText(text,'[',']') ;
                        
                        name.label = [] ;
                        name.domain = [] ;
                        name.domain_item = [] ;
                        name.class = [] ;
                        name.object = [] ;
                        
                        if isempty(identifier)
                            name.label = qualifier ;
                            model = create_label(model,name,coordinates) ;
                        else
                            [name.domain,name.domain_item] = mttCutText(qualifier,'::') ;
                            [name.class,name.object] = mttCutText(identifier,':') ;
                            model = create_object(model,name,coordinates) ;
                        end
                    end
                end
            end
        end
    end
    
    N = N + jump ;
	processing = (N<=L) ;
end

model = incorporate_anonymous_objects(model) ;
model = identify_causal_assignments(model) ;
model = identify_object_binding(model) ;
model = identify_object_interfaces(model) ;
model = associate_external_domains(model) ;
model = tidy_up(model) ;



function model = create_line(model,coordinates)
    next = 1 + mttGetFieldLength(model,'line') ;
    model.line(next).xy1 = coordinates(1:2) ;
    model.line(next).xy2 = coordinates(3:4) ;
    model.line(next).mid = (coordinates(1:2)+coordinates(3:4))/2 ;
            
function model = create_bond(model,coordinates)
	next = 1 + mttGetFieldLength(model,'bond') ;
	
    N = length(coordinates) ;
    
    q1 = coordinates(N-1:N) ;	p1 = coordinates(1:2) ;		
    p2 = coordinates(3:4) ;		q2 = coordinates(N-3:N-2) ;
    p3 = coordinates(5:6) ;		q3 = coordinates(N-5:N-4) ;
    ps = norm(p2-p1) ;		    qs = norm(q2-q1) ;
    
    orientation = sign(ps-qs) ;
    
    switch orientation
    case 1,		% harpoon points forward
        xy1 = p1 ; xy2 = q2 ; v1 = p2-p1 ; v2 = q2-q3 ; harpoon = q1-q2 ;
    case -1,	% harpoon points backward
        xy1 = p2 ; xy2 = q1 ; v1 = q2-q1 ; v2 = p2-p3 ; harpoon = p1-p2 ;
    end
    
    harpoon_side = sign_cross_product(v2,harpoon) ;
    
    mttAssert(~(orientation==0 | harpoon_side==0),...
        ['Ambiguous bond orientation between[',num2str(xy1),'] and [',num2str(xy2),']']) ;
    
    model.bond(next).xy1 = xy1 ;
    model.bond(next).xy2 = xy2 ;
    model.bond(next).v1  = v1 ;
    model.bond(next).v2  = v2 ;
    model.bond(next).harpoon = harpoon ;
    model.bond(next).harpoon_side = harpoon_side ;
    
    model.bond(next).from.obj = [] ;
    model.bond(next).from.interface = [] ;
    model.bond(next).to.obj = [] ;
    model.bond(next).to.interface = [] ;
    model.bond(next).flow = [] ;
    model.bond(next).effort = [] ;
    model.bond(next).unicausal = [] ;
    model.bond(next).domain = [] ;
    model.bond(next).domain_item = [] ;
    
function model = create_label(model,name,coordinates)
	inner_name = mttExtractText(name.label,'<','>') ;
    if isempty(inner_name)
        label_name = name.label ;
        is_inline = 0 ;
    else
        label_name = inner_name ;
        is_inline = 1 ;
    end
    mttValidateName(label_name) ;

    next = 1 + mttGetFieldLength(model,'label') ;
    model.label(next).name = label_name ;
    model.label(next).is_inline = is_inline ;
    model.label(next).xy = coordinates ;

function model = create_object(model,name,coordinates)
global mtt_environment
    
    domain_names = mttGetFieldNames(mtt_environment,'domain') ;
    
    is_anonymous = 0 ;
    if isempty(name.object)
        switch name.class
        case {'0','1'},
            is_anonymous = 1 ;
        case 'SS',
            mttAssert(~isempty(name.object),...
                'Anonymous "SS" object') ;
        otherwise,
            name.object = name.class ;
        end
    end
    
    if is_anonymous
        next = 1 + mttGetFieldLength(model,'anonymous') ;
        model.anonymous(next).class = name.class ;
        model.anonymous(next).xy = coordinates ;
    else
        object_names = mttGetFieldNames(model,'obj') ;
        if ~isempty(object_names)
            mttAssert(~ismember(name.object,object_names),...
                ['Repeated object: "',name.object,'"']) ;
        end
        
        switch name.class
        case {'0','1'},
            mttValidateName(name.object) ;
            mttAssert(~ismember(name.object,{'in','out'}),...
                'Junctions cannot use reserved port names') ;
            
        case {'SS','Se','Sf','De','Df'},
            mttValidateName(name.object) ;
            
            if isempty(name.domain) | isempty(mtt_environment)
                model = setfield(model,'obj',name.object,'domain',[]) ;
                model = setfield(model,'obj',name.object,'domain_item',[]) ;
            else
                mttAssert(ismember(name.domain,domain_names),...
                    ['Unrecognised domain "',name.domain,'"']) ;
                dom = getfield(mtt_environment,'domain',name.domain,'dom') ;
                item = getfield(mtt_environment,'domain',name.domain,'item') ;
                
                if isempty(item)
                    public_domain = getfield(mtt_environment,'public_domain',{dom}) ;
                    item_names = mttGetFieldNames(public_domain,'item') ;
                    
                    mttAssert(ismember(name.domain_item,item_names),...
                        ['Item "',name.domain_item,'" not found in public domain "',name.domain,'"']) ;
                    item_name = name.domain_item ;
                else
                    mttAssert(isempty(name.domain_item),...
                        ['Item unspecified in public domain "',name.domain,'"']) ;
                    item_name = item ;
                end
                
                model = setfield(model,'obj',name.object,'domain',dom) ;
                model = setfield(model,'obj',name.object,'domain_item',item_name) ;
            end
                
        otherwise,
            mttValidateName(name.class) ;
            mttValidateName(name.object) ;
            
            mttAssert(~ismember(name.object,{'in','out'}),...
                'Objects cannot use reserved port names') ;
        end
        model = setfield(model,'obj',name.object,'class',name.class) ;
        model = setfield(model,'obj',name.object,'xy',coordinates) ;
    end

function model = incorporate_anonymous_objects(model)
    number_of_objects = mttGetFieldLength(model,'anonymous') ;

    last = length(num2str(number_of_objects)) ;
    for i = 1:last
        object_number(i) = '0' ;
    end
    
    for i = 1:number_of_objects
        anonymous_object = getfield(model,'anonymous',{i}) ;
        
        current_number = num2str(i) ;
        width = length(current_number) ;
        first = last-width+1 ;
        object_number(first:last) = current_number ;
        
        object_name = ['mtt_obj',object_number] ;
        model = setfield(model,'obj',object_name,model.anonymous(i)) ;
    end

function r = sign_cross_product(v1,v2)
    r = sign(v1(1)*v2(2) - v1(2)*v2(1)) ;
    
function model = identify_causal_assignments(model)
	L = mttGetFieldLength(model,'line') ;
    
    if L>0
        N = mttGetFieldLength(model,'bond') ;
        
        for j = 1:L 
            for i = 1:N
                s(i,j) = norm(model.line(j).mid - model.bond(i).xy1) ;
                f(i,j) = norm(model.line(j).mid - model.bond(i).xy2) ;
            end
        end
        
        [min_range_start,nearest_bond_start] = min(s) ;
        [min_range_finish,nearest_bond_finish] = min(f) ;
        
        equidistant = min_range_start==min_range_finish ;
        at_harpoon = min_range_start>min_range_finish ;
        
        for j = 1:L
            fulcrum = num2str(model.line(j).mid) ;
            
            mttAssert(~equidistant(j),...
                ['Ambiguous causal line at [',num2str(model.line(j).mid),']']) ;
            
            if at_harpoon(j)
                index = nearest_bond_finish(j) ;
                bond = model.bond(index) ;
                terminal = bond.xy2 ;
                terminal_vector = bond.v2 ;
            else
                index = nearest_bond_start(j) ;
                bond = model.bond(index) ;
                terminal = bond.xy1 ;
                terminal_vector = bond.v1 ;
            end
            
            to_lhs = norm(model.line(j).xy1 - terminal) ;
            to_mid = norm(model.line(j).mid - terminal) ;
            to_rhs = norm(model.line(j).xy2 - terminal) ;
            
            mttAssert(to_mid<norm(bond.harpoon),...
                ['Cannot assign causality at [',num2str(fulcrum),']']) ;
            
            causality_ok = 0 ;
            
            is_unicausal = to_mid<min(to_lhs,to_rhs) ;
            if is_unicausal
                [bond.flow,causality_ok] = mttAssign(bond.flow,at_harpoon(j)) ;
                [bond.effort,causality_ok] = mttAssign(bond.effort,at_harpoon(j)) ;
            else
                causal_vector = (right-left) * sign(to_mid>to_left) ;
                causal_assignment = sign_cross_product(terminal_vector,causal_vector) ;
                mttAssert(causal_assignment~=0,...
                    ['Cannot determine causality near [',num2str(fulcrum),']']) ;
                
                if causal_assignment==bond.harpoon_side
                    [bond.flow,causality_ok] = mttAssign(bond.flow,at_harpoon(j)) ;
                else
                    [bond.effort,causality_ok] = mttAssign(bond.effort,at_harpoon(j)) ;
                end
            end
            mttAssert(causality_ok,...            
                ['Ambiguous causal assignment near [',num2str(fulcrum),']']) ;
            
            bond.unicausal = mttCompare(bond.flow,bond.effort) ;
            
            model.bond(index) = bond ;
        end
    end
    
    
function model = identify_object_binding(model)
	object_names = mttGetFieldNames(model,'obj') ;
	number_of_objects = length(object_names) ;
	number_of_bonds = mttGetFieldLength(model,'bond') ;
    
    for j = 1:number_of_bonds
        bond = model.bond(j) ;
        for i = 1:number_of_objects
            object = getfield(model,'obj',object_names{i}) ;
        	origin_proximity(i) = norm(object.xy - bond.xy1) ;
            target_proximity(i) = norm(object.xy - bond.xy2) ;
        end
       	
        [range,index] = min(origin_proximity) ;
        origin_name = object_names{index} ;
        bond.from.obj = origin_name ;
        bond.from.interface = [] ;
        
        [range,index] = min(target_proximity) ;
        target_name = object_names{index} ;
        bond.to.obj = target_name ;
        bond.to.interface = [] ;
        
        model = setfield(model,'bond',{j},bond) ;
        
        origin = getfield(model,'obj',origin_name) ;
        next = 1 + mttGetFieldLength(origin,'bond') ;
        origin.bond(next).number = j ;
        origin.bond(next).is_inbond = 0 ;
        model = setfield(model,'obj',origin_name,origin) ;
        
        target = getfield(model,'obj',target_name) ;
        next = 1 + mttGetFieldLength(target,'bond') ;
        target.bond(next).number = j ;
        target.bond(next).is_inbond = 1 ;
        model = setfield(model,'obj',target_name,target) ;
    end
    
    
function model = identify_object_interfaces(model)
	object_names = mttGetFieldNames(model,'obj') ;
	number_of_objects = length(object_names) ;
	number_of_labels = mttGetFieldLength(model,'label') ;
	number_of_bonds = mttGetFieldLength(model,'bond') ;
    
    for j = 1:number_of_labels
        label = model.label(j) ;
        for i = 1:number_of_objects
            object_name = object_names{i} ;
            object = getfield(model,'obj',object_names{i}) ;
            proximity(i) = norm(object.xy - label.xy) ;
        end
        [range,index] = min(proximity) ;
        associated_object = object_names{index} ;
        object = getfield(model,'obj',associated_object) ;
        
        switch object.class
        case {'0','1'},
            mttAssert(~label.is_inline,...
                ['Inline ports not valid for "0" and "1" junctions']) ;
        end
        
        next = 1 + mttGetFieldLength(object,'interface') ;
        object = setfield(object,'interface',{next},'name',label.name) ;
        object = setfield(object,'interface',{next},'class',[]) ;
        object = setfield(object,'interface',{next},'is_inline',label.is_inline) ;
        object = setfield(object,'interface',{next},'xy',label.xy) ;
        object = setfield(object,'interface',{next},'in',[]) ;
        object = setfield(object,'interface',{next},'out',[]) ;
        object = setfield(object,'interface',{next},'map',[]) ;
        model = setfield(model,'obj',associated_object,object) ;
    end
    
    for j = 1:number_of_objects
        object_name = object_names{j} ;
        object = getfield(model,'obj',object_name) ;
        
        number_of_attached_bonds = mttGetFieldLength(object,'bond') ;
        number_of_interfaces = mttGetFieldLength(object,'interface') ;
        
        for k = 1:number_of_interfaces
            interface = object.interface(k) ;
            
            inbond_proximity = [] ;  inbond_counter = [] ;
            outbond_proximity = [] ; outbond_counter = [] ;
            
            in_counter = 0 ;
            out_counter = 0 ;
            
            for i = 1:number_of_attached_bonds
                bond_number = object.bond(i).number ; 
                bond = model.bond(bond_number) ;
                
                if object.bond(i).is_inbond
                    if isempty(bond.to.interface)
                        in_counter = in_counter + 1 ;
                        inbond_proximity(in_counter) = norm(interface.xy - bond.xy2) ;
                        inbond_counter(in_counter) = bond_number ;
                    end
                else
                    if isempty(bond.from.interface)
                        out_counter = out_counter + 1 ;
                        outbond_proximity(out_counter) = norm(interface.xy - bond.xy1) ;
                        outbond_counter(out_counter) = bond_number ;
                    end
                end
            end
            
            [in_range,inbond_index] = min(inbond_proximity) ;
            [out_range,outbond_index] = min(outbond_proximity) ;

            inbond = inbond_counter(inbond_index) ;
            outbond = outbond_counter(outbond_index) ;
            
            if interface.is_inline
                mttAssert(~isempty(inbond_proximity),...
                    ['No in-bond for interface "',object_name,'[',interface.name,']"']) ;
                mttAssert(~isempty(outbond_proximity),...
                    ['No out-bond for interface "',object_name,'[',interface.name,']"']) ;
                
            	interface.in = inbond ;
                interface.out = outbond ;
                
                model = setfield(model,'bond',{outbond},'from','interface',k) ;
                model = setfield(model,'bond',{inbond},'to','interface',k) ;
            else
                mttAssert(~(isempty(inbond_proximity) & isempty(outbond_proximity)),...
                    ['No bond for interface "',object_name,'[',interface.name,']"']) ;
                
                if isempty(inbond_proximity)
                    interface.out = outbond ;
                    model = setfield(model,'bond',{outbond},'from','interface',k) ;
                elseif isempty(outbond_proximity)
                    interface.in = inbond ;
                    model = setfield(model,'bond',{inbond},'to','interface',k) ;
                else
                    mttAssert(in_range~=out_range,...
                        ['Ambiguous interface "',object_name,'[',interface.name,']"']) ;
                    
                    if out_range<in_range
                        interface.out = outbond ;
	                    model = setfield(model,'bond',{outbond},'from','interface',k) ;
                    else
                        interface.in = inbond ;
	                    model = setfield(model,'bond',{inbond},'to','interface',k) ;
                    end
                end
            end
            object.interface(k) = interface ;
        end
        model = setfield(model,'obj',object_name,object) ;
    end
    
    
    for i = 1:number_of_objects
        object_name = object_names{i} ;
        object = getfield(model,'obj',object_name) ;
            
        number_of_interfaces = mttGetFieldLength(object,'interface') ;
        for k = 1:number_of_interfaces;
            interface = object.interface(k) ;
            
            if interface.is_inline
                mttAssert(~(isempty(interface.in) | isempty(interface.out)),...
                    ['Unbound interface: ',object_name,'[',interface.name,']']) ;
            else
                mttAssert(~(isempty(interface.in) & isempty(interface.out)),...
                    ['Unbound interface: ',object_name,'[',interface.name,']']) ;
            end
        end
    end
    
    objects_with_in = [] ;
    objects_with_out = [] ;
    
    for j = 1:number_of_bonds
        bond = model.bond(j) ;
        
        if isempty(bond.from.interface)
            object_name = bond.from.obj ;
            object = getfield(model,'obj',object_name) ;
            
            if ~ismember(object.class,{'0','1'})
                mttAssert(~ismember(object_name,objects_with_out),...
                    ['Object "',object_name,'" has more than one implicit out-bond']) ;
                if isempty(objects_with_out)
                    objects_with_out = {object_name} ;
                else
                    objects_with_out = [objects_with_out,{object_name}] ;
                end
            end
            
            next = 1 + mttGetFieldLength(object,'interface') ;
            model = setfield(model,'obj',object_name,'interface',{next},'name','out') ;
      		model = setfield(model,'obj',object_name,'interface',{next},'in',[]) ;
            model = setfield(model,'obj',object_name,'interface',{next},'out',j) ;
            model = setfield(model,'bond',{j},'from','interface',next) ;
        end
        
        if isempty(bond.to.interface)
            object_name = bond.to.obj ;
            object = getfield(model,'obj',object_name) ;
            
            if ~ismember(object.class,{'0','1'})
                mttAssert(~ismember(object_name,objects_with_in),...
                    ['Object "',object_name,'" has more than one implicit in-bond']) ;
                if isempty(objects_with_in)
                    objects_with_in = {object_name} ;
                else
                    objects_with_in = [objects_with_in,{object_name}] ;
                end
            end
            
            next = 1 + mttGetFieldLength(object,'interface') ;
            model = setfield(model,'obj',object_name,'interface',{next},'name','in') ;
            model = setfield(model,'obj',object_name,'interface',{next},'in',j) ;
         	model = setfield(model,'obj',object_name,'interface',{next},'out',[]) ;
            model = setfield(model,'bond',{j},'to','interface',next) ;
        end
    end
    
    
function model = associate_external_domains(model)

	object_names = mttGetFieldNames(model,'obj') ;
	number_of_objects = length(object_names) ;
    
	for i = 1:number_of_objects
        object_name = object_names{i} ;
        object = getfield(model,'obj',object_name) ;
        
        switch object.class
        case {'SS','Se','Sf','De','Df'},
            mttAssert(mttGetFieldLength(object,'interface')==1,...
                ['Object "',object_name,'" must have a unique bond interface']) ;
            
            bond_number = [] ;
            
            if ~isempty(object.interface.in)
                bond_number = object.interface.in ;
            end
            if ~isempty(object.interface.out)
                bond_number = object.interface.out ;
            end
            
            [model,ok] = mttUpdateBondDomain(model,bond_number,object.domain,object.domain_item) ;
            mttAssert(ok,['Domain conflict on bond connected to object "',object_name,'"']) ;
        end
    end
    
    
function model = tidy_up(model)
% remove temperory data and xfig geometry from model structure 

	object_names = mttGetFieldNames(model,'obj') ;
	number_of_objects = length(object_names) ;
    
	for i = 1:number_of_objects
        object_name = object_names{i} ;
        object = getfield(model,'obj',object_name) ;
        
        object = mttDeleteField(object,'bond') ;
        
        object_interfaces = getfield(object,'interface') ;
        object_interfaces = mttDeleteField(object_interfaces,'is_inline') ;
        object_interfaces = mttDeleteField(object_interfaces,'xy') ;
        object = setfield(object,'interface',object_interfaces) ;
        object = mttDeleteField(object,'xy') ;
        model = setfield(model,'obj',object_name,object) ;
    end
    
	all_bonds = getfield(model,'bond') ;
    all_bonds = mttDeleteField(all_bonds,'xy1') ;
    all_bonds = mttDeleteField(all_bonds,'xy2') ;
    all_bonds = mttDeleteField(all_bonds,'v1') ;
    all_bonds = mttDeleteField(all_bonds,'v2') ;
    all_bonds = mttDeleteField(all_bonds,'harpoon') ;
    all_bonds = mttDeleteField(all_bonds,'harpoon_side') ;
    model = setfield(model,'bond',all_bonds) ;
    
    model = mttDeleteField(model,'anonymous') ;
    model = mttDeleteField(model,'line') ;
    model = mttDeleteField(model,'label') ;
