from __future__ import absolute_import, division, print_function

import os

from six.moves import cStringIO as StringIO

from libtbx import group_args
from libtbx.program_template import ProgramTemplate
from libtbx.utils import Sorry

# =============================================================================
class Program(ProgramTemplate):

  description = '''
Program for preparing model and data files for depostion into the Protein
Data Bank.

Minimum required data:
  Model file
  Sequence file

The sequence file should have a sequence for each chain in the model file.

Currently, this program only combines the model and sequence into a single
mmCIF file. If the input model is in mmCIF format, extra loops in the
file that are not modified will be kept.

Adding sequences will populate the entity_poly, entity_poly_seq,
struct_ref, and struct_ref_seq loops. The struct_ref and struct_ref_seq
loops are works in progress and will require external information. Also,
the canonical sequence containing all residues for the structure should
be provided. If there are non-standard residues in the model, they will
be automatically changed to the 3 letter residue name surrounded by
parentheses.

More functionality is planned.
'''

  datatypes = ['model', 'phil', 'restraint', 'sequence']

  master_phil_str = '''
mmtbx.validation.sequence.sequence_alignment {
  include scope mmtbx.validation.sequence.master_phil
}
custom_residues = None
  .type = strings
  .help = Space-separated list of three letter codes to be included in \
    the entity_poly.pdbx_one_letter_code mmCIF loop \
    (e.g. custom_residues='SUI PYL')
keep_original_loops = True
  .type = bool
  .help = Preserves mmCIF data from the input model file (if available) that \
    is not overwritten by other input
align_columns = False
  .type = bool
  .help = When set to True, the columns are aligned. This will take longer \
    because the column widths need to be deteremined before outputting.
include scope mmtbx.monomer_library.pdb_interpretation.grand_master_phil_str
include scope mmtbx.geometry_restraints.torsion_restraints.reference_model.reference_model_str
include scope mmtbx.geometry_restraints.external.external_energy_params_str
output {
  suffix = '.deposit'
    .type = str
    .help = Suffix string added to automatically generated output filenames
}

# PHIL parameters for current GUI
include scope libtbx.phil.interface.tracking_params
gui
  .help = "GUI-specific parameter required for output directory"
{
  output_dir = None
  .type = path
  .style = output_dir
}
'''
  # ---------------------------------------------------------------------------
  def custom_init(self):
    self.output_file = None

  # ---------------------------------------------------------------------------
  def validate(self):
    self.data_manager.has_models(expected_n=1, exact_count=True, raise_sorry=True)
    self.data_manager.has_sequences(raise_sorry=True)
    model = self.data_manager.get_model()
    if not model.input_model_format_cif():
      raise Sorry('Your input model must already be in mmCIF.')

  # ---------------------------------------------------------------------------
  def run(self):
    print('Using model: {model_name}'.format(
      model_name=self.data_manager.get_default_model_name()))
    print('Using sequence(s): {sequence_names}'.format(
      sequence_names=', '.join(self.data_manager.get_sequence_names())))

    # get model
    model = self.data_manager.get_model()
    model.set_log(self.logger)

    # add sequences
    sequences = list()
    for sequence_name in self.data_manager.get_sequence_names():
      sequences.extend(self.data_manager.get_sequence(sequence_name))

    # match input sequences with model chains
    print(file=self.logger)
    print('Matching sequences to chains', file=self.logger)
    print('----------------------------', file=self.logger)
    # No error trapping
    model.set_sequences(
      sequences,
      custom_residues=self.params.custom_residues,
      similarity_matrix=self.params.mmtbx.validation.sequence.sequence_alignment.similarity_matrix,
      min_allowable_identity=self.params.mmtbx.validation.sequence.sequence_alignment.min_allowable_identity)

    # When the input is already in mmCIF, just add sequence information
    found_cif_block = False
    if hasattr(model._model_input, 'cif_model'):
      for cif_block in model._model_input.cif_model.values():
        if '_atom_site' in cif_block:
          cif_block.update(model._sequence_validation.sequence_as_cif_block())
          out = StringIO()
          model._model_input.cif_model.show(
            out=out, align_columns=self.params.align_columns)
          self.cif_model = out.getvalue()
          found_cif_block = True
          break
    # otherwise, generate mmCIF
    if not found_cif_block:
      model.get_restraints_manager()
      self.cif_model = model.model_as_mmcif(
        align_columns=self.params.align_columns,
        keep_original_loops=self.params.keep_original_loops)

    # show sequence alignment
    model._sequence_validation.show(out=self.logger)
    print(file=self.logger)

    # write output file to current directory
    if self.params.output.prefix is None:
      self.params.output.prefix = os.path.split(os.path.splitext(
        self.data_manager.get_default_model_name())[0])[1]
    self.data_manager.set_default_output_filename(
      self.get_default_output_filename())
    self.output_file = self.data_manager.get_default_output_model_filename()
    print('Writing mmCIF', file=self.logger)
    print('-------------', file=self.logger)
    print ('  Output file = %s' % self.output_file, file=self.logger)
    self.data_manager.write_model_file(self.cif_model)

  # ---------------------------------------------------------------------------
  def get_results(self):
    return group_args(output_file=self.output_file,
                      cif_model=self.cif_model)

# =============================================================================
# end
