import json


def json_top_subset(json_str, atom_idxs):
    """Given a JSON topology and atom indices from that topology returns
    another JSON topology which is a subset of the first, preserving
    the topology between remaining atoms. The atoms will be ordered in
    the order in which the indices are given.

    Parameters
    ----------

    json_str : str
        A string of valid JSON in the format of JSON used in WepyHDF5
        and mdtraj HDF5 format.

    atom_idxs : list of int
        The atoms for which you want to make a subset of.

    Returns
    -------

    subset_json_str : str
        JSON string of the subset of atoms. Ordering preserved.

    """

    # cast so we can use the list index method
    atom_idxs = list(atom_idxs)

    # do checks on the atom idxs

    # no duplicates
    assert len(set(atom_idxs)) == len(atom_idxs), "duplicate atom indices"

    top = json.loads(json_str)

    # the dictionaries for each thing indexed by their old index
    atom_data_ds = {}
    residue_data_ds = {}

    # mapping of old atom indices to the residue data they belong to
    atom_res_idxs = {}
    res_chain_idxs = {}

    # go through and collect data on the atoms and convert indices to
    # the new ones
    for chain in top['chains']:

        for residue in chain['residues']:
            res_chain_idxs[residue['index']] = chain['index']

            # create the data dict for this residue
            residue_data_ds[residue['index']] = residue



            for atom in residue['atoms']:
                atom_res_idxs[atom['index']] = residue['index']

                # if the current atom's index is in the selection
                if atom['index'] in atom_idxs:

                    # we add this to the mapping by getting the index
                    # of the atom in subset
                    new_idx = atom_idxs.index(atom['index'])

                    # save the atom attributes
                    atom_data_ds[atom['index']] = atom

    # initialize the data structure for the topology subset
    top_subset = {'chains' : [],
                  'bonds' : []}

    residue_idx_map = {}
    chain_idx_map = {}

    old_to_new_atoms = {}

    # initialize the new indexing of the chains and residues
    new_res_idx_counter = 0
    new_chain_idx_counter = 0
    # now in the new order go through and create the topology
    for new_atom_idx, old_atom_idx in enumerate(atom_idxs):

        old_to_new_atoms[old_atom_idx] = new_atom_idx

        atom_data = atom_data_ds[old_atom_idx]

        # get the old residue index
        old_res_idx = atom_res_idxs[old_atom_idx]

        # since the datastrucutre is hierarchical starting with the
        # chains and residues we work our way back up craeting these
        # if neceessary for this atom, once this is taken care of we
        # will add the atom to the data structure
        if old_res_idx not in residue_idx_map:
            residue_idx_map[old_res_idx] = new_res_idx_counter
            new_res_idx_counter += 1

            # do the same but for the chain
            old_chain_idx = res_chain_idxs[old_res_idx]

            # make it if necessary
            if old_chain_idx not in chain_idx_map:
                chain_idx_map[old_chain_idx] = new_chain_idx_counter
                new_chain_idx_counter += 1

                # and add the chain to the topology
                new_chain_idx = chain_idx_map[old_chain_idx]
                top_subset['chains'].append({'index' : new_chain_idx,
                                             'residues' : []})

            # add the new index to the dats dict for the residue
            res_data = residue_data_ds[old_res_idx]
            res_data['index'] = residue_idx_map[old_res_idx]
            # clear the atoms
            res_data['atoms'] = []

            # add the reside to the chain idx
            new_chain_idx = chain_idx_map[old_chain_idx]
            top_subset['chains'][new_chain_idx]['residues'].append(res_data)

        # now that (if) we have made the necessary chains and residues
        # for this atom we replace the atom index with the new index
        # and add it to the residue
        new_res_idx = residue_idx_map[old_res_idx]
        new_chain_idx = chain_idx_map[res_chain_idxs[old_res_idx]]

        atom_data['index'] = new_atom_idx

        top_subset['chains'][new_chain_idx]['residues'][new_res_idx]['atoms'].append(atom_data)

    # then translate the atom indices in the bonds
    new_bonds = []
    for bond_atom_idxs in top['bonds']:

        if all([True if a_idx in old_to_new_atoms else False
                for a_idx in bond_atom_idxs]):
            new_bond_atom_idxs = [old_to_new_atoms[a_idx] for a_idx in bond_atom_idxs
                                  if a_idx in old_to_new_atoms]
            new_bonds.append(new_bond_atom_idxs)

    top_subset['bonds'] = new_bonds


    return json.dumps(top_subset)