module CapeOpen
  export constantstrings, constantfloats, tempreturedependents, pressuredependents
  export MaterialObject, PropertyPackage, PhysicalPropertyCalculator

  """
    Constant string properties
      1- casRegistryNumber => Chemical Abstract Service Registry Number
      2- chemicalFormula => Chemical formula
      3- iupacName => Complete IUPAC Name
      4- SMILESformula => SMILES chemical structure formula
  """
  constantstrings=ASCIIString[
    "casRegistryNumber", # Chemical Abstract Service Registry Number
    "chemicalFormula", # Chemical formula
    "iupacName", # Complete IUPAC Name
    "SMILESformula" # SMILES chemical structure formula
  ]

  """
    Constant float properties
      1-  acentricFactor => Pitzer acentric factor
      2-  associationParameter => association-parameter (Hayden-O’Connell)
      3-  bornRadius => in m
      4-  charge =>
      5-  criticalCompressibilityFactor => critical compressibility factor Z
      6-  criticalDensity => critical density in mol/m3
      7-  criticalPressure => critical pressure in Pa
      8-  criticalTemperature => critical temperature in K
      9-  criticalVolume => critical volume in m3/mol
      10- diffusionVolume => diffusion volume in m3
      11- dipoleMoment => dipole moment in Cm
      12- energyLennardJones => Lennard-Jones energy parameter in K (divided by Boltzmann constant)
      13- gyrationRadius => radius of gyration in m
      14- heatOfFusionAtNormalFreezingPoint => enthalpy change on melting at normal freezing point (101325 Pa) in J/mol
      15- heatOfVaporizationAtNormalBoilingPoint => enthalpy change on vaporization at normal boiling point (101325 Pa) in J/mol
      16- idealGasGibbsFreeEnergyOfFormationAt25C => in J/mol
      17- liquidDensityAt25C => liquid density at 25 ºC in mol/m3
      18- liquidVolumeAt25C => liquid volume at 25 ºC in m3/mol
      19- lengthLennardJones => Lennard-Jones length parameter in m
      20- molecularWeight => relative molar mass
      21- normalBoilingPoint => boiling point temperature at 101325 Pa in K
      22- normalFreezingPoint => melting point temperature at 101325 Pa in K
      23- parachor => Parachor in m3 kg0.25/(s0.5 mol)
      24- standardEntropyGas => Standard entropy of gas in J/mol
      25- standardEntropyLiquid => standard entropy of liquid in J/mol
      26- standardEntropySolid => standard entropy of solid in J/mol
      27- standardEnthalpyAqueousDilution => Standard aqueous infinite dilution enthalpy in J/mol
      28- standardFormationEnthalpyGas => standard enthalpy change on formation of gas in J/mol
      29- standardFormationEnthalpyLiquid => standard enthalpy change on formation of liquid in J/mol
      30- standardFormationEnthalpySolid => standard enthalpy change on formation of solid in J/mol
      31- standardFormationGibbsEnergyGas => standard Gibbs energy change on formation of gas in J/mol
      32- standardFormationGibbsEnergyLiquid => standard Gibbs energy change on formation of liquid in J/mol
      33- standardFormationGibbsEnergySolid => standard Gibbs energy change on formation of solid in J/mol
      34- standardGibbsAqueousDilution => Standard aqueous infinite dilution Gibbs energy in J/mol
      35- triplePointPressure => triple point pressure in Pa
      36- triplePointTemperature => triple point temperature in K
      37- vanderwaalsArea => van der Waals area in m3/mol
      38- vanderwaalsVolume => van der Waals volume in m3/mol
  """
  constantfloats=ASCIIString[
    "acentricFactor", # Pitzer acentric factor
    "associationParameter", # association-parameter (Hayden-O’Connell)
    "bornRadius", # in m
    "charge",
    "criticalCompressibilityFactor", # critical compressibility factor Z
    "criticalDensity", # critical density in mol/m3
    "criticalPressure", # critical pressure in Pa
    "criticalTemperature", # critical temperature in K
    "criticalVolume", # critical volume in m3/mol
    "diffusionVolume", # diffusion volume in m3
    "dipoleMoment", # dipole moment in Cm
    "energyLennardJones", # Lennard-Jones energy parameter in K (divided by Boltzmann constant)
    "gyrationRadius", # radius of gyration in m
    "heatOfFusionAtNormalFreezingPoint", # enthalpy change on melting at normal freezing point (101325 Pa) in J/mol
    "heatOfVaporizationAtNormalBoilingPoint", # enthalpy change on vaporization at normal boiling point (101325 Pa) in J/mol
    "idealGasGibbsFreeEnergyOfFormationAt25C", # in J/mol
    "liquidDensityAt25C", # liquid density at 25 ºC in mol/m3
    "liquidVolumeAt25C", # liquid volume at 25 ºC in m3/mol
    "lengthLennardJones", # Lennard-Jones length parameter in m
    "molecularWeight", # relative molar mass
    "normalBoilingPoint", # boiling point temperature at 101325 Pa in K
    "normalFreezingPoint", # melting point temperature at 101325 Pa in K
    "parachor", # Parachor in m3 kg0.25/(s0.5 mol)
    "standardEntropyGas", # Standard entropy of gas in J/mol
    "standardEntropyLiquid", # standard entropy of liquid in J/mol
    "standardEntropySolid", # standard entropy of solid in J/mol
    "standardEnthalpyAqueousDilution", # Standard aqueous infinite dilution enthalpy in J/mol
    "standardFormationEnthalpyGas", # standard enthalpy change on formation of gas in J/mol
    "standardFormationEnthalpyLiquid", # standard enthalpy change on formation of liquid in J/mol
    "standardFormationEnthalpySolid", # standard enthalpy change on formation of solid in J/mol
    "standardFormationGibbsEnergyGas", # standard Gibbs energy change on formation of gas in J/mol
    "standardFormationGibbsEnergyLiquid", # standard Gibbs energy change on formation of liquid in J/mol
    "standardFormationGibbsEnergySolid", # standard Gibbs energy change on formation of solid in J/mol
    "standardGibbsAqueousDilution", # Standard aqueous infinite dilution Gibbs energy in J/mol
    "triplePointPressure", # triple point pressure in Pa
    "triplePointTemperature", # triple point temperature in K
    "vanderwaalsArea", # van der Waals area in m3/mol
    "vanderwaalsVolume" # van der Waals volume in m3/mol
  ]

  """
    Temprature dependent properties
      1-  cpAqueousInfiniteDilution => Heat capacity of a solute in an infinitely dilute aqueous solution. in J/(mol K)
      2-  dielectricConstant => The ratio of the capacity of a condenser with a particular substance as dielectric to the capacity of the same condenser with a vacuum for dielectric.
      3-  expansivity => Coefficient of linear expansion for a solid at 1 atm in 1/K
      4-  fugacityCoefficientOfVapor => Fugacity coefficient of vapour on the saturation line
      5-  glassTransitionPressure => Glass transition pressure in Pa
      6-  heatCapacityOfLiquid => Heat capacity (Cp) of liquid on the saturation line in J/(mol K)
      7-  heatCapacityOfSolid => Solid heat capacity (Cp) at 1 atm in J/(mol K)
      8-  heatOfFusion => Enthalpy change on fusion fot the solid on the melting line in J/mol
      9-  heatOfSublimation => Enthalpy change on evaporation of the solid on the sublimation line in J/mol
      10- heatOfSolidSolidPhaseTransition => Enthalpy change on phase transition in J/mol
      11- heatOfVaporization => Enthalpy change on evaporation of the liquid on the saturation line in J/mol
      12- idealGasEnthalpy => Enthalpy of ideal gas in J/mol
      13- idealGasEntropy => Temperature-dependent part of entropy of ideal gas in J/(mol K)
      14- idealGasHeatCapacity => Heat capacity (Cp) of ideal gas in J/(mol K)
      15- meltingPressure => Pressure on melting line in Pa
      16- selfDiffusionCoefficientGas => Self-diffusion coefficient in gas phase at 1 atm in m2/s
      17- selfDiffusionCoefficientLiquid => self-diffusion coefficient in liquid phase on saturation line in m2/s
      18- solidSolidPhaseTransitionPressure => Pressure at phase transition in Pa
      19- sublimationPressure => Vapour pressure of solid on the sublimation line in Pa
      20- surfaceTensionSatLiquid => Surface tension of liquid on the saturation line in N/m
      21- thermalConductivityOfLiquid => Thermal conductivity of liquid on saturation line in W/(m K)
      22- thermalConductivityOfSolid => Thermal conductivity of solid at 1 atm in W/(m K)
      23- thermalConductivityOfVapor => Thermal conductivity of dilute gas in W/(m K)
      24- vaporPressure => Vapour pressure of saturated liquid in Pas
      25- virialCoefficient => Second virial coefficient of gas in m3/mol
      26- viscosityOfLiquid => Viscosity of liquid on saturation line in Pas
      27- viscosityOfVapor => Viscosity in dilute gas state in Pas
      28- volumeChangeUponMelting => Volume change for the solid on the melting line in m3/mol
      29- volumeChangeUponSolidSolidPhaseTransition => Volume change upon solid-solid phase transition in m3/mol
      30- volumeChangeUponSublimation => Volume change for the solid on the sublimation line in m3/mol
      31- volumeChangeUponVaporization => Volume change for the liquid on the saturation line in m3/mol
      32- volumeOfLiquid => Volume of liquid on saturation line in m3/mol
      33- volumeOfSolid => Volume of solid at 1 atm in m3/mol

  """
  tempreturedependents=ASCIIString[
    "cpAqueousInfiniteDilution", # Heat capacity of a solute in an infinitely dilute aqueous solution. in J/(mol K)
    "dielectricConstant", # The ratio of the capacity of a condenser with a particular substance as dielectric to the capacity of the same condenser with a vacuum for dielectric.
    "expansivity", # Coefficient of linear expansion for a solid at 1 atm in 1/K
    "fugacityCoefficientOfVapor", # Fugacity coefficient of vapour on the saturation line
    "glassTransitionPressure", # Glass transition pressure in Pa
    "heatCapacityOfLiquid", # Heat capacity (Cp) of liquid on the saturation line in J/(mol K)
    "heatCapacityOfSolid", # Solid heat capacity (Cp) at 1 atm in J/(mol K)
    "heatOfFusion", # Enthalpy change on fusion fot the solid on the melting line in J/mol
    "heatOfSublimation", # Enthalpy change on evaporation of the solid on the sublimation line in J/mol
    "heatOfSolidSolidPhaseTransition", # Enthalpy change on phase transition in J/mol
    "heatOfVaporization", # Enthalpy change on evaporation of the liquid on the saturation line in J/mol
    "idealGasEnthalpy", # Enthalpy of ideal gas in J/mol
    "idealGasEntropy", # Temperature-dependent part of entropy of ideal gas in J/(mol K)
    "idealGasHeatCapacity", # Heat capacity (Cp) of ideal gas in J/(mol K)
    "meltingPressure", # Pressure on melting line in Pa
    "selfDiffusionCoefficientGas", # Self-diffusion coefficient in gas phase at 1 atm in m2/s
    "selfDiffusionCoefficientLiquid", # self-diffusion coefficient in liquid phase on saturation line in m2/s
    "solidSolidPhaseTransitionPressure", # Pressure at phase transition in Pa
    "sublimationPressure", # Vapour pressure of solid on the sublimation line in Pa
    "surfaceTensionSatLiquid", # Surface tension of liquid on the saturation line in N/m
    "thermalConductivityOfLiquid", # Thermal conductivity of liquid on saturation line in W/(m K)
    "thermalConductivityOfSolid", # Thermal conductivity of solid at 1 atm in W/(m K)
    "thermalConductivityOfVapor", # Thermal conductivity of dilute gas in W/(m K)
    "vaporPressure", # Vapour pressure of saturated liquid in Pas
    "virialCoefficient", # Second virial coefficient of gas in m3/mol
    "viscosityOfLiquid", # Viscosity of liquid on saturation line in Pas
    "viscosityOfVapor", # Viscosity in dilute gas state in Pas
    "volumeChangeUponMelting", # Volume change for the solid on the melting line in m3/mol
    "volumeChangeUponSolidSolidPhaseTransition", # Volume change upon solid-solid phase transition in m3/mol
    "volumeChangeUponSublimation", # Volume change for the solid on the sublimation line in m3/mol
    "volumeChangeUponVaporization", # Volume change for the liquid on the saturation line in m3/mol
    "volumeOfLiquid", # Volume of liquid on saturation line in m3/mol
    "volumeOfSolid" # Volume of solid at 1 atm in m3/mol
  ]
  """
    Pressure dependent properties
      1- boilingPointTemperature => Temperature at liquid-vapour transition in K
      2- glassTransitionTemperature => Glass transition temperature in K
      3- meltingTemperature => Temperature on melting line in K
      4- solidSolidPhaseTransitionTemperature => Temperature at phase transition in K
  """
  pressuredependents=ASCIIString[
    "boilingPointTemperature", # Temperature at liquid-vapour transition in K
    "glassTransitionTemperature", # Glass transition temperature in K
    "meltingTemperature", # Temperature on melting line in K
    "solidSolidPhaseTransitionTemperature"  # Temperature at phase transition in K
  ]

  """
    Material Object refers to the software object that implements the
    representation of a Material.
    Material Object must:
    * Allow a client to set any basis-dependent Physical Property on any basis.
    * Allow a client to get any basis-dependent Physical Property using the basis with which it was stored.
    * Perform basis conversions, or delegate basis conversion as necessary. If basis conversion is not meaningful (e.g. in the case of cement), the Material Object must be able to return the quantity in its original basis and to return an error should the quantity be requested in a different basis.
    * Ensure that quantities set in one basis are consistent with quantities set in another basis, or delegate that function as necessary. Where the basis conversion on a quantity is not  feasible, the Material Object must only store the quantity in the basis with which it was set most recently
  """
  type MaterialObject
  end

  typealias PropertyMap Dict{ASCIIString,ASCIIString}
  propmap(stvec,t)=[cs=>t[ind] for (ind,cs) in enumerate(stvec)]

  """
    Property Package – a software component that is both a Physical Property Calculator and
    an Equilibrium Calculator for Materials containing a specific set of Compounds
    occurring in a specific number of physical states. A Property Package will make use of
    certain models to perform these calculations. A Property Package can be configured to
    make use of external Physical Property Calculators and/or Equilibrium Calculators.
    Alternatively, it can provide the functionality of these two components internally without
    making use of external components. Configuring the Compounds, Phases, models and
    external components used in a Property Package is outside the scope of this CAPEOPEN interface specification.
  """
  type PropertyPackage
    """
      Convert from perry units to cape-open standard units, for other properties default value of 1.0 is used
      6-  criticalDensity =>  mol/m3 mol/dm3 1E+3
      7-  criticalPressure =>  Pa MPa 1E+6
      9-  criticalVolume => m3/mol m3/Kmol 1E-3
      15- heatOfVaporizationAtNormalBoilingPoint => J/mol J/kmol 1E-3
      16- idealGasGibbsFreeEnergyOfFormationAt25C => J/mol J/kmol 1E-3
      18- liquidVolumeAt25C => m3/mol dm3/mol 1E-3
      24- standardEntropyGas => J/mol J/Kmol  1E-3
      28- standardFormationEnthalpyGas => J/mol  J/Kmol 1E-3
      31- standardFormationGibbsEnergyGas => J/mol J/Kmol 1E-3
      6-  heatCapacityOfLiquid => J/(mol K) J/(Kmol K) 1E-3
      11- heatOfVaporization => J/mol J/kmol 1E-3
      12- idealGasEnthalpy => J/mol J/Kmol 1E-3
      13- idealGasEntropy =>  J/(mol K) J/(Kmol K) 1E-3
      14- idealGasHeatCapacity =>  J/(mol K) J/(Kmol K) 1E-3
      32- volumeOfLiquid => m3/mol dm3/mol 1E-3
    """
    function PropertyPackage(constantstrings,t1,constantfloats,t2,tempreturedependents,t3,pressuredependents,t4,propertytable)
      new(
        propmap(constantstrings,t1),
        propmap(constantfloats,t2),
        propmap(tempreturedependents,t3),
        propmap(pressuredependents,t4),
        propertytable,
        Dict(
          "criticalDensity"                         =>1E+3, #mol/m3 <= mol/dm3
          "criticalPressure"                        =>1E+6, #Pa <= MPa
          "criticalVolume"                          =>1E-3, #m3/mol <= m3/Kmol
          "heatOfVaporizationAtNormalBoilingPoint"  =>1E-3, #J/mol <= J/kmol
          "idealGasGibbsFreeEnergyOfFormationAt25C" =>1E-3, #J/mol <= J/kmol
          "liquidVolumeAt25C"                       =>1E-3, #m3/mol <= dm3/mol
          "standardEntropyGas"                      =>1E-3, #J/mol <= J/Kmol /1E3
          "standardFormationEnthalpyGas"            =>1E-3, #J/mol <=  J/Kmol /1E3
          "standardFormationGibbsEnergyGas"         =>1E-3, #J/mol <= J/Kmol /1E3
          "heatCapacityOfLiquid"                    =>1E-3, #J/(mol K) <= J/(Kmol K) /1E3
          "heatOfVaporization"                      =>1E-3, #J/mol <= J/kmol /1E3
          "idealGasEnthalpy"                        =>1E-3, #J/mol <= J/Kmol /1E3
          "idealGasEntropy"                         =>1E-3, #J/(mol K) <= J/(Kmol K)
          "idealGasHeatCapacity"                    =>1E-3, #J/(mol K) <= J/(Kmol K)
          "volumeOfLiquid"                          =>1E-3 #m3/mol <= dm3/mol
        ),
        Dict{ASCIIString, Array}(
          "Criticals"=>       Any[[0,0,0], Vector{Float64}(6)],
          "LiquidsDensities"=>Any[[0,0,0], Vector{Float64}(10), 0%UInt8],
          "FormationEnergy"=> Any[[0,0,0], Vector{Float64}(5)],
          "Compounds"=>       Any[[0,0,0], Vector{UInt8}(29), Vector{UInt8}(9), Vector{UInt8}(11), 0.0, 0.0],
          "Cp"=>              Any[[0,0,0], Vector{Float64}(10), 0%UInt8],
          "LiquidsCp"=>       Any[[0,0,0], Vector{Float64}(10), 0%UInt8],
          "VaporizHeat"=>     Any[[0,0,0], Vector{Float64}(10)],
          "LiquidThermal"=>   Any[[0,0,0], Vector{Float64}(10)],
          "VaporPressure"=>   Any[[0,0,0], Vector{Float64}(10)],
          "LiquidViscos"=>    Any[[0,0,0], Vector{Float64}(10)],
          "VaporThermal"=>    Any[[0,0,0], Vector{Float64}(9)],
          "VaporViscos"=>     Any[[0,0,0], Vector{Float64}(9)]
        ),
        "perryanalytic"
      )
    end
    constantstrings::PropertyMap
    constantfloats::PropertyMap
    tempreturedependents::PropertyMap
    pressuredependents::PropertyMap
    propertytable::Vector{ASCIIString}
    convertions::Dict{ASCIIString,Float64}
    tableaddreses::Dict{ASCIIString, Array}
    datafilename::ASCIIString
  end
  perryanalytic=PropertyPackage(
    getindex(constantstrings,1:3),
    ["Compounds" for i=1:3],
    getindex(constantfloats,[ 5,6,7,8,9,15,16,17,18,
                                20,21,24,28,31]),
    ["Criticals", "Criticals", "Criticals", "Criticals", "Criticals", "VaporizHeat", "FormationEnergy", "LiquidsDensities", "LiquidsDensities",
     "Compounds", "Compounds", "FormationEnergy", "FormationEnergy", "FormationEnergy"],
    getindex(tempreturedependents,[6,11,12,13,14,21,23,24,26,27,32]),
    ["LiquidsCp", "VaporizHeat", "Cp", "Cp", "Cp", "LiquidThermal", "VaporThermal", "VaporPressure", "LiquidViscos", "VaporViscos", "LiquidsDensities"],
    ["boilingPointTemperature"],
    ["VaporPressure"],
    ASCIIString[
      "Compounds"        ,
      "VaporPressure"    ,
      "LiquidsDensities",
      "Criticals"        ,
      "VaporizHeat"      ,
      "LiquidsCp"        ,
      "Cp"                ,
      "FormationEnergy" ,
      "VaporViscos"      ,
      "LiquidViscos"     ,
      "VaporThermal"     ,
      "LiquidThermal"
    ]
  )
end


##########################################################################
# The RandomLargeScaleNetwork is a network whose geography changes in
# each user drop. Both BSs and MSs are uniformly distributed over
# a rectangle.

##########################################################################
# Network definition
type RandomLargeScaleNetwork{MS_t <: PhysicalMS, BS_t <: PhysicalBS, System_t <: System, PropagationEnvironment_t <: PropagationEnvironment} <: PhysicalNetwork
    MSs::Vector{MS_t}
    BSs::Vector{BS_t}

    system::System_t
    num_MSs_per_cell::Int
    propagation_environment::PropagationEnvironment_t
    geography_size::@compat Tuple{Float64, Float64}
    MS_serving_BS_distance::Nullable{Float64} # null signifies random placement of MSs. Otherwise they are placed on a circle from the BS.
    aux_network_params::AuxNetworkParams

    assignment::Assignment
end

# Convenience constructor without network params and assignments
RandomLargeScaleNetwork(MSs, BSs, system, num_MSs_per_cell, propagation_environment, geography_size, MS_serving_BS_distance) =
    RandomLargeScaleNetwork(MSs, BSs, system, num_MSs_per_cell, propagation_environment, geography_size, MS_serving_BS_distance, AuxNetworkParams(), Assignment())

Base.show(io::IO, x::RandomLargeScaleNetwork) =
    print(io, "RandomLargeScaleNetwork(I = $(length(x.BSs)), Kc = $(x.num_MSs_per_cell), geography_size = $(x.geography_size), MS_serving_BS_distance = $(x.MS_serving_BS_distance))")
Base.showcompact(io::IO, x::RandomLargeScaleNetwork) =
    print(io, "RandomLargeScaleNetwork($(length(x.BSs)), $(x.num_MSs_per_cell), $(x.geography_size), $(x.MS_serving_BS_distance))")

function setup_random_large_scale_network(
    num_BSs, num_MSs_per_cell, num_MS_antennas, num_BS_antennas;
    system = SinglecarrierSystem(2e9, 15e3),
    propagation_environment = SimpleLargescaleFadingEnvironment(37.6, 15.3, 10, 8),
    geography_size = (1500., 1500.),
    MS_serving_BS_distance = Nullable(150.), # Nullable{Float64}() for random MS placement
    transmit_power = 10^(18.2/10), transmit_powers = transmit_power*ones(Float64, num_BSs),
    BS_antenna_gain_params = [ OmnidirectionalAntennaParams(0) for idx = 1:num_BSs ],
    user_priority = 1., user_priorities = user_priority*ones(Float64, num_BSs*num_MSs_per_cell),
    num_streams = 1, num_streamss = num_streams*ones(Int, num_BSs*num_MSs_per_cell),
    MS_antenna_gain_dB = 0., MS_antenna_gains_dB = MS_antenna_gain_dB*ones(Float64, num_BSs*num_MSs_per_cell),
    receiver_noise_figure = 9., receiver_noise_figures = receiver_noise_figure*ones(Float64, num_BSs*num_MSs_per_cell))

    isa(num_MS_antennas, Vector) || (num_MS_antennas = num_MS_antennas*ones(Int, num_BSs*num_MSs_per_cell))
    isa(num_BS_antennas, Vector) || (num_BS_antennas = num_BS_antennas*ones(Int, num_BSs))

    BSs = [ PhysicalBS(num_BS_antennas[i], Position(0, 0), transmit_powers[i], BS_antenna_gain_params[i]) for i = 1:num_BSs ]
    MSs = [ PhysicalMS(num_MS_antennas[k], Position(0, 0), Velocity(0, 0), user_priorities[k], num_streamss[k], MS_antenna_gains_dB[k], receiver_noise_figures[k], SimpleLargescaleFadingEnvironmentState(zeros(Float64, num_BSs), falses(num_BSs))) for k = 1:num_BSs*num_MSs_per_cell ]

    RandomLargeScaleNetwork(MSs, BSs, system, num_MSs_per_cell, propagation_environment, geography_size, MS_serving_BS_distance)
end

##########################################################################
# SNR functions (only work if MS_serving_BS_distance is not null)

function get_average_SNRs_dB{MS_t <: PhysicalMS, BS_t <: PhysicalBS, System_t <: System}(network::RandomLargeScaleNetwork{MS_t, BS_t, System_t, SimpleLargescaleFadingEnvironment})
    isnull(network.MS_serving_BS_distance) && error("Can only call get_average_SNRs_dB if BS-MS distance for served MSs is specified.")

    I = get_num_BSs(network); Kc = network.num_MSs_per_cell
    pathloss_alpha = network.propagation_environment.pathloss_alpha
    pathloss_beta = network.propagation_environment.pathloss_beta

    SNRs_dB = zeros(Float64, I*Kc)
    for i = 1:I
        Pt_dBm = get_transmit_power_dBm(network.BSs[i])
        for k = (i-1)*Kc+1:i*Kc
            sigma2_dBm = get_receiver_noise_power_dBm(network.MSs[k], network)
            SNRs_dB[k] = Pt_dBm - sigma2_dBm - pathloss_beta - pathloss_alpha*log10(get(network.MS_serving_BS_distance))
        end
    end

    return SNRs_dB
end

get_average_SNRs{MS_t <: PhysicalMS, BS_t <: PhysicalBS, System_t <: System}(network::RandomLargeScaleNetwork{MS_t, BS_t, System_t, SimpleLargescaleFadingEnvironment}) =
    10.^(get_average_SNRs_dB(network)/10)

function set_average_SNRs_dB!{MS_t <: PhysicalMS, BS_t <: PhysicalBS, System_t <: System}(network::RandomLargeScaleNetwork{MS_t, BS_t, System_t, SimpleLargescaleFadingEnvironment}, SNR_dB)
    isnull(network.MS_serving_BS_distance) && error("Can only call set_average_SNRs_dB! if BS-MS distance for served MSs is specified.")

    I = get_num_BSs(network); Kc = network.num_MSs_per_cell
    pathloss_alpha = network.propagation_environment.pathloss_alpha
    pathloss_beta = network.propagation_environment.pathloss_beta

    # Set transmit power of each BS based on the noise characteristics of its first receiving MS
    for i = 1:I
        k1 = (i-1)*Kc+1
        sigma2_dBm = get_receiver_noise_power_dBm(network.MSs[k1], network)
        Pt_dBm = SNR_dB + sigma2_dBm + pathloss_beta + pathloss_alpha*log10(get(network.MS_serving_BS_distance))
        set_transmit_power_dBm!(network.BSs[i], Pt_dBm)
    end
end

set_average_SNRs!{MS_t <: PhysicalMS, BS_t <: PhysicalBS, System_t <: System}(network::RandomLargeScaleNetwork{MS_t, BS_t, System_t, SimpleLargescaleFadingEnvironment}, SNR) =
    set_average_SNR_dB!(network, 10*log10(SNR))

##########################################################################
# Cell assignment functions

# Standard cell assignment functions, only applicable if MS_serving_BS_distance is not null
function IDCellAssignment!(channel, network::RandomLargeScaleNetwork)
    isnull(network.MS_serving_BS_distance) && error("Can only call IDCellAssignment! if BS-MS distance for served MSs is specified.")

    I = get_num_BSs(network); Kc = network.num_MSs_per_cell
    cell_assignment = Array(Int, I*Kc)

    for i = 1:I
        cell_assignment[(i-1)*Kc+1:i*Kc] = i
    end

    network.assignment = Assignment(cell_assignment, I)

    return AssignmentResults()
end

function LargeScaleFadingCellAssignment!(channel, network::RandomLargeScaleNetwork)
    if isnull(network.MS_serving_BS_distance)
        # Greedy scheduler based on the large scale fading realizations
        I = get_num_BSs(network); K = get_num_MSs(network)

        aux_params = get_aux_assignment_params(network)
        @defaultize_param! aux_params "max_num_MSs_per_BS" 1

        # Scheduling matrix
        cell_assignment_matrix = zeros(Int, K, I)

        # User selection metric
        F = (channel.large_scale_fading_factor.^2)*Diagonal(get_transmit_powers(network))
        Fsize = size(F)

        # Do greedy scheduling
        while !all(F .== 0.)
            _, idx = findmax(F)
            k, l = ind2sub(Fsize, idx)

            if sum(cell_assignment_matrix[:,l]) < aux_params["max_num_MSs_per_BS"]
                cell_assignment_matrix[k,l] = 1
                F[k,:] = 0.
            else
                F[:,l] = 0.
            end
        end

        network.assignment = Assignment(cell_assignment_matrix)

        return AssignmentResults()
    else
        # MSs served by their pre-specified BS
        return IDCellAssignment!(channel, network)
    end
end

##########################################################################
# Simulation functions
function draw_user_drop!{MS_t <: PhysicalMS, BS_t <: PhysicalBS, System_t <: System}(network::RandomLargeScaleNetwork{MS_t, BS_t, System_t, SimpleLargescaleFadingEnvironment})
    I = get_num_BSs(network); K = get_num_MSs(network)
    Kc = network.num_MSs_per_cell

    if isnull(network.MS_serving_BS_distance)
        # Drop BSs uniformly at random
        for i = 1:I
            BS_x = network.geography_size[1]*rand(); BS_y = network.geography_size[2]*rand()
            network.BSs[i].position = Position(BS_x, BS_y)
        end
        # Drop MSs uniformly at random
        for k = 1:K
            MS_x = network.geography_size[1]*rand(); MS_y = network.geography_size[2]*rand()
            network.MSs[k].position = Position(MS_x, MS_y)
        end
    else
        # Drop BSs uniformly at random. Also drop the correspondingly served MSs.
        for i = 1:I
            BS_x = network.geography_size[1]*rand(); BS_y = network.geography_size[2]*rand()
            network.BSs[i].position = Position(BS_x, BS_y)

            for k_idx = 1:Kc
                k = (i-1)*Kc + k_idx

                # Position
                theta = 2*pi*rand()
                dx = get(network.MS_serving_BS_distance)*cos(theta)
                dy = get(network.MS_serving_BS_distance)*sin(theta)
                network.MSs[k].position = Position(BS_x + dx, BS_y + dy)

                # Shadow fading (uncorrelated between BSs)
                network.MSs[k].propagation_environment_state =
                    SimpleLargescaleFadingEnvironmentState(network.propagation_environment.shadow_sigma_dB*randn(I), falses(I)) # LoS not used in this model
            end
        end
    end
end

function draw_channel{MS_t <: PhysicalMS, BS_t <: PhysicalBS}(network::RandomLargeScaleNetwork{MS_t, BS_t, SinglecarrierSystem, SimpleLargescaleFadingEnvironment})
    K = get_num_MSs(network); I = get_num_BSs(network)
    Ns = get_num_MS_antennas(network); Ms = get_num_BS_antennas(network)

    coefs = Array(Matrix{Complex128}, K, I)
    large_scale_fading_factor = Array(Float64, K, I)

    distances = get_distances(network)
    angles = get_angles(network)

    for k = 1:K
        for i = 1:I
            # Small scale fading
            coefs[k,i] = (1/sqrt(2))*(randn(Ns[k], Ms[i])
                                 + im*randn(Ns[k], Ms[i]))

            # Pathloss
            pathloss_factor = sqrt(10^(-(network.propagation_environment.pathloss_beta + network.propagation_environment.pathloss_alpha*log10(distances[k,i]))/10))

            # Shadow fading
            shadow_factor = sqrt(10^(network.MSs[k].propagation_environment_state.shadow_realizations_dB[i]/10))

            # Penetration loss
            penetration_loss_factor = sqrt(10^(-network.propagation_environment.penetration_loss_dB/10))

            # BS antenna gain
            bs_antenna_gain = sqrt(get_antenna_gain(network.BSs[i].antenna_params, angles[k,i]))

            # MS antenna gain
            ms_antenna_gain = sqrt(10^(network.MSs[k].antenna_gain_dB/10))

            # Apply scale factors
            large_scale_fading_factor[k,i] = pathloss_factor*shadow_factor*penetration_loss_factor*bs_antenna_gain*ms_antenna_gain
            coefs[k,i] *= large_scale_fading_factor[k,i]
        end
    end

    return SinglecarrierChannel(coefs, Ns, Ms, K, I, large_scale_fading_factor)
end

##########################################################################
# Visualization functions
function plot_network_layout(network::RandomLargeScaleNetwork)
    I = get_num_BSs(network); K = get_num_MSs(network)

    fig = PyPlot.figure()
    ax = fig[:add_subplot](1, 1, 1)

    # Rectangle edges
    Mx = network.geography_size[1]; My = network.geography_size[2]
    ax[:plot]([0, Mx], [0, 0], color="b", linestyle="-")
    ax[:plot]([0, Mx], [My, My], color="b", linestyle="-")
    ax[:plot]([0, 0], [0, My], color="b", linestyle="-")
    ax[:plot]([Mx, Mx], [0, My], color="b", linestyle="-")

    # MSs
    for i = 1:I
        pos = network.BSs[i].position
        ax[:plot](pos.x, pos.y; marker="x", color="b", markersize=6)
        ax[:text](pos.x, pos.y, "BS $i")
    end

    # MSs
    for k = 1:K
        pos = network.MSs[k].position
        ax[:plot](pos.x, pos.y; marker="o", color="r", markersize=6)
        ax[:text](pos.x, pos.y, "MS $k")
    end

    display(fig)
end


using SnakePlot, GLMakie

path = "SnakePlot/example/piezo2.txt"
tms = [13:24, 31:43, 51:76, 123:141, 222:237, 241:258, 265:287, 336:355,
		493:514, 520:531, 536:562, 584:614, 690:703, 710:728, 738:757, 790:811,
		958:973, 980:989, 998:1018, 1075:1099, 1141:1155, 1158:1171, 1183:1202,
		1240:1260, 1315:1327, 1334:1346, 1356:1381, 1431:1447, 1992:2006, 2014:2025,
		2032:2053, 2087:2105, 2260:2279, 2302:2322, 2327:2350, 2360:2382, 2468:2491,
		2740:2760];

seq = readFasta(path)
seqdf = fasta2df(seq)
structdf = assignStructure(seqdf, tms)
positionTMs!(structdf, -1)

loops = @view structdf[structdf.element .== "loop", :]
loopNums = unique(loops.structMap)

yOffset = 0.2
for i in loopNums
    loop = @view loops[loops.structMap .== i, :]
	startRes = loop[1,:resNum]
	n = size(loop, 1)
    prevX = loop.structMap[1] - 0.25
    nxtX = prevX + 1

    ## Check if cytoplasmic or extracellular loop
    if structdf[startRes-1, :y][1] >= 0.5
		side = 1
		prevY = 1
	else
		side = -1
		prevY = 0
	end

    ## Just a line if it is small
	if n <= 5
		dX = (nxtX - prevX)/(n-1)
		for i in 1:size(loop, 1)
			structdf[loop[i, :resNum], :x] = prevX + dX[1] * (i-1)
			structdf[loop[i, :resNum], :y] = prevY + side * yOffset
		end

    ## Square parabola if it is fairly small
    elseif n > 5 && n <= 20
        ## Check if the loop has an odd or even length
        if n%2 != 0
            nBot = 5
        else
            nBot = 4
        end

        ## Divide loop among structure
        leg = div(n - nBot, 2)
        lft = 1:leg
        bot = leg+1:leg+1+nBot
        rgt = n-(leg-1):n

        ## Assign positions
        dY = 0.15
        dX = (nxtX-prevX)/(nBot-1)
        for i in lft
            structdf[loop[i, :resNum], :x] = prevX
            structdf[loop[i, :resNum], :y] = prevY + side*(yOffset + (i-1) * dY)
        end
        for i in bot
            structdf[loop[i, :resNum], :x] = prevX + dX * (i-bot[1])
            structdf[loop[i, :resNum], :y] = prevY + side*(yOffset + (bot[1]-1) * dY)
        end
        structdf[loop[rgt, :resNum], :y] = reverse(loop[lft, :y])
        structdf[loop[rgt, :resNum], :x] .= nxtX
    elseif n > 40 && n <= 120
        loopLayout!(loop, 4, n, prevX, nxtX, prevY, side)
    elseif n > 120 && n <= 240
        loopLayout!(loop, 6, n, prevX, nxtX, prevY, side, expand=2)
    else
        loopLayout!(loop, 12, n, prevX, nxtX, prevY, side, expand=6)
    end
end

f = Figure(resolution=(2000,800))
ax = Axis(f[1, 1], xlabel = "", ylabel = "",
    title = "", grid=:none)
hlines!(ax, 1.1, color=:black, linewidth=2)
hlines!(ax, -0.1, color=:black, linewidth=2)
lines!(structdf.x, structdf.y, strokewidth=1, color = :black)
scatter!(structdf.x, structdf.y, color=:orange, strokewidth=1, strokecolor = :black, markersize=8)
ylims!(ax, -5, 5)
display(f)


# ScatteredBlob
immutable ScatteredBlob{NODE<:AbstractNode} <: Mocha.Blob
    nodes::DataStructures.SortedDict{Path,NODE}
end

function Base.show(io::IO, blob::ScatteredBlob)
    n_nodes = length(blob.nodes)
    plural = n_nodes > 1 ? "s" : ""
    print(io, "ScatteredBlob(", n_nodes, " node", plural, ")")
end

function forward!(
        backend,
        blob_out::ScatteredBlob,
        bank::Bank1D,
        blob_in::ScatteredBlob)
    pathdepth(blob_in, bank.behavior.pathkey)
    map(node -> pathdepth(bank.behavior.pathkey, keys(blob_in.nodes)))
    γkey = cons(Literal(:γ, 1), bank.behavior.pathkey)
    for j in bank.behavior.j_range
        for χ in 0:(bank.spec.n_filters_per_octave-1)
            ψ = bank.ψs[1 + θ, 1 + χ, 1 + j]
            for (path_in, node_in) in input.nodes
                path_out = copy(path_in)
                path_out[γkey] = γ
                transform!(blob[path_out], blob_in[path_in], ψ)
            end
        end
    end
end


using BioMedQuery.Processes
using BioMedQuery.Entrez
using BioMedQuery.UMLS
using JLD
using MySQL

"""
    function osa_pychiatric_comorbidities()
PuMed/Medline comorbidities explained in the following paper
Identifying Psychiatric Comorbidities for Obstructive Sleep Apnea in
the Biomedical Literature and Electronic Health Record
Haddad and Chen. Brown University. 2016

Code by: Isabel Restrepo
BCBI - Brown University
Version: Julia 0.5

Before running, configure your MySQL onnection by adding to ~/.juliarc.jl
the following variables:

ENV["OSA_DB_HOST"]="db_host"
ENV["OSA_DB_USER"]="your_user"
ENV["OSA_DB_PSSWD"]="your_password"

You alseo need to configure the email addess associated with you NCBI account,
and USER and PASSWD associated with your UMLS/UTS account.
"""

using BCBIVizUtils
using Colors
using JLD

function load_occurrance_file(; results_dir = "results/",)
    file  = jldopen(results_dir* "/occur_sp.jdl", "r")
    occur = read(file, "occur")
    # display(occur)
    file  = jldopen(results_dir* "/mesh2ind.jdl", "r")
    mesh2ind = read(file, "mesh2ind")

    # display(mesh2ind)
    #invert map
    ind2label = Dict()
    for key  in keys(mesh2ind)
        val = mesh2ind[key]
        ind2label[val] = key
    end
    labels = Array{ASCIIString}(length(ind2label))
    for i in range(1,length(ind2label))
        labels[i] = ind2label[i]
    end

    coo = occur* occur.'

    graph = BCBIVizUtils.coo2graph(coo)
    colors = distinguishable_colors(length(ind2label), RGB(1,0,0))

    return graph, labels, colors
end

function plot3D()
    graph, labels, colors = init()
    BCBIVizUtils.graph2plotlyjs3D(graph, labels, colors)
end

function osa_pychiatric_comorbidities(; results_dir = "results/",
                                        max_articles = typemax(Int64),
                                        run_pubmed_search_and_save = true,
                                        run_mesh2umls_map = true,
                                        filter_semantic_occurrences  = true,)

    # 1. Database configuration
    db_host = ENV["OSA_DB_HOST"]
    mysql_usr=ENV["OSA_DB_USER"]
    mysql_pswd=ENV["OSA_DB_PSSWD"]
    dbname="osa_pychiatric_comorbidities"
    overwrite=true
    db = nothing

    db_config = Dict(:host=>db_host,
                  :dbname=>dbname,
                  :username=>mysql_usr,
                  :pswd=>mysql_pswd,
                  :overwrite=>overwrite)

    # 2. PubMed Search
    if run_pubmed_search_and_save
        email= ENV["NCBI_EMAIL"]
        search_term="(sleep apnea, obstructive[MeSH Major Topic])"
        overwrite=true
        verbose = false

        db = pubmed_search_and_save(email, search_term, max_articles,
        save_efetch_mysql, db_config, verbose)
    end

    #3. MAP all MeSH to UMLS concepts
    if run_mesh2umls_map

        if db == nothing
            db = mysql_connect(db_host, mysql_usr, mysql_pswd, dbname)
        end
        user = ENV["UMLS_USER"]
        psswd = ENV["UMLS_PSSWD"]
        credentials = Credentials(user, psswd)
        append = false

        map_mesh_to_umls_async!(db, credentials; append_results=append)
    end

    #4. Filter those that are only Psychiatric Mental Disorders
    if filter_semantic_occurrences

        if db == nothing
            db = mysql_connect(db_host, mysql_usr, mysql_pswd, dbname)
        end

        umls_concept = "Mental or Behavioral Dysfunction"

        if !isdir(results_dir)
            mkdir(results_dir)
        end

        occur_path = results_dir*"/occur_sp.jdl"
        labels2ind_path = results_dir*"/labels2ind.jdl"

        labels2ind, occur = umls_semantic_occurrences(db, umls_concept)

        println("-------------------------------------------------------------")
        println("Output Descritor to Index Dictionary")
        println(labels2ind)
        println("-------------------------------------------------------------")

        println("-------------------------------------------------------------")
        println("Output Data Matrix")
        println(occur)
        println("-------------------------------------------------------------")

        # save(occur_path, "occur", occur)
        jldopen(occur_path, "w") do file
         write(file, "occur", occur)
        end
        jldopen(labels2ind_path, "w") do file
         write(file, "labels2ind", labels2ind)
        end
    end

    #5. Plot comorbidities graph

end

# osa_pychiatric_comorbidities(results_dir = "/Users/isa/dropbox_brown/results/osa_pychiatric_pubmed_comorbidities",
# max_articles = 5, run_pubmed_search_and_save = false)

osa_pychiatric_comorbidities(results_dir = "/Users/isa/dropbox_brown/results/osa_pychiatric_pubmed_comorbidities", run_pubmed_search_and_save = false)


using DataFrames
using DataFramesMeta
using CSV

d = Pkg.dir("ContinuousPOMDPTreeSearchExperiments", "icaps_2018","data")

solver_order = ["pomcpow" => "POMCPOW",
                "pft" => "PFT-DPW",
                "qmdp" => "QMDP",
                "pomcpdpw" => "POMCP-DPW",
                "despot" => "DESPOT",
                "d_pomcp" => "POMCP\\textsuperscript{D}",
                "d_despot" => "DESPOT\\textsuperscript{D}"]

cardinality = Dict("lasertag" => "(D, D, D)",
                   "lightdark" => "(D, D, C)",
                   "subhunt" => "(D, D, C)",
                   "vdpbarrier" => "(C, C, C)",
                   "vdptag" => "(C, C, C)")

problem_order = ["lasertag", "lightdark", "subhunt", "vdpbarrier"]

filenames = Dict("simpleld"=>"/home/zach/.julia/v0.6/ContinuousPOMDPTreeSearchExperiments/icaps_2018/data/simpleld_Monday_5_Mar_21_39.csv","subhunt"=>"/home/zach/.julia/v0.6/ContinuousPOMDPTreeSearchExperiments/icaps_2018/data/subhunt_Monday_5_Mar_22_08.csv","lasertag"=>"/home/zach/.julia/v0.6/ContinuousPOMDPTreeSearchExperiments/icaps_2018/data/lasertag_Monday_5_Mar_20_17.csv","vdpbarrier"=>"/home/zach/.julia/v0.6/ContinuousPOMDPTreeSearchExperiments/icaps_2018/data/vdpbarrier_Monday_5_Mar_23_01.csv")
filenames["lightdark"] = filenames["simpleld"]

# filenames = Dict("lasertag" => "$(Pkg.dir("ContinuousPOMDPTreeSearchExperiments"))/icaps_2018/data/lasertag_Monday_26_Feb_18_46.csv",
#                  "lightdark" => "$(Pkg.dir("ContinuousPOMDPTreeSearchExperiments"))/icaps_2018/data/simpleld_Monday_26_Feb_20_13.csv",
#                  "subhunt" => "$(Pkg.dir("ContinuousPOMDPTreeSearchExperiments"))/icaps_2018/data/subhunt_Monday_26_Feb_20_44.csv",
#                  "vdpbarrier" => "$(Pkg.dir("ContinuousPOMDPTreeSearchExperiments"))/icaps_2018/data/bdpbarrier_Monday_26_Feb_21_42.csv")

data = Dict(
    "lightdark" => Dict(
        "limits" => (-20.0, 80.0),
        "name" => "Light Dark"
    ),

    "subhunt" => Dict(
        "limits" => (0.0, 80.0),
        "name" => "Sub Hunt"
    ),

    "lasertag" => Dict(
        "limits" => (-20.0, -8.0),
        "name" => "Laser Tag"
    ),

    "vdptag" => Dict(
        "limits" => (-20.0, 40.0),
        "name" => "VDP Tag"
    ),

    "vdpbarrier" => Dict(
        "limits" => (0.0, 31.0),
        "name" => "VDP Tag"
    ),

)

for p in problem_order
    df = CSV.read(filenames[p])
    d = data[p]
    for s in unique(df[:solver])
        rs = @where(df, :solver.==s)[:reward]
        m = mean(rs)
        sem = std(rs)/sqrt(length(rs))
        if s == "ar_despot"
            s = "despot"
        elseif s == "despot_01"
            s = "despot"
        elseif p == "lasertag" && s == "pomcp"
            s = "d_pomcp"
        end
        d[s] = (m, sem)
    end
end

hbuf = IOBuffer()

for k in problem_order
    print(hbuf, "& $(data[k]["name"]) \\makebox[0pt][l]{$(cardinality[k])} & ")
end
print(hbuf, "\\\\")

tbuf = IOBuffer()
for (k, n) in solver_order
    print(tbuf, n*" ")
    for p in problem_order
        d = data[p]
        if haskey(d, k)
            m, sem = d[k]
            lo, hi = d["limits"]
            frac = (m-lo)/(hi-lo)
            @printf(tbuf, "& \\result{%.1f}{%.1f}{%d}{%.2f} ",
                    m, sem, round(Int, 100*frac), frac
                   )
        else
            print(tbuf,  "& \\noresult{} ")
        end
    end
    print(tbuf, "\\\\\n")
end

columns = "l"*"rl"^length(data)

tabletex = """
    \\begin{tabular}{$columns}
        \\toprule
            $(String(hbuf))
        \\midrule
            $(String(tbuf))
        \\bottomrule
    \\end{tabular}
"""

println(tabletex)