#!/usr/bin/env python
'''
build American Mineralogist Crystal Structure Databse (amcsd)
'''

import os
import requests
import numpy as np
from itertools import groupby
import larch
from .xrd_fitting import peaklocater
from .xrd_cif import create_xrdcif, SPACEGROUPS
from .xrd_tools import lambda_from_E

import json
from larch.utils.jsonutils import encode4js, decode4js

from sqlalchemy import (create_engine, MetaData, Table, Column, Integer,
                        String, Unicode, PrimaryKeyConstraint,
                        ForeignKeyConstraint, ForeignKey, Numeric, func,
                        and_, or_, not_, tuple_)

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.pool import SingletonThreadPool

SYMMETRIES = ['triclinic', 'monoclinic', 'orthorhombic', 'tetragonal',
              'trigonal', 'hexagonal', 'cubic']

ELEMENTS = [['1',  'Hydrogen',  'H'], ['2',  'Helium',  'He'], ['3',  'Lithium',  'Li'],
            ['4',  'Beryllium',  'Be'], ['5',  'Boron',  'B'], ['6',  'Carbon',  'C'],
            ['7',  'Nitrogen',  'N'], ['8',  'Oxygen',  'O'], ['9',  'Fluorine',  'F'],
            ['10',  'Neon',  'Ne'], ['11',  'Sodium',  'Na'], ['12',  'Magnesium',  'Mg'],
            ['13',  'Aluminum',  'Al'], ['14',  'Silicon',  'Si'], ['15',  'Phosphorus',  'P'],
            ['16',  'Sulfur',  'S'], ['17',  'Chlorine',  'Cl'], ['18',  'Argon',  'Ar'],
            ['19',  'Potassium',  'K'], ['20',  'Calcium',  'Ca'], ['21',  'Scandium',  'Sc'],
            ['22',  'Titanium',  'Ti'], ['23',  'Vanadium',  'V'], ['24',  'Chromium',  'Cr'],
            ['25',  'Manganese',  'Mn'], ['26',  'Iron',  'Fe'], ['27',  'Cobalt',  'Co'],
            ['28',  'Nickel',  'Ni'], ['29',  'Copper',  'Cu'], ['30',  'Zinc',  'Zn'],
            ['31',  'Gallium',  'Ga'], ['32',  'Germanium',  'Ge'], ['33',  'Arsenic',  'As'],
            ['34',  'Selenium',  'Se'], ['35',  'Bromine',  'Br'], ['36',  'Krypton',  'Kr'],
            ['37',  'Rubidium',  'Rb'], ['38',  'Strontium',  'Sr'], ['39',  'Yttrium',  'Y'],
            ['40',  'Zirconium',  'Zr'], ['41',  'Niobium',  'Nb'], ['42',  'Molybdenum',  'Mo'],
            ['43',  'Technetium',  'Tc'], ['44',  'Ruthenium',  'Ru'], ['45',  'Rhodium',  'Rh'],
            ['46',  'Palladium',  'Pd'], ['47',  'Silver',  'Ag'], ['48',  'Cadmium',  'Cd'],
            ['49',  'Indium',  'In'], ['50',  'Tin',  'Sn'], ['51',  'Antimony',  'Sb'],
            ['52',  'Tellurium',  'Te'], ['53',  'Iodine',  'I'], ['54',  'Xenon',  'Xe'],
            ['55',  'Cesium',  'Cs'], ['56',  'Barium',  'Ba'], ['57',  'Lanthanum',  'La'],
            ['58',  'Cerium',  'Ce'], ['59',  'Praseodymium',  'Pr'], ['60',  'Neodymium',  'Nd'],
            ['61',  'Promethium',  'Pm'], ['62',  'Samarium',  'Sm'], ['63',  'Europium',  'Eu'],
            ['64',  'Gadolinium',  'Gd'], ['65',  'Terbium',  'Tb'], ['66',  'Dysprosium',  'Dy'],
            ['67',  'Holmium',  'Ho'], ['68',  'Erbium',  'Er'], ['69',  'Thulium',  'Tm'],
            ['70',  'Ytterbium',  'Yb'], ['71',  'Lutetium',  'Lu'], ['72',  'Hafnium',  'Hf'],
            ['73',  'Tantalum',  'Ta'], ['74',  'Tungsten',  'W'], ['75',  'Rhenium',  'Re'],
            ['76',  'Osmium',  'Os'], ['77',  'Iridium',  'Ir'], ['78',  'Platinum',  'Pt'],
            ['79',  'Gold',  'Au'], ['80',  'Mercury',  'Hg'], ['81',  'Thallium',  'Tl'],
            ['82',  'Lead',  'Pb'], ['83',  'Bismuth',  'Bi'], ['84',  'Polonium',  'Po'],
            ['85',  'Astatine',  'At'], ['86',  'Radon',  'Rn'], ['87',  'Francium',  'Fr'],
            ['88',  'Radium',  'Ra'], ['89',  'Actinium',  'Ac'], ['90',  'Thorium',  'Th'],
            ['91',  'Protactinium',  'Pa'], ['92',  'Uranium',  'U'], ['93',  'Neptunium',  'Np'],
            ['94',  'Plutonium',  'Pu'], ['95',  'Americium',  'Am'], ['96',  'Curium',  'Cm'],
            ['97',  'Berkelium',  'Bk'], ['98',  'Californium',  'Cf'], ['99',  'Einsteinium',  'Es'],
            ['100',  'Fermium',  'Fm'], ['101',  'Mendelevium',  'Md'], ['102',  'Nobelium',  'No'],
            ['103',  'Lawrencium',  'Lr'], ['104',  'Rutherfordium',  'Rf'], ['105',  'Dubnium',  'Db'],
            ['106',  'Seaborgium',  'Sg'], ['107',  'Bohrium',  'Bh'], ['108',  'Hassium',  'Hs'],
            ['109',  'Meitnerium',  'Mt'], ['110',  'Darmstadtium',  'Ds'], ['111',  'Roentgenium',  'Rg'],
            ['112',  'Ununbium',  'Uub'], ['113',  'Ununtrium',  'Uut'], ['114',  'Ununquadium',  'Uuq'],
            ['115',  'Ununpentium',  'Uup'], ['116',  'Ununhexium',  'Uuh'], ['117',  'Ununseptium',  'Uus'],
            ['118',  'Ununoctium',  'Uuo']]

CATEGORIES = ['soil',
              'salt',
              'clay']

QMIN = 0.2
QMAX = 10.0
QSTEP = 0.01
QAXIS = np.arange(QMIN, QMAX+QSTEP, QSTEP)

ENERGY = 19000 ## units eV
_cifdb = None

def get_cifdb(dbname='amcsd_cif0.db', _larch=None):
    global _cifdb
    if _cifdb is None:
        _cifdb = cifDB(dbname=dbname)
    if _larch is not None:
        symname = '_xray._cifdb'
        if not _larch.symtable.has_symbol(symname):
            _larch.symtable.set_symbol(symname, _cifdb)
    return _cifdb

def make_engine(dbname):
    return create_engine('sqlite:///%s' % (dbname),
                         poolclass=SingletonThreadPool)

def iscifDB(dbname):
    '''
    test if a file is a valid scan database:
    must be a sqlite db file, with tables named according to _tables
    '''
    _tables = ('ciftbl',
               'elemtbl',
               'nametbl',
               #'formtbl',
               'spgptbl',
               'symtbl',
               'authtbl',
               'qtbl',
               'cattbl',
               'symref',
               #'compref',
               #'qref',
               'authref',
               'catref')
    result = False
    try:
        engine = make_engine(dbname)
        meta = MetaData()
        meta.reflect(bind=engine)
        result = all([t in meta.tables for t in _tables])
    except:
        pass
    return result


class cifDB(object):
    '''
    interface to the American Mineralogist Crystal Structure Database
    '''
    def __init__(self, dbname=None, read_only=True,verbose=False):

        ## This needs to be modified for creating new if does not exist.
        self.version = '0.0.2'
        self.dbname = dbname
        if verbose:
            print('\n\n================ %s ================\n' % self.dbname)
        if not os.path.exists(self.dbname):
            parent, child = os.path.split(__file__)
            self.dbname = os.path.join(parent, self.dbname)
            if not os.path.exists(self.dbname):
                print("File '%s' not found; building a new database!" % self.dbname)
                self.create_cifdb(name=self.dbname)
            else:
                if not iscifDB(self.dbname):
                    raise ValueError("'%s' is not a valid cif database file!" % self.dbname)

        self.dbname = self.dbname
        self.engine = make_engine(self.dbname)
        self.conn = self.engine.connect()

        kwargs = {}
        if read_only:
            kwargs = {'autoflush': True, 'autocommit':False}
            def readonly_flush(*args, **kwargs):
                return
            self.session = sessionmaker(bind=self.engine, **kwargs)()
            self.session.flush = readonly_flush
        else:
            self.session = sessionmaker(bind=self.engine, **kwargs)()

        self.metadata =  MetaData()
        self.metadata.reflect(bind=self.engine)
        tables = self.tables = self.metadata.tables

        ## Load tables
        self.elemtbl = Table('elemtbl', self.metadata)
        self.nametbl = Table('nametbl', self.metadata)
        self.formtbl = Table('formtbl', self.metadata)
        self.spgptbl = Table('spgptbl', self.metadata)
        self.symtbl  = Table('symtbl',  self.metadata)
        self.authtbl = Table('authtbl', self.metadata)
        self.qtbl    = Table('qtbl',    self.metadata)
        self.cattbl  = Table('cattbl',  self.metadata)

        self.symref  = Table('symref',  self.metadata)
        self.compref = Table('compref', self.metadata)
        self.qref    = Table('qref',    self.metadata)
        self.authref = Table('authref', self.metadata)
        self.catref  = Table('catref',  self.metadata)

        self.ciftbl  = Table('ciftbl', self.metadata)

        self.axis = np.array([float(q[0]) for q in self.query(self.qtbl.c.q).all()])


    def query(self, *args, **kws):
        "generic query"
        return self.session.query(*args, **kws)

    def close(self):
        "close session"
        self.session.flush()
        self.session.close()

    def create_cifdb(self,name=None,verbose=False):

        if name is None:
            self.dbname = 'amcsd_cif0.db'
            counter = 0
            while os.path.exists(self.dbname % counter):
                counter += 1
            self.dbname = self.dbname % counter
        else:
            self.dbname = name

        self.open_database()

        ###################################################
        ## Look up tables
        elemtbl = Table('elemtbl', self.metadata,
                  Column('z', Integer, primary_key=True),
                  Column('element_name', String(40), unique=True, nullable=True),
                  Column('element_symbol', String(2), unique=True, nullable=False)
                  )
        nametbl = Table('nametbl', self.metadata,
                  Column('mineral_id', Integer, primary_key=True),
                  Column('mineral_name', String(30), unique=True, nullable=True)
                  )
        formtbl = Table('formtbl', self.metadata,
                  Column('formula_id', Integer, primary_key=True),
                  Column('formula_name', String(30), unique=True, nullable=True)
                  )
        spgptbl = Table('spgptbl', self.metadata,
                  Column('iuc_id', Integer),
                  Column('hm_notation', String(16), unique=True, nullable=True),
                  PrimaryKeyConstraint('iuc_id', 'hm_notation')
                  )
        symtbl  = Table('symtbl', self.metadata,
                  Column('symmetry_id', Integer, primary_key=True),
                  Column('symmetry_name', String(16), unique=True, nullable=True)
                  )
        authtbl = Table('authtbl', self.metadata,
                  Column('author_id', Integer, primary_key=True),
                  Column('author_name', String(40), unique=True, nullable=True)
                  )
        qtbl    = Table('qtbl', self.metadata,
                  Column('q_id', Integer, primary_key=True),
                  #Column('q', Float()) ## how to make this work? mkak 2017.02.14
                  Column('q', String())
                  )
        cattbl  = Table('cattbl', self.metadata,
                  Column('category_id', Integer, primary_key=True),
                  Column('category_name', String(16), unique=True, nullable=True)
                  )
        ###################################################
        ## Cross-reference tables
        symref  = Table('symref', self.metadata,
                  Column('iuc_id', None, ForeignKey('spgptbl.iuc_id')),
                  Column('symmetry_id', None, ForeignKey('symtbl.symmetry_id')),
                  PrimaryKeyConstraint('iuc_id', 'symmetry_id')
                  )
        compref = Table('compref', self.metadata,
                  Column('z', None, ForeignKey('elemtbl.z')),
                  Column('amcsd_id', None, ForeignKey('ciftbl.amcsd_id')),
                  PrimaryKeyConstraint('z', 'amcsd_id')
                  )
        qref    = Table('qref', self.metadata,
                  Column('q_id', None, ForeignKey('qtbl.q_id')),
                  Column('amcsd_id', None, ForeignKey('ciftbl.amcsd_id')),
                  PrimaryKeyConstraint('q_id', 'amcsd_id')
                  )
        authref = Table('authref', self.metadata,
                  Column('author_id', None, ForeignKey('authtbl.author_id')),
                  Column('amcsd_id', None, ForeignKey('ciftbl.amcsd_id')),
                  PrimaryKeyConstraint('author_id', 'amcsd_id')
                  )
        catref  = Table('catref', self.metadata,
                  Column('category_id', None, ForeignKey('cattbl.category_id')),
                  Column('amcsd_id', None, ForeignKey('ciftbl.amcsd_id')),
                  PrimaryKeyConstraint('category_id', 'amcsd_id')
                  )
        ###################################################
        ## Main table
        ciftbl  = Table('ciftbl', self.metadata,
                  Column('amcsd_id', Integer, primary_key=True),
                  Column('mineral_id', Integer),
                  Column('formula_id', Integer),
                  Column('iuc_id', ForeignKey('spgptbl.iuc_id')),
                  Column('a', String(5)),
                  Column('b', String(5)),
                  Column('c', String(5)),
                  Column('alpha', String(5)),
                  Column('beta', String(5)),
                  Column('gamma', String(5)),
                  Column('cif', String(25)), ## , nullable=True
                  Column('zstr',String(25)),
                  Column('qstr',String(25)),
                  Column('url',String(25))
                  )
        ###################################################
        ## Add all to file
        self.metadata.create_all() ## if not exists function (callable when exists)

        ###################################################
        ## Define 'add/insert' functions for each table
        def_elem = elemtbl.insert()
        def_name = nametbl.insert()
        def_form = formtbl.insert()
        def_spgp = spgptbl.insert()
        def_sym  = symtbl.insert()
        def_auth = authtbl.insert()
        def_q    = qtbl.insert()
        def_cat  = cattbl.insert()

        add_sym  = symref.insert()
        add_comp = compref.insert()
        add_q    = qref.insert()
        add_auth = authref.insert()
        add_cat  = catref.insert()

        new_cif  = ciftbl.insert()


        ###################################################
        ## Populate the fixed tables of the database

        ## Adds all elements into database
        for element in ELEMENTS:
            z, name, symbol = element
            def_elem.execute(z=int(z), element_name=name, element_symbol=symbol)

        ## Adds all crystal symmetries
        for symmetry_id,symmetry in enumerate(SYMMETRIES):
            def_sym.execute(symmetry_name=symmetry.strip())
            if symmetry.strip() == 'triclinic':      ## triclinic    :   1 -   2
                for iuc_id in range(1,2+1):
                    add_sym.execute(iuc_id=iuc_id,symmetry_id=(symmetry_id+1))
            elif symmetry.strip() == 'monoclinic':   ## monoclinic   :   3 -  15
                for iuc_id in range(3,15+1):
                    add_sym.execute(iuc_id=iuc_id,symmetry_id=(symmetry_id+1))
            elif symmetry.strip() == 'orthorhombic': ## orthorhombic :  16 -  74
                for iuc_id in range(16,74+1):
                    add_sym.execute(iuc_id=iuc_id,symmetry_id=(symmetry_id+1))
            elif symmetry.strip() == 'tetragonal':   ## tetragonal   :  75 - 142
                for iuc_id in range(75,142+1):
                    add_sym.execute(iuc_id=iuc_id,symmetry_id=(symmetry_id+1))
            elif symmetry.strip() == 'trigonal':     ## trigonal     : 143 - 167
                for iuc_id in range(143,167+1):
                    add_sym.execute(iuc_id=iuc_id,symmetry_id=(symmetry_id+1))
            elif symmetry.strip() == 'hexagonal':    ## hexagonal    : 168 - 194
                for iuc_id in range(168,194+1):
                    add_sym.execute(iuc_id=iuc_id,symmetry_id=(symmetry_id+1))
            elif symmetry.strip() == 'cubic':        ## cubic        : 195 - 230
                for iuc_id in range(195,230+1):
                    add_sym.execute(iuc_id=iuc_id,symmetry_id=(symmetry_id+1))

        for cat in CATEGORIES:
            def_cat.execute(category_name=cat)

        ## Adds qrange
        for q in QAXIS:
            def_q.execute(q='%0.2f' % q)

        ## Adds all space groups
        for spgrp_no in SPACEGROUPS.keys():
            for spgrp_name in SPACEGROUPS[spgrp_no]:
                try:
                    def_spgp.execute(iuc_id=spgrp_no, hm_notation=spgrp_name)
                except:
                    if verbose:
                        print('Duplicate: %s %s' % (spgrp_no,spgrp_name))
                pass


    def __add_space_groups(self):
        ## Add missing space groups
        for spgrp_no in SPACEGROUPS.keys():
            for spgrp_name in SPACEGROUPS[spgrp_no]:
                match = False
                search_spgrp = self.spgptbl.select(self.spgptbl.c.hm_notation == spgrp_name)
                for row in search_spgrp.execute():
                    match = True
                if match is False:
                    print('Adding: %s %s' % (spgrp_no,spgrp_name))
                    self.spgptbl.insert().execute(iuc_id=spgrp_no,hm_notation=spgrp_name)

    def add_ciffile(self, ciffile, verbose=True, url=False, ijklm=1, file=None):
        '''
        ## Adds ciffile into database
        When reading in new CIF:
        i.   put entire cif into field
        ii.  read _database_code_amcsd; write 'amcsd_id' to 'cif data'
        iii. read _chemical_name_mineral; find/add in' minerallist'; write
             'mineral_id' to 'cif data'
        iv.  read _symmetry_space_group_name_H-M - find in 'spacegroup'; write
             iuc_id to 'cif data'
        v.   read author name(s) - find/add in 'authorlist'; write 'author_id',
             'amcsd_id' to 'authref'
        vi.  read _chemical_formula_sum; write 'z' (atomic no.), 'amcsd_id'
             to 'compref'
        vii. calculate q - find each corresponding 'q_id' for all peaks; in write
            'q_id','amcsd_id' to 'qpeak'
        '''

        if url:
            cifstr = requests.get(ciffile).text
        else:
            with open(ciffile,'rb') as file:
                cifstr = str(file.read().decode('utf-8'))
        cif = create_xrdcif(text=cifstr)

        if cif.id_no is None:
            cif_no = 99999
            search_cif = self.query(self.ciftbl.c.amcsd_id).filter(self.ciftbl.c.amcsd_id == cif_no).all()
            cnt_lp = 0
            while len(search_cif) > 0:
                cif_no += 1
                cnt_lp += 1
                search_cif = self.query(self.ciftbl.c.amcsd_id).filter(self.ciftbl.c.amcsd_id == cif_no).all()
                if cnt_lp > 500: ##  safe guards against infinite loop
                    print(' *** too many loops to find unassigned AMCSD number.')
                    return
            print(' *** Assigning unnumbered CIF to AMCSD %i' % cif_no)
            cif.id_no = cif_no

        ## check for amcsd in file already
        ## Find amcsd_id in database
        self.ciftbl = Table('ciftbl', self.metadata)
        search_cif = self.ciftbl.select(self.ciftbl.c.amcsd_id == cif.id_no)
        for row in search_cif.execute():
            if verbose:
                if url:
                    print('AMCSD %i already exists in database.\n' % cif.id_no)
                else:
                    print('%s: AMCSD %i already exists in database %s.' %
                         (os.path.split(ciffile)[-1],cif.id_no,self.dbname))
            return

        ## Define q-array for each entry at given energy
        qhkl = cif.calc_q(wvlgth=lambda_from_E(ENERGY), q_min=QMIN, q_max=QMAX)
        qarr = self.create_q_array(qhkl)

        ###################################################
        def_name = self.nametbl.insert()
        def_form = self.formtbl.insert()
        def_spgp = self.spgptbl.insert()
        def_sym  = self.symtbl.insert()
        def_auth = self.authtbl.insert()
        def_q    = self.qtbl.insert()
        def_cat  = self.cattbl.insert()
        add_sym  = self.symref.insert()
        add_comp = self.compref.insert()
        add_q    = self.qref.insert()
        add_auth = self.authref.insert()
        add_cat  = self.catref.insert()
        new_cif  = self.ciftbl.insert()

        ## Find mineral_name
        match = False
        search_mineral = self.nametbl.select(self.nametbl.c.mineral_name == cif.label)
        for row in search_mineral.execute():
            mineral_id = row.mineral_id
            match = True
        if match is False:
            def_name.execute(mineral_name=cif.label)
            search_mineral = self.nametbl.select(self.nametbl.c.mineral_name == cif.label)
            for row in search_mineral.execute():
                mineral_id = row.mineral_id

        ## Find formula_name
        match = False
        search_formula = self.formtbl.select(self.formtbl.c.formula_name == cif.formula)
        for row in search_formula.execute():
            formula_id = row.formula_id
            match = True
        if match is False:
            def_form.execute(formula_name=cif.formula)
            search_formula = self.formtbl.select(self.formtbl.c.formula_name == cif.formula)
            for row in search_formula.execute():
                formula_id = row.formula_id

        ## Find composition (loop over all elements)
        z_list = []
        for element in set(cif.atom.label):
            search_elements = self.elemtbl.select(self.elemtbl.c.element_symbol == element)
            for row in search_elements.execute():
                z_list += [row.z]
        zarr = self.create_z_array(z_list)


        ## Save CIF entry into database
        new_cif.execute(amcsd_id=cif.id_no,
                        mineral_id=int(mineral_id),
                        formula_id=int(formula_id),
                        iuc_id=cif.symmetry.no,
                        a=str(cif.unitcell[0]),
                        b=str(cif.unitcell[1]),
                        c=str(cif.unitcell[2]),
                        alpha=str(cif.unitcell[3]),
                        beta=str(cif.unitcell[4]),
                        gamma=str(cif.unitcell[5]),
                        cif=cifstr,
                        zstr=json.dumps(zarr.tolist(),default=str),
                        qstr=json.dumps(qarr.tolist(),default=str),
                        url=str(ciffile))

        ## Build q cross-reference table
        for q in qhkl:
            search_q = self.qtbl.select(self.qtbl.c.q == '%0.2f' % (int(q * 100) / 100.))
            for row in search_q.execute():
                q_id = row.q_id

            try:
                add_q.execute(q_id=q_id,amcsd_id=cif.id_no)
            except:
                pass


        ## Build composition cross-reference table
        for element in set(cif.atom.label):
            search_elements = self.elemtbl.select(self.elemtbl.c.element_symbol == element)
            for row in search_elements.execute():
                z = row.z

            try:
                add_comp.execute(z=z, amcsd_id=cif.id_no)
            except:
                print('could not find element: %s (amcsd: %i)' % (element,cif.id_no))
                pass

        ## Find author_name
        for author_name in cif.publication.author:
            match = False
            search_author = self.authtbl.select(self.authtbl.c.author_name == author_name)
            for row in search_author.execute():
                author_id = row.author_id
                match = True
            if match is False:
                def_auth.execute(author_name=author_name)
                search_author = self.authtbl.select(self.authtbl.c.author_name == author_name)
                for row in search_author.execute():
                    author_id = row.author_id
                    match = True
            if match == True:
                add_auth.execute(author_id=author_id,
                                   amcsd_id=cif.id_no)

    #     ## not ready for defined categories
    #     cif_category.execute(category_id='none',
    #                          amcsd_id=cif.id_no)

        if url:
            self.amcsd_info(cif.id_no, no_qpeaks=np.sum(qarr))
        else:
            self.amcsd_info(cif.id_no, no_qpeaks=np.sum(qarr),ciffile=ciffile)

    def url_to_cif(self, url=None, verbose=False, savecif=False, addDB=True,
                   all=False, minval=None):

        maxi = 20573
        exceptions = [0,7271,10783,14748,15049,15050,15851,18368,
                      18449,18450,18451,18452,18453,20029]

        ## ALL CAUSE FAILURE IN CIFFILE FUNCTION:
        ##  7271 : author name doubled in cif
        ## 14748 : has label of amcsd code but no number (or anything) assigned
        ## 15049 : page number 'L24307 1' could not be parsed as number
        ## 15050 : page number 'L24307 1' could not be parsed as number
        ## 15851 : no first page number provided despite providing field label
        ## 18368 : non-numerical entries in B_iso fields
        ## 18449 : no first page number provided despite providing field label
        ## 18450 : no first page number provided despite providing field label
        ## 20029 : no volume number provided despite providing field label

        if url is None:
            url = 'http://rruff.geo.arizona.edu/AMS/download.php?id=%05d.cif&down=cif'

        ## Defines url range for searching and adding to cif database
        if all:
            iindex = range(99999) ## trolls whole database online
        elif minval is not None:
            iindex = np.arange(minval, 99999) ## starts at given min and counts up
        else:
            iindex = np.arange(13600, 13700) ## specifies small range including CeO2 match

        for i in iindex:
            if i not in exceptions and i < maxi:
                url_to_scrape = url % i
                r = requests.get(url_to_scrape)
                if r.text.split()[0] == "Can't" or '':
                    if verbose:
                        print('\t---> ERROR on amcsd%05d.cif' % i)
                else:
                    if verbose:
                        print('Reading %s' % url_to_scrape)
                    if savecif:
                        file = 'amcsd%05d.cif' % i
                        f = open(file,'w')
                        f.write(r.text)
                        f.close()
                        if verbose:
                            print('Saved %s' % file)
                    if addDB:
                        try:
                            self.add_ciffile(url_to_scrape, url=True, verbose=verbose, ijklm=i)
                        except:
                            pass




##################################################################################
##################################################################################

#         usr_qry = self.query(self.ciftbl,
#                              self.elemtbl,self.nametbl,self.spgptbl,self.symtbl,
#                              self.authtbl,self.qtbl,self.cattbl,
#                              self.authref,self.compref,self.catref,self.symref)\
#                       .filter(self.authref.c.amcsd_id == self.ciftbl.c.amcsd_id)\
#                       .filter(self.authtbl.c.author_id == self.authref.c.author_id)\
#                       .filter(self.compref.c.amcsd_id == self.ciftbl.c.amcsd_id)\
#                       .filter(self.compref.c.z == self.elemtbl.c.z)\
#                       .filter(self.catref.c.amcsd_id == self.ciftbl.c.amcsd_id)\
#                       .filter(self.catref.c.category_id == self.cattbl.c.category_id)\
#                       .filter(self.nametbl.c.mineral_id == self.ciftbl.c.mineral_id)\
#                       .filter(self.symref.c.symmetry_id == self.symtbl.c.symmetry_id)\
#                       .filter(self.symref.c.iuc_id == self.spgptbl.c.iuc_id)\
#                       .filter(self.spgptbl.c.iuc_id == self.ciftbl.c.iuc_id)

##################################################################################
##################################################################################


##################################################################################

    def amcsd_info(self, amcsd_id, no_qpeaks=None, ciffile=None):
        mineral_id,iuc_id = self.cif_by_amcsd(amcsd_id,only_ids=True)
        mineral_name = self.search_for_mineral(minid=mineral_id)[0].mineral_name
        authors      = self.author_by_amcsd(amcsd_id)

        ## ALLelements,mineral_name,iuc_id,authors = self.all_by_amcsd(amcsd_id)

        if ciffile:
            print(' ==== File : %s ====' % os.path.split(ciffile)[-1])
        else:
            print(' ===================== ')
        print(' AMCSD: %i' % amcsd_id)
        print(' Name: %s' % mineral_name)
        print(' %s' % self.composition_by_amcsd(amcsd_id))
        try:
            print(' Space Group No.: %s (%s)' % (iuc_id,self.symm_id(iuc_id)))
        except:
            print(' Space Group No.: %s' % iuc_id)
        if no_qpeaks:
            print(' No. q-peaks in range : %s' % no_qpeaks)

        authorstr = ' Author(s): '
        for author in authors:
             authorstr = '%s %s' % (authorstr,author.split()[0])
        print(authorstr)
        print(' ===================== ')

    def symm_id(sel, iuc_id):

        if not isinstance(iuc_id, int):
            iuc_id = int(iuc_id.split(':')[0])

        if   iuc_id < 3  : return 'triclinic'    ##   1 -   2 : Triclinic
        elif iuc_id < 16 : return 'monoclinic'   ##   3 -  15 : Monoclinic
        elif iuc_id < 75 : return 'orthorhombic' ##  16 -  74 : Orthorhombic
        elif iuc_id < 143: return 'tetragonal'   ##  75 - 142 : Tetragonal
        elif iuc_id < 168: return 'trigonal'     ## 143 - 167 : Trigonal
        elif iuc_id < 195: return 'hexagonal'    ## 168 - 194 : Hexagonal
        elif iuc_id < 231: return 'cubic'        ## 195 - 230 : Cubic
        else:
            return

    def return_cif(self,amcsd_id):
        search_cif = self.ciftbl.select(self.ciftbl.c.amcsd_id == amcsd_id)
        for row in search_cif.execute():
            return row.cif

##################################################################################

    def all_by_amcsd(self, amcsd_id):

        mineral_id,iuc_id = self.cif_by_amcsd(amcsd_id,only_ids=True)

        mineral_name = self.search_for_mineral(minid=mineral_id)[0].mineral_name
        ALLelements  = self.composition_by_amcsd(amcsd_id)
        authors      = self.author_by_amcsd(amcsd_id)

        return ALLelements, mineral_name, iuc_id, authors

    def q_by_amcsd(self,amcsd_id,qmin=QMIN,qmax=QMAX):

        q_results = self.query(self.ciftbl.c.qstr).filter(self.ciftbl.c.amcsd_id == amcsd_id).all()
        q_all = [json.loads(qrow[0]) for qrow in q_results]

        return [self.axis[i] for i,qi in enumerate(q_all[0]) if qi == 1 and self.axis[i] >= qmin and self.axis[i] <= qmax]

    def author_by_amcsd(self,amcsd_id):

        search_authors = self.authref.select(self.authref.c.amcsd_id == amcsd_id)
        authors = []
        for row in search_authors.execute():
            authors.append(self.search_for_author(row.author_id,id_no=False)[0][0])
        return authors

    def composition_by_amcsd(self, amcsd_id):
        q = self.query(self.compref).filter(self.compref.c.amcsd_id==amcsd_id)
        return [row.z for row in q.all()]

    def cif_by_amcsd(self,amcsd_id,only_ids=False):

        search_cif = self.ciftbl.select(self.ciftbl.c.amcsd_id == amcsd_id)
        for row in search_cif.execute():
            if only_ids:
                return row.mineral_id, row.iuc_id
            else:
                return row.cif

    def mineral_by_amcsd(self,amcsd_id):

        search_cif = self.ciftbl.select(self.ciftbl.c.amcsd_id == amcsd_id)
        for row in search_cif.execute():
            cifstr = row.cif
            mineral_id = row.mineral_id
            iuc_id = row.iuc_id

        search_mineralname = self.nametbl.select(self.nametbl.c.mineral_id == mineral_id)
        for row in search_mineralname.execute():
            mineral_name = row.mineral_name
        return mineral_name

##################################################################################
##################################################################################

    def amcsd_by_q(self, peaks, qmin=None, qmax=None, qstep=None, list=None,
                   verbose=False):

        if qmin is None: qmin = QMIN
        if qmax is None: qmax = QMAX
        if qstep is None: qstep = QSTEP

        ## Defines min/max limits of q-range
        imin, imax = 0, len(self.axis)
        if qmax < np.max(self.axis):
            imax = abs(self.axis-qmax).argmin()
        if qmin > np.min(self.axis):
            imin = abs(self.axis-qmin).argmin()
        qaxis = self.axis[imin:imax]
        stepq = (qaxis[1]-qaxis[0])

        amcsd, q_amcsd = self.match_qc(list=list, qmin=qmin, qmax=qmax)

        ## Re-bins data if different step size is specified
        if qstep > stepq:
            new_qaxis   = np.arange(np.min(qaxis),np.max(qaxis)+stepq,qstep)
            new_q_amcsd = np.zeros((np.shape(q_amcsd)[0],np.shape(new_qaxis)[0]))
            for m,qrow in enumerate(q_amcsd):
                for n,qn in enumerate(qrow):
                    if qn == 1:
                        k = np.abs(new_qaxis-qaxis[n]).argmin()
                        new_q_amcsd[m][k] = 1
            qaxis = new_qaxis
            q_amcsd = new_q_amcsd


        ## Create data array
        peaks_weighting = np.ones(len(qaxis),dtype=int)*-1
        peaks_true = np.zeros(len(qaxis),dtype=int)
        peaks_false = np.ones(len(qaxis),dtype=int)
        for p in peaks:
            i = np.abs(qaxis-p).argmin()
            peaks_weighting[i],peaks_true[i],peaks_false[i] = 1,1,0

        ## Calculate score/matches/etc.
        total_peaks = np.sum((q_amcsd),axis=1)
        match_peaks = np.sum((peaks_true*q_amcsd),axis=1)
        miss_peaks = np.sum((peaks_false*q_amcsd),axis=1)
        scores = np.sum((peaks_weighting*q_amcsd),axis=1)

        return sorted(zip(scores, amcsd, total_peaks, match_peaks, miss_peaks), reverse=True)


    def amcsd_by_chemistry(self, include=[], exclude=[]):

        amcsd_incld = []
        amcsd_excld = []
        z_incld = []
        z_excld = []

        if len(include) > 0:
            for element in include:
                z = self.get_element(element).z
                if z is not None and z not in z_incld:
                    z_incld += [z]
        if isinstance(exclude,bool):
            if exclude:
                for element in ELEMENTS:
                    z, name, symbol = element
                    z = int(z)
                    if z not in z_incld:
                        z_excld += [z]
        else:
            if len(exclude) > 0:
                for element in exclude:
                    z = self.get_element(element).z
                    if z is not None and z not in z_excld:
                        z_excld += [z]

        z_list_include = [1 if z in z_incld else 0 for z in np.arange(len(ELEMENTS)+1)]
        z_list_exclude = [1 if z in z_excld else 0 for z in np.arange(len(ELEMENTS)+1)]

        amcsd,z_amcsd = self.return_z_matches(list=list)

        ## Calculate score/matches/etc.
        match_z = np.sum((np.array(z_list_include)*np.array(z_amcsd)),axis=1)
        miss_z  = np.sum((np.array(z_list_exclude)*np.array(z_amcsd)),axis=1)

        for i,amcsd_id in enumerate(amcsd):
            if match_z[i] == np.sum(z_list_include) and miss_z[i] <= 0:
                amcsd_incld += [amcsd_id]
            else:
                amcsd_excld += [amcsd_id]
        return amcsd_incld


    def amcsd_by_mineral(self, min_name, list=None, verbose=True):
        """
        search by mineral name
        """
        out = []
        minerals = self.search_for_mineral(name=min_name)

        q = self.query(self.ciftbl)
        if list is not None:
            q = q.filter(self.ciftbl.c.amcsd_id.in_(list))

        ##  Searches mineral name for database entries
        if len(minerals) > 0:
            mids = [m.mineral_id for m in minerals]
            q = q.filter(self.ciftbl.c.mineral_id.in_(mids))
            for row in q.all():
                if row.amcsd_id not in out:
                    out.append(row.amcsd_id)
        return out

    def amcsd_by_author(self,include=[''],list=None,verbose=True):

        amcsd_incld = []
        auth_id = []

        for author in include:
            id = self.search_for_author(author)
            auth_id += id

        ##  Searches mineral name for database entries
        usr_qry = self.query(self.ciftbl,self.authtbl,self.authref)\
                      .filter(self.authref.c.amcsd_id == self.ciftbl.c.amcsd_id)\
                      .filter(self.authref.c.author_id == self.authtbl.c.author_id)
        if list is not None:
            usr_qry = usr_qry.filter(self.ciftbl.c.amcsd_id.in_(list))

        ##  Searches author name in database entries
        if len(auth_id) > 0:
            fnl_qry = usr_qry.filter(self.authref.c.author_id.in_(auth_id))
            ## This currently works in an 'or' fashion, as each name in list
            ## can be matched to multiple auth_id values, so it is simpler to
            ## consider them all separately. Making a 2D list and restructuring
            ## query could improve this
            ## mkak 2017.02.24
            for row in fnl_qry.all():
                if row.amcsd_id not in amcsd_incld:
                    amcsd_incld += [row.amcsd_id]

        return amcsd_incld


    def match_elements(self, elems, exclude=None):
        """match structues containing all elements in a list

        Arguments:
        ----------
        elems    list of elements to match
        exclude  list of elements to exclude for match (default None)

        Returns:
        --------
        list of amcsd ids for structures

        """
        matches = None
        q = self.query(self.compref)

        for elem in elems:
            elem = self.get_element(elem).z
            rows = q.filter(self.compref.c.z==elem).all()
            sids = [row.amcsd_id for row in rows]
            if matches is None:
                matches = sids
            else:
                matches = [s for s in sids if s in matches]

        if exclude is not None:
            for elem in exclude:
                elem = self.get_element(elem).z
                for row in q.filter(self.compref.c.z==elem).all():
                    if row.amcsd_id in matches:
                        matches.remove(row.amcsd_id)
        return matches

    def create_z_array(self,z):
        z_array = np.zeros((len(ELEMENTS)+1),dtype=int) ## + 1 gives index equal to z; z[0]:nothing
        for zn in z:
            z_array[zn] = 1
        return z_array


##################################################################################
##################################################################################
    def match_qc(self, list=None, qmin=QMIN, qmax=QMAX):

        if list is None:
            qqry = self.query(self.ciftbl.c.qstr).all()
            idqry = self.query(self.ciftbl.c.amcsd_id).all()
        else:
            qqry = self.query(self.ciftbl.c.qstr)\
                   .filter(self.ciftbl.c.amcsd_id.in_(list))\
                   .all()
            idqry = self.query(self.ciftbl.c.amcsd_id)\
                    .filter(self.ciftbl.c.amcsd_id.in_(list))\
                    .all()

        imin,imax = 0,len(self.axis)
        if qmax < QMAX: imax = abs(self.axis-qmax).argmin()
        if qmin > QMIN: imin = abs(self.axis-qmin).argmin()

        return [id[0] for id in idqry],[json.loads(q[0])[imin:imax] for q in qqry]

    def create_q_array(self, q):

        q_array = np.zeros(len(self.axis), dtype=int)
        for qn in q:
            i = np.abs(self.axis-qn).argmin()
            q_array[i] = 1
        return q_array

##################################################################################

    def get_element(self, element):
        '''
        searches elements for match in symbol, name, or atomic number;
        match must be exact.

        returns row with attributes  .z, .element_name, .element_symbol
        '''
        if isinstance(element, int):
            element = '%d' % element
        elif isinstance(element, str):
            element = element.title()
        q = self.query(self.elemtbl)
        row = q.filter(or_(self.elemtbl.c.z == element,
                           self.elemtbl.c.element_symbol == element,
                           self.elemtbl.c.element_name == element)).one()
        return row

    def search_for_author(self,name,exact=False,id_no=True,verbose=False):
        '''
        searches database for author matching criteria given in 'name'
           - if name is a string:
                  - will match author name containing text
                  - will match id number if integer given in string
                  - will only look for exact match if exact flag is given
           - if name is an integer, will only match id number from database
        id_no: if True, will only return the id number of match(es)
               if False, returns name and id number
        e.g.   as INTEGER
               >>> cif.search_for_author(6,id_no=False)
                    ([u'Chao G Y'], [6])
               as STRING
               >>> cif.search_for_author('6',id_no=False)
                    ([u'Chao G Y', u'Geology Team 654'], [6, 7770])
        '''

        authname = []
        authid   = []

        id, name = filter_int_and_str(name,exact=exact)
        authrow = self.query(self.authtbl)\
                      .filter(or_(self.authtbl.c.author_name.like(name),
                                  self.authtbl.c.author_id  == id))
        if len(authrow.all()) == 0:
            if verbose: print('%s not found in author database.' % name)
        else:
            for row in authrow.all():
                authname += [row.author_name]
                authid   += [row.author_id]

        if id_no: return authid
        else: return authname,authid

    def search_for_mineral(self, name=None, minid=None, exact=False):
        '''
        searches database for mineral by name or by ID

        Arguments:
        ----------
          name  (str or None): mineral name to match
          minid (int or None): mineral ID in database to match
          exact (bool):  whether to match name exactly [False]


        Returns:
        --------
        list of matching rows

        # [row.mineral_name, row.mineral_id]
        '''

        rows = []
        q = self.query(self.nametbl)

        if name is not None:
            if not exact:
                name = '%%%s%%' % name
            rows = q.filter(self.nametbl.c.mineral_name.like(name)).all()
        elif minid is not None:
            rows = q.filter(self.nametbl.c.mineral_id == minid).all()
        return rows

    def cif_count(self):
        return self.query(self.ciftbl).count()

    def return_q(self):
        q = [float(row.q) for row in self.query(self.qtbl).all()]
        return np.array(q)

    def get_mineral_names(self):
        names = []
        for name in self.query(self.nametbl.c.mineral_name).all():
            if isinstance(name[0], str):
                names.append(name[0])
        return sorted(names)

    def return_author_names(self):

        authorqry = self.query(self.authtbl)
        names = []
        for row in authorqry.all():
            names += [row.author_name]

        return sorted(names)

def filter_int_and_str(s, exact=False):
        try:
            i = int(s)
        except:
            i = 0
        if not exact:
            try:
                s = '%'+s+'%'
            except:
                pass
        return i, s


def column(matrix, i):
    return [row[i] for row in matrix]

class RangeParameter(object):
    def __init__(self,min=None,max=None,unit=None):
        self.min   = min
        self.max   = max
        self.unit  = unit

class SearchCIFdb(object):
    '''
    interface to the search the cif database
    '''
    def __init__(self, verbose=False):

        self.verbose = verbose

        ## running list of included amcsd id numbers
        self.amcsd_id = []

        ## tags for searching
        self.authors    = []
        self.keywords   = []
        self.categories = []
        self.amcsd      = []
        self.qpks       = []

        self.mnrlname   = ''

        self.elem_incl = []
        self.elem_excl = []
        self.allelem   = column(ELEMENTS, 2)

        self.lattice_keys = ['a', 'b', 'c', 'alpha', 'beta', 'gamma']

        self.sg    = None
        self.a     = RangeParameter()
        self.b     = RangeParameter()
        self.c     = RangeParameter()
        self.alpha = RangeParameter()
        self.beta  = RangeParameter()
        self.gamma = RangeParameter()


    def show_all(self):
        for key in ['authors','mnrlname','keywords','categories','amcsd','qpks']:
             print('%s : %s' % (key,self.show_parameter(key=key)))
        print('chemistry : %s' % self.show_chemistry())
        print('geometry : %s' % self.show_geometry())

    def show_parameter(self, key='authors'):
        s = ''
        if len(self.__dict__[key]) > 0:
            for i,item in enumerate(self.__dict__[key]):
                item = item.split()[0]
                if i == 0:
                    s = '%s' % (item)
                else:
                    s = '%s, %s' % (s,item)
        return s


    def read_parameter(self,s,clear=True,key='authors'):
        '''
        This function works for keys:
        'authors'
        'mnrlname
        keywords','categories','amcsd','qpks'
        '''

        if clear:
            self.__dict__[key] = []
        if len(s) > 0:
            for a in s.split(','):
                try:
                    self.__dict__[key] += [a.split()[0]]
                except:
                    pass

    def read_chemistry(self,s,clear=True):

        if clear:
            self.elem_incl,self.elem_excl = [],[]
        chem_incl,chem_excl = [],[]

        chemstr = re.sub('[( )]','',s)
        ii = -1
        for i,s in enumerate(chemstr):
            if s == '-':
                ii = i
        if ii > 0:
            chem_incl = chemstr[0:ii].split(',')
            if len(chemstr)-ii == 1:
                for elem in self.allelem:
                    if elem not in chem_incl:
                        chem_excl += [elem]
            elif ii < len(chemstr)-1:
                chem_excl = chemstr[ii+1:].split(',')
        else:
            chem_incl = chemstr.split(',')

        for elem in chem_incl:
            elem = capitalize_string(elem)
            if elem in self.allelem and elem not in self.elem_incl:
                self.elem_incl += [elem]
                if elem in self.elem_excl:
                    j = self.elem_excl.index(elem)
                    self.elem_excl.pop(j)
        for elem in chem_excl:
            elem = capitalize_string(elem)
            if elem in self.allelem and elem not in self.elem_excl and elem not in self.elem_incl:
                self.elem_excl += [elem]

    def show_chemistry(self):

        s = ''
        for i,elem in enumerate(self.elem_incl):
            if i==0:
                s = '(%s' % elem
            else:
                s = '%s,%s' % (s,elem)
        if len(self.elem_incl) > 0:
            s = '%s) ' % s
        if len(self.elem_excl) > 0:
            s = '%s- ' % s
        # if all else excluded, don't list
        if (len(self.allelem)-20) > (len(self.elem_incl)+len(self.elem_excl)):
            for i,elem in enumerate(self.elem_excl):
                if i==0:
                    s = '%s(%s' % (s,elem)
                else:
                    s = '%s,%s' % (s,elem)
            if len(self.elem_excl) > 0:
                s = '%s)' % s
        return s

    def show_geometry(self,unit='A'):

        s = ''

        key = 'sg'
        if self.__dict__[key] is not None:
            s = '%s%s=%s,' % (s,key,self.__dict__[key])
        for i,key in enumerate(self.lattice_keys):
            if self.__dict__[key].min is not None:
                s = '%s%s=%0.2f' % (s,key,float(self.__dict__[key].min))
                if self.__dict__[key].max is not None:
                    s = '%sto%0.2f' % (s,float(self.__dict__[key].max))
                s = '%s%s,' % (s,self.__dict__[key].unit)

        if len(s) > 1:
            if s[-1] == ',':
                s = s[:-1]

        return s

    def read_geometry(self,s):

        geostr = s.split(',')
        used = []
        for par in geostr:
            key = par.split('=')[0]
            val = par.split('=')[1]
            if key in 'sg':
                self.__dict__[key] = val
                used += [key]
            elif key in self.lattice_keys:
                values = [''.join(g) for _, g in groupby(val, str.isalpha)]
                self.__dict__[key].min = values[0]
                if len(values) > 1:
                    self.__dict__[key].unit = values[-1]
                if len(values) > 2:
                    self.__dict__[key].max = values[2]
                else:
                    self.__dict__[key].max = None
                used += [key]

        ## Resets undefined to None
        for key in self.lattice_keys:
            if key not in used:
                self.__dict__[key] = RangeParameter()
        key = 'sg'
        if key not in used:
            self.__dict__[key] = None

def match_database(cifdb, peaks, minq=QMIN, maxq=QMAX, verbose=True):
    """
    fracq  : min. ratio of matched q to possible in q range, i.e. 'goodness gauge'
    pk_wid : maximum range in q which qualifies as a match between fitted and ideal
    """
    stepq = 0.05
    scores,amcsd,total_peaks,match_peaks,miss_peaks = zip(*cifdb.amcsd_by_q(peaks,
                                                       qmin=minq,qmax=maxq,qstep=stepq,
                                                       list=None,verbose=False))

    MATCHES = [match for i,match in enumerate(amcsd) if scores[i] > 0]

    if verbose:
        print('\n')
        if len(MATCHES) > 100:
            print('DISPLAYING TOP 100 of %i TOTAL MATCHES FOUND.' % len(MATCHES))
        else:
            print('%i TOTAL MATCHES FOUND.' % len(MATCHES))
        j = 0
        for i,id_no in enumerate(amcsd):
            if j < 100:
                if scores[i] > 0:
                    j += 1
                    str = 'AMCSD %5d, %s (score of %2d --> %i of %i peaks)' % (id_no,
                             cifdb.mineral_by_amcsd(id_no),scores[i],
                             match_peaks[i],total_peaks[i])
                    print(str)
        print('')

    return MATCHES


def cif_match(peaks, qmin=None, qmax=None, verbose=False, _larch=None):
    """
    fracq  : min. ratio of matched q to possible in q range, i.e. 'goodness gauge'
    pk_wid : maximum range in q which qualifies as a match between fitted and ideal
    """
    cifdb = get_cifdb(_larch=_larch)
    qstep = 0.05

    rows = cifdb.amcsd_by_q(peaks, qmin=qmin,qmax=qmax, qstep=qstep)

    scores, amcsd, total_peaks, match_peaks, miss_peaks = rows

    matches = []
    for i, cdat in enumerate(amcsd):
        if score[i] > 0:
            matches.append(cdat)

    if verbose:
        print('\n')
        if len(MATCHES) > 100:
            print('DISPLAYING TOP 100 of %i TOTAL MATCHES FOUND.' % len(MATCHES))
        else:
            print('%i TOTAL MATCHES FOUND.' % len(MATCHES))
        matches = matches[:100]
        for i, id_no in enumerate(amcsd):
            if j < 100:
                if scores[i] > 0:
                    j += 1
                    str = 'AMCSD %5d, %s (score of %2d --> %i of %i peaks)' % (id_no,
                             cifdb.mineral_by_amcsd(id_no),scores[i],
                             match_peaks[i],total_peaks[i])
                    print(str)
        print('')

    return matches


def read_cif(filename=None, amcsd_id=None, _larch=None):
    """make a representation of a CIF data structure
    for crystallographic computations

    Arguments:
    ----------
    filename  (str or None) name of CIF file
    amcsd_id  (int or None) index of CIF in Am Min Cystal Structure database

    Returns
    -------
    CIF representation
    """
    cifdb = get_cifdb(_larch=_larch)
    return create_xrdcif(filename=filename, cifdb=cifdb, amcsd_id=amcsd_id)
