# -*- coding: utf-8 -*-
"""
Created on Tue Aug 14 20:14:16 2018

@author: USER
"""

# Codes are free to use. Do whatever you want

from __future__ import absolute_import

"""Read raw data"""

####################### LIBRARY #############################

# exceptions library
from exceptions import (Data_Format_Exception,
                        Data_Match_Exception)

# Python stdlib imports
import datetime

# data processing library
import numpy as np

# pyrod library

####################### CONSTANT ############################

# constant 

####################### FUNCTIONS ###########################



######################## CLASSS #############################


# film property--roughenss, surface roughness and interface roughenss

class roughness(object):
    
    def __init__(self, iq):
        
        self.q = iq
    
    # r_beta -- robbinson roughness factor
    def robinson_roughness(self, r_beta):
        
        """Robinson roughness model: beta is the film fraction in first layer.
           In surface layer n, the fraction is beta^n. 
           This fuction is for cubic crystal, which is common for pervoskite
           
           [1]. Robinson, Ian K. "Crystal truncation rods and surface roughness." 
                Physical Review B 33.6 (1986): 3830."""
                
        if r_beta < 0:
            raise Data_Format_Exception('robinsion_roughness factor should be positive')
            
        r_factor = (1-r_beta)/((1-r_beta)**2 + 4*r_beta*(np.sin(np.pi*self.q)**2))**0.5

        return r_factor
    
    def interface_roughness(self, i_beta):
        
        """interface roughness model, refinement for small angle reflection
           exp(-q2*r2)"""
           
        if i_beta < 0:
            raise Data_Format_Exception('interface_roughness factor should be positive')
            
        i_factor = np.exp(-1*(i_beta*self.q))
        
        return i_factor
    
# film vacancy--gradient vacancy, layer surface 

class vacancy(object):
    
    def __init__(self, iq, var_list, var_table):
        
        self.q = iq
        
        self.l = var_list
        self.t = var_table
        
    def gradient_vacancy(self, alpha, va, vi):
        
        """
           Normally, A and O is easy to exist vancany in ABO3, and exponential function is 
           good fitting in XRD fitting. However, some materials have special vancany mdoel
           For example: the oxygen vancany distribtion of SCoO3 and SrFeO3, oxygen vancany 
           is very complex. Even for STO, linear oxygen vancany clustering is very common 
           in thermal threatment.
           ...............................
           
           [1].Druce, J., et al. "Surface termination and subsurface restructuring of 
               perovskite-based solid oxide electrode materials." Energy & Environmental 
               Science 7.11 (2014): 3593-3599.
        """
        
        # check and put vacancy at interface and surface in order
        if va < 0 or vi < 0 or alpha < 0:
            raise Data_Format_Exception('interface_roughness factor should be positive')
            
        vs = np.max([va, vi])
        ve = np.min([va, vi])
        
        # layers
        layer = range(len(self.t['posz_list']))
        
        # factors
        
        a = (vs - ve)/(np.exp(-alpha*(layer-1)) - 1)
        b = ve - a
        
        gradient_v = a + b*np.exp(-alpha*np.array(layer))
        
        return gradient_v
    
    def surface_vacancy(self, ions, vs, layers = [-1]):
        
        """vacancy for specified layers and ions. Normally, A site ion and O2- is vacancy"""
        
        if not len(ions) == len(vs):
            raise Data_Match_Exception('ions number should match vacancy number!')
            
        ocu_table = self.t['ocu_table']
        ion_table = self.t['ion_table']
        
        ion_index = 0
        
        for ion in ions:
            for layer in layers:
                
                layer_ion  = ion_table[-1*(1 + layer)]
                ion_locate = layer_ion.index(ion)
                ocu_table[-1*(1 + layer), ion_locate] = 1-vs[ion_index]
                
            ion_index += 1
            
        return ocu_table
        
# strain relex for epitaxial films. 50uc, normally.
# interface relax and surface relax

class relax(object):
    
    def __init__(self, posz_list):
        
        self.pz = posz_list
        
    def stain_relax(self, cl, bc, sc):
        
        # sr -- strain relax
        # sc -- strained lattice c
        # cl -- critalc layaers number
        # bc -- bulk lattice c
        
        ln = len(self.pz)
        sr = self.pz*((sc-bc)*np.exp(-np.array(range(ln)/cl) + bc))
        
        return sr
    
    # layer relax include interface relax and surface relax
    # surface relax is common and well studied
    # interface relax and second surface layer relax is also included
    
    def layer_relax(self, rv, layers):
        
        if not len(rv) == len(layers):
            raise Data_Match_Exception('relax value number should match layer number')
            
        lr = self.pz
        
        # input relax value in posz_list
        for layer in layers:
            lr[layer] = rv[layer]
            
        return lr
    
    