

from rdkit import Chem

from guacamol.common_scoring_functions import (
    TanimotoScoringFunction,
    RdkitScoringFunction,
    CNS_MPO_ScoringFunction,
    IsomerScoringFunction,
    SMARTSScoringFunction,
)
from guacamol.goal_directed_benchmark import GoalDirectedBenchmark
from guacamol.goal_directed_score_contributions import uniform_specification
from guacamol.score_modifier import (
    MinGaussianModifier,
    MaxGaussianModifier,
    ClippedScoreModifier,
    GaussianModifier,
)
from guacamol.scoring_function import (
    ArithmeticMeanScoringFunction,
    GeometricMeanScoringFunction,
    ScoringFunction,
)
from guacamol.utils.descriptors import (
    num_rotatable_bonds,
    num_aromatic_rings,
    logP,
    qed,
    tpsa,
    bertz,
    mol_weight,
    AtomCounter,
    num_rings,
)


def isomers_c11h24(mean_function="geometric") -> GoalDirectedBenchmark:
    """
    Benchmark to try and get all C11H24 molecules there are.
    There should be 159 if one ignores stereochemistry.
    Args:
        mean_function: 'arithmetic' or 'geometric'
    """

    specification = uniform_specification(159)

    return GoalDirectedBenchmark(
        name="C11H24",
        objective=IsomerScoringFunction("C11H24", mean_function=mean_function),
        contribution_specification=specification,
    )


def isomers_c7h8n2o2(mean_function="geometric") -> GoalDirectedBenchmark:
    """
    Benchmark to try and get 100 isomers for C7H8N2O2.
    Args:
        mean_function: 'arithmetic' or 'geometric'
    """

    specification = uniform_specification(100)

    return GoalDirectedBenchmark(
        name="C7H8N2O2",
        objective=IsomerScoringFunction("C7H8N2O2", mean_function=mean_function),
        contribution_specification=specification,
    )


def isomers_c9h10n2o2pf2cl(mean_function="geometric", n_samples=250) -> GoalDirectedBenchmark:
    """
    Benchmark to try and get 100 isomers for C9H10N2O2PF2Cl.
    Args:
        mean_function: 'arithmetic' or 'geometric'
    """

    specification = uniform_specification(n_samples)

    return GoalDirectedBenchmark(
        name="C9H10N2O2PF2Cl",
        objective=IsomerScoringFunction("C9H10N2O2PF2Cl", mean_function=mean_function),
        contribution_specification=specification,
    )


def hard_cobimetinib(max_logP=5.0) -> GoalDirectedBenchmark:
    smiles = "OC1(CN(C1)C(=O)C1=C(NC2=C(F)C=C(I)C=C2)C(F)=C(F)C=C1)C1CCCCN1"

    modifier = ClippedScoreModifier(upper_x=0.7)
    os_tf = TanimotoScoringFunction(smiles, fp_type="FCFP4", score_modifier=modifier)
    os_ap = TanimotoScoringFunction(
        smiles, fp_type="ECFP6", score_modifier=MinGaussianModifier(mu=0.75, sigma=0.1)
    )

    rot_b = RdkitScoringFunction(
        descriptor=num_rotatable_bonds, score_modifier=MinGaussianModifier(mu=3, sigma=1)
    )

    rings = RdkitScoringFunction(
        descriptor=num_aromatic_rings, score_modifier=MaxGaussianModifier(mu=3, sigma=1)
    )

    t_cns = ArithmeticMeanScoringFunction(
        [os_tf, os_ap, rot_b, rings, CNS_MPO_ScoringFunction(max_logP=max_logP)]
    )

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name="Cobimetinib MPO", objective=t_cns, contribution_specification=specification
    )


def hard_osimertinib(mean_cls=GeometricMeanScoringFunction) -> GoalDirectedBenchmark:
    smiles = "COc1cc(N(C)CCN(C)C)c(NC(=O)C=C)cc1Nc2nccc(n2)c3cn(C)c4ccccc34"

    modifier = ClippedScoreModifier(upper_x=0.8)
    similar_to_osimertinib = TanimotoScoringFunction(
        smiles, fp_type="FCFP4", score_modifier=modifier
    )

    but_not_too_similar = TanimotoScoringFunction(
        smiles, fp_type="ECFP6", score_modifier=MinGaussianModifier(mu=0.85, sigma=0.1)
    )

    tpsa_over_100 = RdkitScoringFunction(
        descriptor=tpsa, score_modifier=MaxGaussianModifier(mu=100, sigma=10)
    )

    logP_scoring = RdkitScoringFunction(
        descriptor=logP, score_modifier=MinGaussianModifier(mu=1, sigma=1)
    )

    make_osimertinib_great_again = mean_cls(
        [similar_to_osimertinib, but_not_too_similar, tpsa_over_100, logP_scoring]
    )

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name="Osimertinib MPO",
        objective=make_osimertinib_great_again,
        contribution_specification=specification,
    )


def hard_fexofenadine(mean_cls=GeometricMeanScoringFunction) -> GoalDirectedBenchmark:
    """
    make fexofenadine less greasy
    :return:
    """
    smiles = "CC(C)(C(=O)O)c1ccc(cc1)C(O)CCCN2CCC(CC2)C(O)(c3ccccc3)c4ccccc4"

    modifier = ClippedScoreModifier(upper_x=0.8)
    similar_to_fexofenadine = TanimotoScoringFunction(smiles, fp_type="AP", score_modifier=modifier)

    tpsa_over_90 = RdkitScoringFunction(
        descriptor=tpsa, score_modifier=MaxGaussianModifier(mu=90, sigma=10)
    )

    logP_under_4 = RdkitScoringFunction(
        descriptor=logP, score_modifier=MinGaussianModifier(mu=4, sigma=1)
    )

    optimize_fexofenadine = mean_cls([similar_to_fexofenadine, tpsa_over_90, logP_under_4])

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name="Fexofenadine MPO",
        objective=optimize_fexofenadine,
        contribution_specification=specification,
    )


def start_pop_ranolazine() -> GoalDirectedBenchmark:
    ranolazine = "COc1ccccc1OCC(O)CN2CCN(CC(=O)Nc3c(C)cccc3C)CC2"

    modifier = ClippedScoreModifier(upper_x=0.7)
    similar_to_ranolazine = TanimotoScoringFunction(
        ranolazine, fp_type="AP", score_modifier=modifier
    )

    logP_under_4 = RdkitScoringFunction(
        descriptor=logP, score_modifier=MaxGaussianModifier(mu=7, sigma=1)
    )

    aroma = RdkitScoringFunction(
        descriptor=num_aromatic_rings, score_modifier=MinGaussianModifier(mu=1, sigma=1)
    )

    fluorine = RdkitScoringFunction(
        descriptor=AtomCounter("F"), score_modifier=GaussianModifier(mu=1, sigma=1.0)
    )

    optimize_ranolazine = ArithmeticMeanScoringFunction(
        [similar_to_ranolazine, logP_under_4, fluorine, aroma]
    )

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name="Ranolazine MPO",
        objective=optimize_ranolazine,
        contribution_specification=specification,
        starting_population=[ranolazine],
    )


def weird_physchem() -> GoalDirectedBenchmark:
    min_bertz = RdkitScoringFunction(
        descriptor=bertz, score_modifier=MaxGaussianModifier(mu=1500, sigma=200)
    )

    mol_under_400 = RdkitScoringFunction(
        descriptor=mol_weight, score_modifier=MinGaussianModifier(mu=400, sigma=40)
    )

    aroma = RdkitScoringFunction(
        descriptor=num_aromatic_rings, score_modifier=MinGaussianModifier(mu=3, sigma=1)
    )

    fluorine = RdkitScoringFunction(
        descriptor=AtomCounter("F"), score_modifier=GaussianModifier(mu=6, sigma=1.0)
    )

    opt_weird = ArithmeticMeanScoringFunction([min_bertz, mol_under_400, aroma, fluorine])

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name="Physchem MPO", objective=opt_weird, contribution_specification=specification
    )


def similarity_cns_mpo(smiles, molecule_name, max_logP=5.0) -> GoalDirectedBenchmark:
    benchmark_name = f"{molecule_name}"
    os_tf = TanimotoScoringFunction(smiles, fp_type="FCFP4")
    os_ap = TanimotoScoringFunction(smiles, fp_type="AP")
    anti_fp = TanimotoScoringFunction(
        smiles, fp_type="ECFP6", score_modifier=MinGaussianModifier(mu=0.70, sigma=0.1)
    )

    t_cns = ArithmeticMeanScoringFunction(
        [os_tf, os_ap, anti_fp, CNS_MPO_ScoringFunction(max_logP=max_logP)]
    )

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name=benchmark_name, objective=t_cns, contribution_specification=specification
    )


def similarity(
    smiles: str,
    name: str,
    fp_type: str = "ECFP4",
    threshold: float = 0.7,
    rediscovery: bool = False,
) -> GoalDirectedBenchmark:
    category = "rediscovery" if rediscovery else "similarity"
    benchmark_name = f"{name} {category}"

    modifier = ClippedScoreModifier(upper_x=threshold)
    scoring_function = TanimotoScoringFunction(
        target=smiles, fp_type=fp_type, score_modifier=modifier
    )
    if rediscovery:
        specification = uniform_specification(1)
    else:
        specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name=benchmark_name, objective=scoring_function, contribution_specification=specification
    )


def logP_benchmark(target: float) -> GoalDirectedBenchmark:
    benchmark_name = f"logP (target: {target})"
    objective = RdkitScoringFunction(
        descriptor=logP, score_modifier=GaussianModifier(mu=target, sigma=1)
    )

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name=benchmark_name, objective=objective, contribution_specification=specification
    )


def tpsa_benchmark(target: float) -> GoalDirectedBenchmark:
    benchmark_name = f"TPSA (target: {target})"
    objective = RdkitScoringFunction(
        descriptor=tpsa, score_modifier=GaussianModifier(mu=target, sigma=20.0)
    )

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name=benchmark_name, objective=objective, contribution_specification=specification
    )


def cns_mpo(max_logP=5.0) -> GoalDirectedBenchmark:
    specification = uniform_specification(1, 10, 100)
    return GoalDirectedBenchmark(
        name="CNS MPO",
        objective=CNS_MPO_ScoringFunction(max_logP=max_logP),
        contribution_specification=specification,
    )


def qed_benchmark() -> GoalDirectedBenchmark:
    specification = uniform_specification(1, 10, 100)
    return GoalDirectedBenchmark(
        name="QED",
        objective=RdkitScoringFunction(descriptor=qed),
        contribution_specification=specification,
    )


def median_camphor_menthol(mean_cls=GeometricMeanScoringFunction) -> GoalDirectedBenchmark:
    t_camphor = TanimotoScoringFunction("CC1(C)C2CCC1(C)C(=O)C2", fp_type="ECFP4")
    t_menthol = TanimotoScoringFunction("CC(C)C1CCC(C)CC1O", fp_type="ECFP4")
    median = mean_cls([t_menthol, t_camphor])

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name="Median molecules 1", objective=median, contribution_specification=specification
    )


def perindopril_rings() -> GoalDirectedBenchmark:
    # perindopril with two aromatic rings
    perindopril = TanimotoScoringFunction(
        "O=C(OCC)C(NC(C(=O)N1C(C(=O)O)CC2CCCCC12)C)CCC", fp_type="ECFP4"
    )
    arom_rings = RdkitScoringFunction(
        descriptor=num_aromatic_rings, score_modifier=GaussianModifier(mu=2, sigma=0.5)
    )

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name="Perindopril MPO",
        objective=GeometricMeanScoringFunction([perindopril, arom_rings]),
        contribution_specification=specification,
    )


def amlodipine_rings() -> GoalDirectedBenchmark:
    # amlodipine with 3 rings
    amlodipine = TanimotoScoringFunction(
        r"Clc1ccccc1C2C(=C(/N/C(=C2/C(=O)OCC)COCCN)C)\C(=O)OC", fp_type="ECFP4"
    )
    rings = RdkitScoringFunction(
        descriptor=num_rings, score_modifier=GaussianModifier(mu=3, sigma=0.5)
    )

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name="Amlodipine MPO",
        objective=GeometricMeanScoringFunction([amlodipine, rings]),
        contribution_specification=specification,
    )


def sitagliptin_replacement() -> GoalDirectedBenchmark:
    # Find a molecule dissimilar to sitagliptin, but with the same properties
    smiles = "Fc1cc(c(F)cc1F)CC(N)CC(=O)N3Cc2nnc(n2CC3)C(F)(F)F"
    sitagliptin = Chem.MolFromSmiles(smiles)
    target_logp = logP(sitagliptin)
    target_tpsa = tpsa(sitagliptin)

    similarity = TanimotoScoringFunction(
        smiles, fp_type="ECFP4", score_modifier=GaussianModifier(mu=0, sigma=0.1)
    )
    lp = RdkitScoringFunction(
        descriptor=logP, score_modifier=GaussianModifier(mu=target_logp, sigma=0.2)
    )
    tp = RdkitScoringFunction(
        descriptor=tpsa, score_modifier=GaussianModifier(mu=target_tpsa, sigma=5)
    )
    isomers = IsomerScoringFunction("C16H15F6N5O")

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name="Sitagliptin MPO",
        objective=GeometricMeanScoringFunction([similarity, lp, tp, isomers]),
        contribution_specification=specification,
    )


def zaleplon_with_other_formula() -> GoalDirectedBenchmark:
    # zaleplon_with_other_formula with other formula
    zaleplon = TanimotoScoringFunction(
        "O=C(C)N(CC)C1=CC=CC(C2=CC=NC3=C(C=NN23)C#N)=C1", fp_type="ECFP4"
    )
    formula = IsomerScoringFunction("C19H17N3O2")

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name="Zaleplon MPO",
        objective=GeometricMeanScoringFunction([zaleplon, formula]),
        contribution_specification=specification,
    )


def smarts_with_other_target(smarts: str, other_molecule: str) -> ScoringFunction:
    smarts_scoring_function = SMARTSScoringFunction(target=smarts)
    other_mol = Chem.MolFromSmiles(other_molecule)
    target_logp = logP(other_mol)
    target_tpsa = tpsa(other_mol)
    target_bertz = bertz(other_mol)

    lp = RdkitScoringFunction(
        descriptor=logP, score_modifier=GaussianModifier(mu=target_logp, sigma=0.2)
    )
    tp = RdkitScoringFunction(
        descriptor=tpsa, score_modifier=GaussianModifier(mu=target_tpsa, sigma=5)
    )
    bz = RdkitScoringFunction(
        descriptor=bertz, score_modifier=GaussianModifier(mu=target_bertz, sigma=30)
    )

    return GeometricMeanScoringFunction([smarts_scoring_function, lp, tp, bz])


def valsartan_smarts() -> GoalDirectedBenchmark:
    # valsartan smarts with sitagliptin properties
    sitagliptin_smiles = "NC(CC(=O)N1CCn2c(nnc2C(F)(F)F)C1)Cc1cc(F)c(F)cc1F"
    valsartan_smarts = "CN(C=O)Cc1ccc(c2ccccc2)cc1"
    specification = uniform_specification(1, 10, 100)
    return GoalDirectedBenchmark(
        name="Valsartan SMARTS",
        objective=smarts_with_other_target(valsartan_smarts, sitagliptin_smiles),
        contribution_specification=specification,
    )


def median_tadalafil_sildenafil() -> GoalDirectedBenchmark:
    # median mol between tadalafil and sildenafil
    m1 = TanimotoScoringFunction(
        "O=C1N(CC(N2C1CC3=C(C2C4=CC5=C(OCO5)C=C4)NC6=C3C=CC=C6)=O)C", fp_type="ECFP6"
    )
    m2 = TanimotoScoringFunction(
        "CCCC1=NN(C2=C1N=C(NC2=O)C3=C(C=CC(=C3)S(=O)(=O)N4CCN(CC4)C)OCC)C", fp_type="ECFP6"
    )
    median = GeometricMeanScoringFunction([m1, m2])

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name="Median molecules 2", objective=median, contribution_specification=specification
    )


def pioglitazone_mpo() -> GoalDirectedBenchmark:
    # pioglitazone with same mw but less rotatable bonds
    smiles = "O=C1NC(=O)SC1Cc3ccc(OCCc2ncc(cc2)CC)cc3"
    pioglitazone = Chem.MolFromSmiles(smiles)
    target_molw = mol_weight(pioglitazone)

    similarity = TanimotoScoringFunction(
        smiles, fp_type="ECFP4", score_modifier=GaussianModifier(mu=0, sigma=0.1)
    )
    mw = RdkitScoringFunction(
        descriptor=mol_weight, score_modifier=GaussianModifier(mu=target_molw, sigma=10)
    )
    rb = RdkitScoringFunction(
        descriptor=num_rotatable_bonds, score_modifier=GaussianModifier(mu=2, sigma=0.5)
    )

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name="Pioglitazone MPO",
        objective=GeometricMeanScoringFunction([similarity, mw, rb]),
        contribution_specification=specification,
    )


def decoration_hop() -> GoalDirectedBenchmark:
    smiles = "CCCOc1cc2ncnc(Nc3ccc4ncsc4c3)c2cc1S(=O)(=O)C(C)(C)C"

    pharmacophor_sim = TanimotoScoringFunction(
        smiles, fp_type="PHCO", score_modifier=ClippedScoreModifier(upper_x=0.85)
    )
    # change deco
    deco1 = SMARTSScoringFunction("CS([#6])(=O)=O", inverse=True)
    deco2 = SMARTSScoringFunction("[#7]-c1ccc2ncsc2c1", inverse=True)

    # keep scaffold
    scaffold = SMARTSScoringFunction(
        "[#7]-c1n[c;h1]nc2[c;h1]c(-[#8])[c;h0][c;h1]c12", inverse=False
    )

    deco_hop1_fn = ArithmeticMeanScoringFunction([pharmacophor_sim, deco1, deco2, scaffold])

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name="Deco Hop", objective=deco_hop1_fn, contribution_specification=specification
    )


def scaffold_hop() -> GoalDirectedBenchmark:
    """
    Keep the decoration, and similarity to start point, but change the scaffold.
    """

    smiles = "CCCOc1cc2ncnc(Nc3ccc4ncsc4c3)c2cc1S(=O)(=O)C(C)(C)C"

    pharmacophor_sim = TanimotoScoringFunction(
        smiles, fp_type="PHCO", score_modifier=ClippedScoreModifier(upper_x=0.75)
    )

    deco = SMARTSScoringFunction(
        "[#6]-[#6]-[#6]-[#8]-[#6]~[#6]~[#6]~[#6]~[#6]-[#7]-c1ccc2ncsc2c1", inverse=False
    )

    # anti scaffold
    scaffold = SMARTSScoringFunction("[#7]-c1n[c;h1]nc2[c;h1]c(-[#8])[c;h0][c;h1]c12", inverse=True)

    scaffold_hop_obj = ArithmeticMeanScoringFunction([pharmacophor_sim, deco, scaffold])

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name="Scaffold Hop", objective=scaffold_hop_obj, contribution_specification=specification
    )


def ranolazine_mpo() -> GoalDirectedBenchmark:
    """
    Make start_pop_ranolazine more polar and add a fluorine
    """
    ranolazine = "COc1ccccc1OCC(O)CN2CCN(CC(=O)Nc3c(C)cccc3C)CC2"

    modifier = ClippedScoreModifier(upper_x=0.7)
    similar_to_ranolazine = TanimotoScoringFunction(
        ranolazine, fp_type="AP", score_modifier=modifier
    )

    logP_under_4 = RdkitScoringFunction(
        descriptor=logP, score_modifier=MaxGaussianModifier(mu=7, sigma=1)
    )

    tpsa_f = RdkitScoringFunction(
        descriptor=tpsa, score_modifier=MaxGaussianModifier(mu=95, sigma=20)
    )

    fluorine = RdkitScoringFunction(
        descriptor=AtomCounter("F"), score_modifier=GaussianModifier(mu=1, sigma=1.0)
    )

    optimize_ranolazine = GeometricMeanScoringFunction(
        [similar_to_ranolazine, logP_under_4, fluorine, tpsa_f]
    )

    specification = uniform_specification(1, 10, 100)

    return GoalDirectedBenchmark(
        name="Ranolazine MPO",
        objective=optimize_ranolazine,
        contribution_specification=specification,
        starting_population=[ranolazine],
    )
