"""Program for creating the Hybrid PSSE case from the original PSSE case.

This program can only run on PSSE version 30 and above. if the power
flow base case is in version 29 or below, please save it as version 30 to begin
with.

**********************************************************************
*                                                                    *
*  THIS PROGRAM AND ITS DOCUMENTATION ARE PREPARED BY                *
*  ELECTRANIX CORPORATION, WINNIPEG, MANITOBA, CANADA                *
*                                                                    *
*  ELECTRANIX DOES NOT WARRANT EXPRESSLY OR BY IMPLICATION,          *
*  THE DATA OR COMPUTER PROGRAMMING IN THIS FILE TO BE CORRECT.      *
*  ELECTRANIX ASSUMES NO RESPONSIBILITY FOR                          *
*  APPROPRIATE USE OF THIS FILE AND ASSUMES NO LIABILITY FOR ANY     *
*  POSSIBLE DAMAGING STUDY RESULTS OBTAINED BY USE OF THIS FILE.     *
*                                                                    *
**********************************************************************
===============================================================================
VER   DATE        BY:          DESCRIPTION OF CHANGES
-------------------------------------------------------------------------------

1.0   06/02/2015  ELECTRANIX   Initial Version
1.1   03/06/2015  ELECTRANIX   New CCT format .dyr records added (V33 & up)
2.0   15/06/2016  SIEMENS PTI  Refactored for Efficiency and Python Style Guide
2.1   16/06/2016  ELECTRANIX   Add swing bus (if deleted) and EINIT .dyr record 
2.2   04/10/2016  ELECTRANIX   Revised the code to use python "execfile" instead of "import"  
2.3   20/12/2016  ELECTRANIX   Revised the initial powerflow solution method 
2.4   13/01/2017  ELECTRANIX   Fixed a bug associated with deleting FACTS devices 
2.5   13/07/2017  ELECTRANIX   Improve automatic modification of DYR file (API version 2)
===============================================================================
"""

import sys
import os
import math
import re

import psspy

try:
    set
except NameError:
    from sets import Set as set

def RedirectOutputToPSSE():
    # redirect output back to PSSE:
    psspy.lines_per_page_one_device(2, 10000000)
    psspy.progress_output(1, "", [0, 0])
    psspy.prompt_output(1, "", [0, 0])
    return;

python_hybrid_api_version = 2

psspy.psseinit(120000)

# cache the param defaults:
_i, _f = psspy.getbatdefaults()
_s = ""
# CONSTANTS:

if 'sav_path' not in locals():
    print "\n Error: A valid .sav file must be loaded before the configuration script is run.\n"
    print " Please load a valid .sav file and try again.\n"
    RedirectOutputToPSSE()
    raise SystemExit

error = psspy.case(locals()['sav_path'])
if error != 0:
    print "\n An error occured while reading the .sav case " + locals()['sav_path'] + "\n"
    print " Either the file does not exist or a reading error occured.\n"
    print " Execution of the python script has been halted.\n"
    RedirectOutputToPSSE()
    raise SystemExit

PROGRESS_FILE = os.path.dirname(locals()['sav_path']) + r"\ETranPyScriptResults.txt"
print " Outputting script log to " + PROGRESS_FILE
#PROGRESS_FILE = "HBpython.txt"

# directing the output to a txt file
ierr = psspy.progress_output(2, PROGRESS_FILE, [0, 0])
if ierr != 0:
    print " Error opening file " + PROGRESS_FILE
ierr = psspy.prompt_output(2, PROGRESS_FILE, [0, 0])
if ierr != 0:
    print " Error opening file " + PROGRESS_FILE

calling_api_version = locals().get('hybrid_python_api_version', 1) #default to 1 for old cfg files
if (python_hybrid_api_version != calling_api_version):
    print "\n ERROR: The hybrid config file version (" + str(calling_api_version) + ") does not match the installed E-Tran Plus python interface (" + str(python_hybrid_api_version) + ")\n";
    if (python_hybrid_api_version > calling_api_version):
        print " Your generated hybrid_cfg.py file is out-of-date\n"
        print " Please re-translate your case using E-Tran to create an updated hybrid_cfg.py file\n"
    else:
        print " Your E-Tran is out-of-date compared to the E-Tran install used to generate the hybrid_cfg.py file\n"
        print " Please update your local E-Tran install and try again\n"
    #print again to PSSE window
    RedirectOutputToPSSE()
    print "\n ERROR: The hybrid config file version (" + str(calling_api_version) + ") does not match the installed E-Tran Plus python interface (" + str(python_hybrid_api_version) + ")\n";
    if (python_hybrid_api_version > calling_api_version):
        print " Your generated hybrid_cfg.py file is out-of-date\n"
        print " Please re-translate your case using E-Tran to create an updated hybrid_cfg.py file\n"
    else:
        print " Your E-Tran is out-of-date compared to the E-Tran install used to generate the hybrid_cfg.py file\n"
        print " Please update your local E-Tran install and try again\n"

    raise SystemExit()

# Display a message to indicate the execution of the python script:
print "*" * 64
print " Start executing the python script to insert the Hybrid interface"
print " (This script can only be used with PSSE version 30 or greater)"
print "*" * 64 + "\n"

# lists of popular solution settings
settings1 = [0, 0, 0, 1, 1, 0, 99, 0]
settings2 = [0, 0, 0, 1, 1, 0, 0, 0]
settings3 = [0, 0, 0, 1, 2, 0, 0, 0]
settings4 = [0, 0, 0, 1, 1, 0, 99, 0]
settings5 = [0, 0, 0, 1, 1, 0, 0, 0]
settings6 = [0, 0, 0, 1, 2, 0, 0, 0]

settings = [settings1,settings2,settings3,settings4,settings5,settings6]

# initially solve the power flow case using Fixed slope decoupled Newton Raphson
# locked taps, enabled all shunts, disabled area interchange, adjust DC taps,
# VAR limits apply automatically:

itr_best = 100

best_attempt = 100

print "\n Source SAV path is " + locals()['sav_path'] + "\n"
print " Source DYR path is " + locals()['dyr_path'] + "\n"

# checking the number of iterations
for attempt in range(0,6):
    print "\n Solution attempt # %i" % ( attempt + 1)

    # open the .sav file:
    psspy.case(locals()['sav_path'])
    
    # load solution settings of this attempt
    this_soln = settings[attempt]
    
    if attempt < 3:
        psspy.fdns(this_soln)
    
    else:
        psspy.fnsl(this_soln)
        
    # checking if case solved
    result = psspy.solved()
    
    # checking iterations
    this_itr = psspy.iterat()
    
    # update lowest iteration and best attempt if case solved and this_itr is lowest number of iterations yet
    if (result == 0):
        itr_best = this_itr
        best_attempt = attempt
        break

# exit python execution if itr_best did not change from 100
if (itr_best == 100):
    print "\n The original base case could not be solved with six different solution settings."
    print " Check the base case and run the script once again"    
    # exit from the python execution:
    RedirectOutputToPSSE()
    print "\n The original base case could not be solved with six different solution settings."
    print " Check the base case and run the script once again"    
    raise SystemExit()

print " Base case successfully solved on attempt # %i" % (best_attempt + 1)


name, major, minor, modlvl, date, stat = psspy.psseversion()
PSSE_VER = major  # save the PSSE version as some of the API's are new

print " \n The PSSE version of your Powerflow case is " + str(PSSE_VER)

if locals()['new_dyr_format'] and PSSE_VER < 33:
    print " The new CCT format of CONEC/CONET models requested"
    print " are only applicable to PSSE V33 and up.\n"
    print " Writing the .dyr record with conventional format...\n "

    locals()['new_dyr_format'] = False

# this will be the name of the interface added .sav case:
hybrid_sav = locals()['sav_path'][:-4] + locals()['sav_suffix'] + ".sav"

# this will be the new .dyr file that has the original .dyr plus required new models:
# It will write out to the same folder as the sav file
hybrid_dyr = locals()['sav_path'][0:locals()['sav_path'].rfind('\\')]   # start with the full path minus filename of the sav file
hybrid_dyr = hybrid_dyr + locals()['dyr_path'][locals()['dyr_path'].rfind('\\'):]  # add on just filename of the dyr file
hybrid_dyr = hybrid_dyr[:-4] + locals()['dyr_suffix'] + ".dyr"  # modify the full dyr path and filename to include the _hybrid suffix

# cache boundary bus count:
len_boundary = len(locals()['boundary_buses'])

# check that boundary buses are in the case. If one is missing, stop script:
for ibus in locals()['boundary_buses']:
    error, type_ = psspy.busint(ibus, "TYPE")
    if error:
        print " Boundary bus " + str(ibus) + " is not in the base case"
        print " Execution of the python script is abandoned"
        print " Exiting from the python script\n"
        RedirectOutputToPSSE()
        raise SystemExit()

# check that boundary buses to see whether they are dummy buses associated with multi-section line. 
# If one dummy bus is found, stop script: 
for ibus in locals()['boundary_buses']: 
    ierr = psspy.inibrx(ibus, 1) 
    if ierr == 3:
        print " Boundary bus " + str(ibus) + " is a dummy bus of a PSSE multisection line"
        print " Boundary bus cannot be a dummy bus"
        print " Execution of the python script is abandoned"
        print " Exiting from the python script\n"
        RedirectOutputToPSSE()
        raise SystemExit()
      
# check that deleted buses are in the case. Remove missing ones from list:
for ibus in locals()['deleted_buses']:
    error, type_ = psspy.busint(ibus, 'TYPE')
    if error:
        s = " Bus to be deleted (i.e. Bus " + str(ibus) + ") is not in "
        s += "the base case, will continue.....\n"
        print s

        # delete the bus from the "DeletedBus" list:
        locals()['deleted_buses'].remove(ibus)

# disconnect the deleted buses:
for ibus in locals()['deleted_buses']:
    psspy.dscn(ibus)

# disconnect lines between the boundary buses
# note that in V30, VSC DC links between boundary busses are not deleted
# (due to the unavailablity of API routines in V30)

# Create a bus subsystem for the circuit we are keeping in PSCAD

sid = 1
numbus=len(locals()['boundary_buses'])
buses=locals()['boundary_buses']
usekv = 0
basekv = []
numarea = 0
areas = []
numowner = 0
owners = []
numzone = 0
zones = []

err = psspy.bsys(sid, usekv, basekv, numarea, areas, numbus, buses, numowner, owners, numzone, zones)

# Find and disable all branches in the circuit we are keeping in PSCAD:

owner = 1  # to use bus ownership
ties = 1  # for interior subsystem branches only
flag = 1  # for only in - service non - transformer branches
entry = 1  # for single entry (each branch once)

string = ['FROMNUMBER', 'TONUMBER']
err, (ibuses, jbuses) = psspy.abrnint(sid, owner, ties, flag, entry, string)

string = ['ID']
err, (ckts,) = psspy.abrnchar(sid, owner, ties, flag, entry, string)

for ibus, jbus, ckt in zip(ibuses, jbuses, ckts):
    psspy.branch_data(ibus, jbus, ckt, intgar1=0)

# Find and disable all 2 winding xfmrs in the circuit we are keeping in PSCAD:

string = ['FROMNUMBER', 'TONUMBER']
err, (ibuses, jbuses) = psspy.atrnint(sid, owner, ties, flag, entry, string)

string = ['ID']
err, (ckts,) = psspy.atrnchar(sid, owner, ties, flag, entry, string)

for ibus, jbus, ckt in zip(ibuses, jbuses, ckts):
    psspy.two_winding_data(ibus, jbus, ckt, intgar1=0)

# Find and disable all DC Links in the circuit we are keeping in PSCAD:

if PSSE_VER == 30:
    for dc_id in range(1, 51):  # for two terminal DC
        ierr, recbus = psspy.dc2int(dc_id, 'RECT')
        if ierr == 2:
            continue
        for ibus in locals()['boundary_buses']:
            if recbus == ibus:
                ierr, invbus = psspy.dc2int(dc_id, 'INV')
                for jbus in locals()['boundary_buses']:
                    if invbus == jbus:
                        psspy.two_terminal_dc_line_data(dc_id, intgar1=0)

else:  # PSSE VER 31+
    string = ['DCNAME']  # for two terminal DC
    err, (dc_names,) = psspy.a2trmdcchar(sid, ties, flag, string)

    for dc_name in dc_names:
        psspy.two_terminal_dc_line_data(dc_name, intgar1=0)

    string = ['VSCNAME']  # for two terminal VSC DC
    err, (vsc_names,) = psspy.avscdcchar(sid, ties, flag, string)

    for vsc_name in vsc_names:
        err = psspy.vsc_dc_line_data(vsc_name, intgar1=0)

# deleting shunt devices at boundary busses. Note that GNE devices at boundary
# busses (introduced in V32 - from V32 and onwards) are not deleted by this
# script.

# disable all loads at the boundary busses:

flag = 1  # for only in - service loads
string = ['NUMBER']
err, (ibuses,) = psspy.aloadint(sid, flag, string)

string = ['ID']
err, (loadids,) = psspy.aloadchar(sid, flag, string)

for ibus, loadid in zip(ibuses, loadids):
    ierr = psspy.load_data(ibus, loadid, intgar1=0)

# disable all bus shunts at the boundary busses:

flag = 1  # for only in-service fixed shunts

if PSSE_VER <= 31:  # bus code must also be set to 2
    for ibus in locals()['boundary_buses']:
        psspy.bus_data(ibus, intgar1=2, realar1=0.0, realar2=0.0)

if PSSE_VER > 30:
    string = ['NUMBER']
    ierr, (ibuses,) = psspy.afxshuntint(sid, flag, string)

    string = ['ID']
    ierr, (shuntids,) = psspy.afxshuntchar(sid, flag, string)

    for ibus, shuntid in zip(ibuses, shuntids):
        ierr = psspy.shunt_data(ibus, shuntid, 0)

# disable all switched shunts at the boundary busses:

flag = 1  # for only in - service switched shunts
string = ['NUMBER']
err, (ibuses,) = psspy.aswshint(sid, flag, string)

for ibus in ibuses:
    psspy.switched_shunt_data_3(ibus, intgar11=0)

# out-of-service the existing FACTS devices (with only shunt element -
# ie STATCOMS) at the boundary busses:

if PSSE_VER > 30:
    flag = 1  # for only in - service FACTS devices
    ties = 1  # for interior subsystem FACTS only
    fcttyp = 1  # for only FACTS devices without a series element

    string = ['FACTSNAME']
    err, (ibuses,) = psspy.afactschar(sid, ties, flag, fcttyp, string)
    for ibus in ibuses:
        psspy.facts_data_2(ibus, intgar3=0, intgar5=0)

else:  # version 30:
    for factid in range(1, 51):
        err, sendbus = psspy.fcdint(factid, 'SEND')
        if err:
            continue
        for ibus in locals()['boundary_buses']:
            if sendbus == ibus:
                err, termbus = psspy.fcdint(factid, 'TERM')
                if termbus == 0:
                    psspy.facts_data(ibus, intgar3=0, intgar5=0)

# disable generators existing at the boundary busses:

flag = 1  # for only in-service generators

string = ['NUMBER']
err, (gen_buses,) = psspy.amachint(sid, flag, string)

string = ['ID']
err, (machids,) = psspy.amachchar(sid, flag, string)

for ibus, machid in zip(gen_buses, machids):
    psspy.machine_data_2(ibus, machid, intgar1=0, intgar6=0)

# finished deleting all the required shunt devices at the boundary busses

# Disable all 3 winding transformers in the circuit we are keeping in PSCAD    

string = ['WIND1NUMBER', 'WIND2NUMBER', 'WIND3NUMBER']
err, (ibuses, jbuses, kbuses) = psspy.atr3int(sid, owner, ties, flag, entry, string)

string = ['ID']
err, (ckts,) = psspy.atr3char(sid, owner, ties, flag, entry, string)

for ibus, jbus, kbus, ckt in zip(ibuses, jbuses, kbuses, ckts):
    psspy.three_wnd_impedance_data(ibus, jbus, kbus, ckt, intgar8=0)

# Add the interface generators (with generator ID specified in "locals()['genid']")
# at the boundary busses. The diagonal source impedance and off-diagonal
# branches are computed by E-TRAN using an LDU reduction of the kept network:


nodes = len(locals()['boundary_buses'])

gen_r_temp = locals()['gen_r']
gen_x_temp = locals()['gen_x']
ibus_temp = locals()['boundary_buses']
jbus_temp = locals()['boundary_buses']

k = -1
for i in range(0, nodes):
    k += 1
    # Add the off-diagonal branches at the boundary busses:
    for j in range(i + 1, nodes):
        k += 1
      
        if abs(gen_r_temp[k]) > 1.0E-10 or abs(gen_x_temp[k]) > 1.0E-10:
            ibus = ibus_temp[i]
            jbus = jbus_temp[j]          
            machid = locals()['genid']
            basemva = locals()['BMVA']
            err, ibus_BASEKV = psspy.busdat(ibus, 'BASE')
            err, jbus_BASEKV = psspy.busdat(jbus, 'BASE')
    
            if ibus_BASEKV == jbus_BASEKV: # off diagonal is a transmission line
                err = psspy.branch_data(ibus, jbus, machid, [1, ibus, 1, 0, 0, 0],
                                      [gen_r_temp[k], gen_x_temp[k], 0.0, 0.0, 0.0,
                                       0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0,
                                       1.0, 1.0])

                s = " ****** Equivalent Branch added between busses: %d and %d"
                s += " - Error Status = %d."
                print s % (ibus, jbus, err)

            else:  # # off diagonal is a transformer
                err, realaro = psspy.two_winding_data(ibus, jbus, machid,[1,ibus,1,0,0,0,33,0,ibus,0,1,0,1,1,1],[gen_r_temp[k], gen_x_temp[k], basemva, 1.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0, 1.0, 1.0, 1.0, 1.0,0.0,0.0, 1.1, 0.9, 1.1, 0.9,0.0,0.0],"")    
                s = " ****** Equivalent transformer added between busses: %d and %d"
                s += " - Error Status = %d."
                print s % (ibus, jbus, err)

# Add  diagonal sources at the boundary busses:
k = -1
for i in range(0, nodes):
    k += 1
    ibus = ibus_temp[i]
    # extracting the interface point bus voltage:
    err, vpu = psspy.busdat(ibus, 'PU')

    # calculating the bus mismatch:
    err, mismatch = psspy.busmsm(ibus)

    # if the case is <= 31, bus code is set to 2 at the time of deleting the
    # bus shunts:
    if PSSE_VER > 31:
        # set the TYPE of the interface bus to 2 (i.e a generator bus):
        psspy.bus_data(ibus, intgar1=2)

    # add the plant:
    psspy.plant_data(ibus, 0, (1.0, 100.0))

    gen_z = complex(gen_r_temp[k], gen_x_temp[k])
    basemva = locals()['BMVA']
    err = 99

    if PSSE_VER <= 30:

        if gen_r_temp[k] < 0.0 or gen_x_temp[k] < 0.0:
            # add shunt R and X to ground:
            y = 1.0 / gen_z
            scalc = y * basemva

            err = psspy.bus_data(ibus,[_i,_i,_i,_i],[scalc.real, scalc.imag,_f,_f,_f],"")

            s = " ****** Equivalent Shunt added at bus: %d, Error Status = %d."
            print s % (ibus, err)

            # Calculate source PQ flow:
            sload = scalc.conjugate() * vpu ** 2
            scalc = -mismatch + sload

            # add 999 ZSource:
            err = psspy.machine_data(ibus, locals()['genid'], [1, 1, 0, 0, 0],
                                   [scalc.real, scalc.imag, scalc.imag,
                                    scalc.imag, 9999.0, -9999.0, basemva,
                                    0.0, 999.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0,
                                    1.0])

            s = " ****** Equivalent Machine added at bus: %d, Error Status = %d."
            print s % (ibus, err)

        else:  # r > 0 and x > 0):

            if abs(gen_z) > 1.0E-10:

                # add ZSource:
                err = psspy.machine_data(ibus, locals()['genid'], [1, 1, 0, 0, 0],
                                       [-mismatch.real, -mismatch.imag,
                                        -mismatch.imag, -mismatch.imag,
                                        9999.0, -9999.0, basemva,
                                        gen_r_temp[k], gen_x_temp[k],
                                        0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0])

            else:  # gen_z < 1E-10:
                # add 999 ZSource:
                err = psspy.machine_data(ibus, locals()['genid'], [1, 1, 0, 0, 0],
                                       [-mismatch.real, -mismatch.imag,
                                        -mismatch.imag, -mismatch.imag,
                                        9999.0, -9999.0, basemva, 0.0, 999.0,
                                        0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0])

            s = " ****** Equivalent Machine added at bus: %d, Error Status = %d."
            print s % (ibus, err)

    else:  # PSSE version 31+

        if gen_r_temp[k] < 0.0 or gen_x_temp[k] < 0.0:
            # add shunt R and X to ground:
            y = 1.0 / gen_z
            scalc = y * basemva
            err = psspy.shunt_data(ibus, locals()['genid'], 1, [scalc.real, scalc.imag])

            s = " ****** Equivalent Bus Shunt added at bus: %d, Error Status = %d."
            print s % (ibus, err)

            # Calculate source PQ flow:
            sload = scalc.conjugate() * vpu ** 2
            scalc = -mismatch + sload

            # add 999 ZSource:
            err = psspy.machine_data_2(ibus, locals()['genid'], [1, 1, 0, 0, 0, 0],
                                     [scalc.real, scalc.imag, scalc.imag,
                                      scalc.imag, 9999.0, -9999.0, basemva,
                                      0.0, 999.0, 0.0, 0.0, 1.0, 1.0, 1.0,
                                      1.0, 1.0, 1.0])

            s = " ****** Equivalent Machine added at bus: %d, Error Status = %d."
            print s % (ibus, err)

        else:  # r > 0 and x > 0):

            if abs(gen_z) > 1.0E-10:
                # add ZSource:
                err = psspy.machine_data_2(ibus, locals()['genid'], [1, 1, 0, 0, 0, 0],
                                         [-mismatch.real, -mismatch.imag,
                                          -mismatch.imag, -mismatch.imag,
                                          9999.0, -9999.0, basemva,
                                          gen_r_temp[k], gen_x_temp[k],
                                          0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0,
                                          1.0])

            else:  # gen_z < 1E-10:

                # add 999 ZSource
                err = psspy.machine_data_2(ibus, locals()['genid'], [1, 1, 0, 0, 0, 0],
                                         [-mismatch.real, -mismatch.imag,
                                          -mismatch.imag, -mismatch.imag,
                                          9999.0, -9999.0, basemva,
                                          0.0, 999.0, 0.0, 0.0, 1.0,
                                          1.0, 1.0, 1.0, 1.0, 1.0])

            s = " ****** Equivalent Machine added at bus: %d, Error Status = %d."
            print s % (ibus, err)

    psspy.plant_data(ibus, _i, [vpu, _f])

    # increment k:
    for j in range(i + 1, nodes):
        k = k + 1

# save the interface added power flow case:
psspy.save(hybrid_sav)

# At this point islands must be checked. An island detected means the original type 3 
# bus/es are delted from the PSSE case. If that is the case, then make one interface bus  
# to be type 3. This ensures the _hybird case can be subsiquently solved using power flow solution algorithms.
# However, adding a trpe 3 bus is not essential, as _hybird is prepared for PSSE dynamics (i.e activity
# CONG would change this back to type 2 bus prior to dynamic simulation. 

islands = psspy.chktre()
if islands > 0:
    print " Island detected....One of the interface bus with generator is converted to Type 3...."
    psspy.alert_output(6,"",[0,0])  # supress the warning messages temporary
    psspy.bus_data(ibus_temp[0], intgar1=3)
    psspy.lines_per_page_one_device(2,10000000)
    psspy.alert_output(1,"",[0,0])
    psspy.save(hybrid_sav)

# At this point there cannot be any inlands, since a multiport network is modeled at the boundary buses.

# This time the power flow should converge in the same number of iterations

if best_attempt < 3:
    psspy.fdns(settings[best_attempt])
else:
    psspy.fnsl(settings[best_attempt])

itr = psspy.iterat()  # checking the number of iterations
itr_best = itr_best + 1 # add one iteration 
if itr > itr_best:
    print " Power flow case did not converge in the same or lower number of iterations....Please check"

psspy.save(hybrid_sav)

# Write dynamics records for the Hybrid interface (i.e adding ETRCOM, INITAD,
# CHOUT models) The content is written to a temporary file as specified in
# Hybrid_dyr

# this is the dyre string we will build up:

dyrstrPrefix = " / E-Tran Hybrid (PSSE-PSCAD Co-Simulation) models have been added to the end of this file\n\n"

# maintain list of existing USRMSC ids
usrmscIds = set()

dyrstr = ""

commentingOutRecord = False

commentedOutModels = []

# Read in the existing DYR file and
if not (os.path.isfile(locals()['dyr_path'])):
    print " The .dyr file " + locals()['dyr_path'] + " does not exist.\n"
    print " The script will now exit.\n"
    RedirectOutputToPSSE()
    print "\n The .dyr file " + locals()['dyr_path'] + " does not exist.\n"
    print " The script will now exit.\n"
    raise SystemExit()
    
else:
    f = open(locals()['dyr_path'])
    for line in f:
        # build up the hybrid DYR
        if not commentingOutRecord:
            # keep track of all the USRMSC values
            if line.upper().find("USRMSC") >= 0:
                # use whitespace and commas as delimiters, and throw away any leading empty string
                lineSplit = filter(None, re.split("[\s,]+", line))
                if lineSplit[0].isdigit():                
                    usrmscIds.add(int(lineSplit[0]))
            # if there is a model with ckt id of HB matching one of the generator busses, comment it out to prevent issues
            if line.upper().find("HB") >= 0:
                # use whitespace and commas as delimiters, and throw away any leading empty string
                lineSplit = filter(None, re.split("[\s,]+", line))
                if len(lineSplit) >= 3 and lineSplit[0].isdigit() and lineSplit[2].upper().find("HB") >= 0:
                    if int(lineSplit[0]) in gen_buses:
                        commentingOutRecord = True
                        commentedOutModels.append(lineSplit[0] + " " + lineSplit[1] + " " + lineSplit[2])
        
        if commentingOutRecord:
            dyrstr += "/ " + line
        else:            
            dyrstr += line
            
        if line.find("/") >= 0:
            commentingOutRecord = False
    f.close()


if gen_buses:
    dyrstrPrefix += " / The following pre-existing generator models with machine ID 'HB' have been commented out\n"
    dyrstrPrefix += " / in this DYR file to prevent conflicts with the models added for the hybrid simulation:\n"
    for model in commentedOutModels:
        dyrstrPrefix += " / " + model + " ...\n"
    dyrstrPrefix += "\n"
                
dyrstr = dyrstrPrefix + dyrstr

inout_cnt = 0

if locals()['add_chout']:
    inout_cnt += 1

if locals()['add_chin']:
    inout_cnt += 1

inout_cnt += len_boundary

minsVal = 1

if locals()['new_dyr_format']:
    while minsVal in usrmscIds:
        minsVal += 1
    dyrstr += str(minsVal) + ", 'USRMSC', 'EINIT', 512, 0, 7, 0, 0, 0, "
    dyrstr += " 1" + ", "
    usrmscIds.add(minsVal)
    minsVal += 1
else:
    dyrstr += "0, 'USRMDL', 0, 'EINIT', 8, 0, 7, 0, 0, 0, "
    dyrstr += " 1" + ", "

ip_addr = locals()['ip_addr']
port = locals()['start_port']
dyrstr += str(port) + ", " + str(port + inout_cnt - 1) + ", " + ip_addr + " / \n\n"
      

# adding the ETRCOM model at each interface point:

port = locals()['start_port']

for ibus in locals()['boundary_buses']:
    dyrstr += str(ibus) + ", " + "'USRMDL', HB, 'ETRCOM', 1, 1, 5, 0, 0, 20, "
    dyrstr += str(port) + ", 0, 0, 0, 0 / \n\n"
    port += 1

if locals()['add_chout']:
    if locals()['new_dyr_format'] == 0:
        dyrstr += "0, 'USRMDL', 0, 'CHOUT', 0, 2, 4, 0, 0, 10, "
        dyrstr += str(port)
        dyrstr += ", 0, 0, 0 / \n\n"
    else:
        while minsVal in usrmscIds:
            minsVal += 1
        dyrstr += str(minsVal) + ", 'USRMSC', 'CHOUT', 512, 2, 4, 0, 0, 10, "
        dyrstr += str(port)
        dyrstr += ", 0, 0, 0 / \n\n"
        usrmscIds.add(minsVal)
        minsVal += 1
    port += 1

if locals()['add_chin']:
    if locals()['new_dyr_format']:
        while minsVal in usrmscIds:
            minsVal += 1
        dyrstr += str(minsVal) + ", 'USRMSC', 'CHIN', 512, 0, 4, 0, 0, 10, "
        dyrstr += str(port)
        dyrstr += ", 0, 0, 0 / \n\n"
        usrmscIds.add(minsVal)
        minsVal += 1
    else:
        dyrstr += "0, 'USRMDL', 0, 'CHIN', 8, 0, 4, 0, 0, 10" + ", "
        dyrstr += str(port)
        dyrstr += ", 0, 0, 0 / \n\n"

# dump dyre string to file:
dyrfile = open(hybrid_dyr, 'w')
dyrfile.write(dyrstr)
dyrfile.close()

RedirectOutputToPSSE()

print " Hybrid SAV file written to " + hybrid_sav
print " Hybrid DYR file written to " + hybrid_dyr

print " DONE"



