"""Channel capacity inequalities coupled to a shrinkage-fed reservoir/neck design."""
import math
import numpy as np
from filling import pipe_velocity
from feeding import design_feeder,smallest_diameter,cylinder_measures,extend_to_pouring_plane


def design_channels(mesh,s,c,fs,gs,options,mask=None):
    target=float(options.get('fill_time_s',3))
    pressure=float(options.get('gas_pressure_pa',100))
    gate_min=float(options.get('gate_min_mm',4));vent_min=float(options.get('vent_min_mm',2));riser_min=float(options.get('riser_min_mm',6))
    if not all(math.isfinite(v) and v>0 for v in (target,pressure,gate_min,vent_min,riser_min)) or max(gate_min,vent_min,riser_min)>500:
        raise ValueError('Channel targets must be positive; minimum diameters must not exceed 500 mm.')
    if min(fs.viscosity,fs.liquid_density,gs.gas_viscosity,gs.atmospheric_pa)<=0 or gs.gas_temperature_C<=-273.15 or min(fs.minor_loss,fs.roughness_mm,fs.backpressure_pa,gs.surface_tension,gs.gas_generation_cm3_m2_s)<0 or not 0<=gs.contact_angle_deg<=180:
        raise ValueError('Invalid hydraulic or gas properties for channel design.')
    if not c['vents']:raise ValueError('At least one vent connection is required.')
    feeding=design_feeder(mesh,s,c,fs,gs,options,mask)
    s.riser_mm=feeding['diameter_mm']
    volume=abs(float(mesh.volume))*1e-9;rho=fs.liquid_density;gravity=9.80665
    gate_point=np.asarray(c['gate']);elbow=np.array([gate_point[0],mesh.bounds[0,1]-20,gate_point[2]])
    sprue_top=float(mesh.bounds[1,2]+s.head_mm)
    head_mm=sprue_top-float(gate_point[2])
    if rho*gravity*head_mm/1000<=pressure+fs.backpressure_pa:
        raise ValueError('Gas pressure and backpressure exceed the available gravity head.')
    R=8.314462618;temperature=gs.gas_temperature_C+273.15;pa=gs.atmospheric_pa;p=pa+pressure
    generation=gs.gas_generation_cm3_m2_s*1e-6*float(mesh.area)*1e-6*temperature/293.15
    gate=gate_min;vent=vent_min
    area=lambda d:.5*32*(d/2000)**2*math.sin(2*math.pi/32)
    runner_length=float(np.linalg.norm(gate_point-elbow))/1000
    internal_length=float(np.linalg.norm(mesh.extents))/1000
    def state(dg,dv):
        top=max(sprue_top+(1.5*dg if options.get('cup',True) else 0),c['feeding_geometry']['top_z_mm'])
        vent_lengths=[max((top-float(v[2]))/1000,s.pitch_mm/1000) for v in c['vents']]
        sprue_length=(top-float(gate_point[2]))/1000
        cup_volume=math.pi*((top-sprue_top)/1000)/3*((dg/2000)**2+(dg/2000)*(1.25*dg/1000)+(1.25*dg/1000)**2) if options.get('cup',True) else 0
        # Conservatively count overlaps twice, including the inlet cylinder inside the funnel.
        reservoir_volume=cylinder_measures(feeding['diameter_mm'],top-c['feeding_geometry']['base_z_mm'],feeding['neck_diameter_mm'])[0]*1e-9
        assembly_volume=volume+reservoir_volume+feeding['neck_volume_cm3']*1e-6+math.pi*(dg/2000)**2*(runner_length+sprue_length+.4*dg/1000)+math.pi*(dv/2000)**2*(sum(vent_lengths)+len(vent_lengths)*.4*dv/1000)+cup_volume
        length=runner_length+sprue_length+internal_length
        return top,vent_lengths,assembly_volume/target,length,assembly_volume
    def gate_capacity(d,length):
        hydraulic_d=d*math.cos(math.pi/32)/1000
        capillary=max(0.,-4*gs.surface_tension*math.cos(math.radians(gs.contact_angle_deg))/hydraulic_d)
        head=head_mm/1000-(pressure+fs.backpressure_pa+capillary)/(rho*gravity)
        return area(d)*pipe_velocity(head,rho,fs.viscosity,hydraulic_d,length,fs.minor_loss,fs.roughness_mm/1000)
    def vent_capacity(d,lengths):
        # Inscribed circular passage is no larger than the 32-sided printed channel.
        radius=d*math.cos(math.pi/32)/2000;cross=math.pi*radius*radius
        density=p*.02897/(R*temperature);orifice=.65*cross*math.sqrt(2*pressure/density)
        return sum(min(math.pi*radius**4*(p*p-pa*pa)/(16*gs.gas_viscosity*ell*p),orifice) for ell in lengths)
    for _ in range(80):
        top,lengths,rate,length,assembly_volume=state(gate,vent)
        new_gate=smallest_diameter(gate,lambda d:gate_capacity(d,length)>=rate)
        new_vent=smallest_diameter(vent,lambda d:vent_capacity(d,lengths)>=rate+generation)
        if new_gate==gate and new_vent==vent:break
        gate,vent=new_gate,new_vent
    else:raise ValueError('No converged filling-volume/channel-capacity solution. Increase filling time or review head.')
    top,lengths,rate,length,assembly_volume=state(gate,vent)
    extend_to_pouring_plane(feeding,c['feeding_geometry'],top)
    s.gate_mm=gate;s.vent_mm=vent
    gate_cap=gate_capacity(gate,length);vent_cap=vent_capacity(vent,lengths)
    checks={'metal_capacity':bool(gate_cap>=rate),'gas_capacity':bool(vent_cap>=rate+generation),**{'feeding_'+k:v for k,v in feeding['checks'].items()}}
    if not all(checks.values()):raise ValueError('Calculated channel/feeder constraints are not satisfied. Review physical inputs.')
    return {'method':'capacity-and-shrinkage-feeding-v2','casting_volume_cm3':volume*1e6,'assembly_fill_volume_upper_bound_cm3':assembly_volume*1e6,'final_length_mm':s.length_mm,'target_fill_s':target,'target_metal_rate_cm3_s':rate*1e6,'gate_capacity_cm3_s':gate_cap*1e6,'allowed_gas_gauge_pa':pressure,'vent_count':len(c['vents']),'vent_capacity_cm3_s':vent_cap*1e6,'required_gas_rate_cm3_s':(rate+generation)*1e6,'equivalent_metal_path_mm':length*1000,'internal_path_proxy_mm':internal_length*1000,'actual_external_path_mm':(length-internal_length)*1000,'available_gate_head_mm':head_mm,'vent_lengths_mm':[v*1000 for v in lengths],'gate_mm':gate,'vent_mm':vent,'riser_mm':s.riser_mm,'feeding':feeding,'checks':checks,'all_sizing_constraints_met':all(checks.values()),'minimums_mm':{'gate':gate_min,'vent':vent_min,'riser':riser_min},'porous_escape_credited':False,'assumptions':['Capacity targets include a conservative filling-volume upper bound of casting, feeder, neck, all channels and funnel; overlaps are counted twice.','Actual external channel lengths plus casting bounding-box diagonal for the unresolved internal flow path.','All vents assumed open; the separate filling/gas simulation may fail as vents close or become isolated.','Gas-source allowance uses casting surface area as a proxy, not interface chemistry.','Feeder is sized by volume, solidification-time and neck-flow inequalities; spatial feeding reach is not certified.','Recalculate for final dimensions. Do not rescale exported channels.']}
