'''WISDEM Monopile API'''

__author__ = ['Jake Nunemaker']
__copyright__ = 'Copyright 2020, National Renewable Energy Laboratory'
__maintainer__ = 'Jake Nunemaker'
__email__ = 'jake.nunemaker@nrel.gov'


import openmdao.api as om

from ORBIT import ProjectManager

class Orbit(om.Group):
    def initialize(self):
        self.options.declare('floating', default=False)
        
    def setup(self):
        
        # Define all input variables from all models
        self.set_input_defaults('wtiv', 'example_wtiv')
        self.set_input_defaults('feeder', 'example_feeder')
        self.set_input_defaults('num_feeders', 1)
        self.set_input_defaults('num_towing', 1)
        self.set_input_defaults('num_station_keeping', 3)
        self.set_input_defaults('oss_install_vessel', 'example_heavy_lift_vessel')
        self.set_input_defaults('site_distance', 40.0, units='km')
        self.set_input_defaults('site_distance_to_landfall', 40.0, units='km')
        self.set_input_defaults('interconnection_distance', 40.0, units='km')
        self.set_input_defaults('plant_turbine_spacing', 7)
        self.set_input_defaults('plant_row_spacing', 7)
        self.set_input_defaults('plant_substation_distance', 1, units='km')
        self.set_input_defaults('num_port_cranes', 1)
        self.set_input_defaults('num_assembly_lines', 1)
        self.set_input_defaults('takt_time', 170.0, units='h')
        self.set_input_defaults('port_cost_per_month', 2e6, units='USD/mo')
        self.set_input_defaults('commissioning_pct', 0.01)
        self.set_input_defaults('decommissioning_pct', 0.15)
        self.set_input_defaults('site_auction_price', 100e6, units='USD')
        self.set_input_defaults('site_assessment_plan_cost', 1e6, units='USD')
        self.set_input_defaults('site_assessment_cost', 25e6, units='USD')
        self.set_input_defaults('construction_operations_plan_cost', 2.5e6, units='USD')
        self.set_input_defaults('design_install_plan_cost', 2.5e6, units='USD')        
        self.set_input_defaults('boem_review_cost', 0.0, units='USD')        
        
        self.add_subsystem('orbit', OrbitWisdem(floating=self.options['floating']), promotes=['*'])
        

class OrbitWisdem(om.ExplicitComponent):
    '''ORBIT-WISDEM Fixed Substructure API'''
    def initialize(self):
        self.options.declare('floating', default=False)

    def setup(self):
        ''''''
        # Inputs
        # self.add_discrete_input('weather_file', 'block_island', desc='Weather file to use for installation times.')

        # Vessels
        self.add_discrete_input('wtiv', 'example_wtiv', desc='Vessel configuration to use for installation of foundations and turbines.')
        self.add_discrete_input('feeder', 'future_feeder', desc='Vessel configuration to use for (optional) feeder barges.')
        self.add_discrete_input('num_feeders', 1, desc='Number of feeder barges to use for installation of foundations and turbines.')
        self.add_discrete_input('num_towing', 1, desc='Number of towing vessels to use for floating platforms that are assembled at port (with or without the turbine).')
        self.add_discrete_input('num_station_keeping', 3, desc='Number of station keeping vessels that attach to floating platforms under tow-out.')
        self.add_discrete_input('oss_install_vessel', 'example_heavy_lift_vessel', desc='Vessel configuration to use for installation of offshore substations.')

        # Site
        self.add_input('site_depth', 40., units='m', desc='Site depth.')
        self.add_input('site_distance', 40., units='km', desc='Distance from site to installation port.')
        self.add_input('site_distance_to_landfall', 50., units='km', desc='Distance from site to landfall for export cable.')
        self.add_input('interconnection_distance', 3., units='km', desc='Distance from landfall to interconnection.')
        self.add_input('site_mean_windspeed', 9., units='m/s', desc='Mean windspeed of the site.')

        # Plant
        self.add_discrete_input('number_of_turbines', 60, desc='Number of turbines.')
        self.add_input('plant_turbine_spacing', 7, desc='Turbine spacing in rotor diameters.')
        self.add_input('plant_row_spacing', 7, desc='Row spacing in rotor diameters. Not used in ring layouts.')
        self.add_input('plant_substation_distance', 1, units='km', desc='Distance from first turbine in string to substation.')

        # Turbine
        self.add_input('turbine_rating', 8., units='MW', desc='Rated capacity of a turbine.')
        self.add_input('turbine_rated_windspeed', 11., units='m/s', desc='Rated windspeed of the turbine.')
        self.add_input('turbine_capex', 1100, units='USD/kW', desc='Turbine CAPEX')
        self.add_input('hub_height', 100., units='m', desc='Turbine hub height.')
        self.add_input('turbine_rotor_diameter', 130, units='m', desc='Turbine rotor diameter.')
        self.add_input('tower_mass', 400., units='t', desc='mass of the total tower.')
        self.add_input('tower_length', 100., units='m', desc='Total length of the tower.')
        self.add_input('tower_deck_space', 0., units='m**2', desc='Deck space required to transport the tower. Defaults to 0 in order to not be a constraint on installation.')
        self.add_input('nacelle_mass', 500., units='t', desc='mass of the rotor nacelle assembly (RNA).')
        self.add_input('nacelle_deck_space', 0., units='m**2', desc='Deck space required to transport the rotor nacelle assembly (RNA). Defaults to 0 in order to not be a constraint on installation.')
        self.add_discrete_input('number_of_blades', 3, desc='Number of blades per turbine.')
        self.add_input('blade_mass', 50., units='t', desc='mass of an individual blade.')
        self.add_input('blade_deck_space', 0., units='m**2', desc='Deck space required to transport a blade. Defaults to 0 in order to not be a constraint on installation.')

        # Mooring
        self.add_discrete_input('num_mooring_lines', 3, desc='Number of mooring lines per platform.')
        self.add_input('mooring_line_mass', 1e4, units='kg', desc='Total mass of a mooring line')
        self.add_input('mooring_line_diameter', 0.1, units='m', desc='Cross-sectional diameter of a mooring line')
        self.add_input('mooring_line_length', 1e3, units='m', desc='Unstretched mooring line length')
        self.add_input('anchor_mass', 1e4, units='kg', desc='Total mass of an anchor')
        self.add_discrete_input('anchor_type', 'drag_embedment', desc='Number of mooring lines per platform.')
        
        # Port
        self.add_input('port_cost_per_month', 2e6, units='USD/mo', desc='Monthly port costs.')
        self.add_input('takt_time', 170.0, units='h', desc='Substructure assembly cycle time when doing assembly at the port.')
        self.add_discrete_input('num_assembly_lines', 1, desc='Number of assembly lines used when assembly occurs at the port.')
        self.add_discrete_input('num_port_cranes', 1, desc='Number of cranes used at the port to load feeders / WTIVS when assembly occurs on-site or assembly cranes when assembling at port.')

        # Monopile
        self.add_input('monopile_length', 100., units='m', desc='Length of monopile.')
        self.add_input('monopile_diameter', 7., units='m', desc='Diameter of monopile.')
        self.add_input('monopile_mass', 900., units='t', desc='mass of an individual monopile.')
        self.add_input('monopile_deck_space', 0., units='m**2', desc='Deck space required to transport a monopile. Defaults to 0 in order to not be a constraint on installation.')
        self.add_input('transition_piece_mass', 250., units='t', desc='mass of an individual transition piece.')
        self.add_input('transition_piece_deck_space', 0., units='m**2', desc='Deck space required to transport a transition piece. Defaults to 0 in order to not be a constraint on installation.')

        # Project
        self.add_input('site_auction_price', 100e6, units='USD', desc='Cost to secure site lease')
        self.add_input('site_assessment_plan_cost', 1e6, units='USD', desc='Cost to do engineering plan for site assessment')
        self.add_input('site_assessment_cost', 25e6, units='USD', desc='Cost to execute site assessment')
        self.add_input('construction_operations_plan_cost', 2.5e6, units='USD', desc='Cost to do construction planning')
        self.add_input('boem_review_cost', 0.0, units='USD', desc='Cost for additional review by U.S. Dept of Interior Bureau of Ocean Energy Management (BOEM)')
        self.add_input('design_install_plan_cost', 2.5e6, units='USD', desc='Cost to do installation planning')

        # Other
        self.add_input('commissioning_pct', 0.01, desc='Commissioning percent.')
        self.add_input('decommissioning_pct', 0.15, desc='Decommissioning percent.')

        # Outputs
        # Totals
        self.add_output('bos_capex', 0.0, units='USD', desc='Total BOS CAPEX not including commissioning or decommissioning.')
        self.add_output('total_capex', 0.0, units='USD', desc='Total BOS CAPEX including commissioning and decommissioning.')
        self.add_output('total_capex_kW', 0.0, units='USD/kW', desc='Total BOS CAPEX including commissioning and decommissioning.')
        self.add_output('installation_time', 0.0, units='h', desc='Total balance of system installation time.')
        self.add_output('installation_capex', 0.0, units='USD', desc='Total balance of system installation cost.')


    def compile_orbit_config_file(self, inputs, outputs, discrete_inputs, discrete_outputs):
        ''''''

        floating = self.options['floating']
            
        config = {
            # Vessels
            'wtiv': discrete_inputs['wtiv'],
            'array_cable_install_vessel': 'example_cable_lay_vessel',
            'array_cable_bury_vessel': 'example_cable_lay_vessel',
            'export_cable_install_vessel': 'example_cable_lay_vessel',
            'export_cable_bury_vessel': 'example_cable_lay_vessel',
            
            # Site/plant
            'site': {
                'depth': float(inputs['site_depth']),
                'distance': float(inputs['site_distance']),
                'distance_to_landfall': float(inputs['site_distance_to_landfall']),
                'mean_windspeed': float(inputs['site_mean_windspeed'])
            },

            'landfall': {
                'interconnection_distance': float(inputs['interconnection_distance']),
            },
            
            'plant': {
                'layout': 'grid',
                'num_turbines': int(discrete_inputs['number_of_turbines']),
                'row_spacing': float(inputs['plant_row_spacing']),
                'turbine_spacing': float(inputs['plant_turbine_spacing']),
                'substation_distance': float(inputs['plant_substation_distance'])
            },
            
            # Turbine + components
            'turbine': {
                'hub_height': float(inputs['hub_height']),
                'rotor_diameter': float(inputs['turbine_rotor_diameter']),
                'turbine_rating': float(inputs['turbine_rating']),
                'rated_windspeed': float(inputs['turbine_rated_windspeed']),
                'tower': {
                    'type': 'Tower',
                    'deck_space': float(inputs['tower_deck_space']),
                    'mass': float(inputs['tower_mass']),
                    'length': float(inputs['tower_length'])
                },
                
                'nacelle': {
                    'type': 'Nacelle',
                    'deck_space': float(inputs['nacelle_deck_space']),
                    'mass': float(inputs['nacelle_mass'])
                },
                
                'blade': {
                    'type': 'Blade',
                    'number': int(discrete_inputs['number_of_blades']),
                    'deck_space': float(inputs['blade_deck_space']),
                    'mass': float(inputs['blade_mass'])
                }
            },

            # Substructure components
            'transition_piece': {
                'type': 'Transition Piece',
                'deck_space': float(inputs['transition_piece_deck_space']),
                'mass': float(inputs['transition_piece_mass'])
            },
            
            # Electrical
            'array_system_design': {
                'cables': ['XLPE_630mm_66kV', 'XLPE_185mm_66kV'],
            },

            'export_system_design': {
                'cables': 'XLPE_1000m_220kV',
                'interconnection_distance': float(inputs['interconnection_distance']),
                'percent_added_length': .1
            },
            
            # Phase Specific
            'OffshoreSubstationInstallation': {
                'oss_install_vessel': 'example_heavy_lift_vessel',
                'feeder': 'future_feeder',
                'num_feeders': int(discrete_inputs['num_feeders']),
            },

            # Project development costs
            'project_development': {
                'site_auction_price': float(inputs['site_auction_price']), #100e6,
                'site_assessment_plan_cost': float(inputs['site_assessment_plan_cost']), #1e6,
                'site_assessment_cost': float(inputs['site_assessment_cost']), #25e6,
                'construction_operations_plan_cost': float(inputs['construction_operations_plan_cost']), #2.5e6,
                'boem_review_cost': float(inputs['boem_review_cost']), #0,
                'design_install_plan_cost': float(inputs['design_install_plan_cost']), #2.5e6
            },

            # Other
            'commissioning': float(inputs['commissioning_pct']),
            'decomissioning': float(inputs['decommissioning_pct']),
            'turbine_capex': float(inputs['turbine_capex']),
            
            # Phases
            # Putting monopile or semisub here would override the inputs we assume to get from WISDEM
            'design_phases': [
                'ProjectDevelopment',
                #'MonopileDesign',
                #'SemiSubmersibleDesign',
                #'MooringSystemDesign',
                #'ScourProtectionDesign',
                'ArraySystemDesign',
                'ExportSystemDesign',
                'OffshoreSubstationDesign'
            ],
        }

        # Unique design phases
        if floating:
            config['install_phases'] = {
                'ExportCableInstallation': 0,
                'OffshoreSubstationInstallation': 0,
                'MooringSystemInstallation': 0,
                'MooredSubInstallation': ('MooringSystemInstallation', 0.25),
                'ArrayCableInstallation': ('MooredSubInstallation', 0.25),
            }
        else:
            config['design_phases'] += ['ScourProtectionDesign']
            config['install_phases'] = {
                'ExportCableInstallation': 0,
                'OffshoreSubstationInstallation': 0,
                'ArrayCableInstallation': 0,
                'MonopileInstallation': 0,
                'ScourProtectionInstallation': 0,
                'TurbineInstallation': ('MonopileInstallation', 0.25),
            }
            
        # Unique vessels
        if floating:
            vessels = {
                'support_vessel': 'example_support_vessel',
                'towing_vessel': 'example_towing_vessel',
                'mooring_install_vessel': 'example_support_vessel',
                
                'towing_vessel_groups': {
                    'towing_vessels': int(discrete_inputs['num_towing']),
                    'station_keeping_vessels': int(discrete_inputs['num_station_keeping']),
                },
            }
        else:
            vessels = {
                'feeder': discrete_inputs['feeder'],
                'num_feeders': int(discrete_inputs['num_feeders']),
                'spi_vessel': 'example_scour_protection_vessel',
            }
        config.update( vessels )

        # Unique support structure design/assembly
        if floating:
            config['port'] = {
                'sub_assembly_lines': int(discrete_inputs['num_assembly_lines']),
                'turbine_assembly_cranes': int(discrete_inputs['num_port_cranes']),
                'monthly_rate': float(inputs['port_cost_per_month'])
            }
                
            config['substructure'] = {
                'takt_time': float(inputs['takt_time']),
                'towing_speed': 6  # km/h
            }

            anchorstr_in = discrete_inputs['anchor_type'].lower()
            if anchorstr_in.find('drag') >= 0:
                anchorstr = 'Drag Embedment'
            elif anchorstr_in.find('suction') >= 0:
                anchorstr = 'Suction Pile'
                
            config['mooring_system'] = {
                'num_lines': int(discrete_inputs['num_mooring_lines']),
                'line_mass': 1e-3*float(inputs['mooring_line_mass']),
                'line_diam': float(inputs['mooring_line_diameter']),
                'line_length': float(inputs['mooring_line_length']),
                'anchor_mass': 1e-3*float(inputs['anchor_mass']),
                'anchor_type': anchorstr,
            }
        else:
            config['port'] = {
                'num_cranes': int(discrete_inputs['num_port_cranes']),
                'monthly_rate': float(inputs['port_cost_per_month'])
            }
                
            config['monopile'] = {
                'type': 'Monopile',
                'length': float(inputs['monopile_length']),
                'diameter': float(inputs['monopile_diameter']),
                'deck_space': float(inputs['monopile_deck_space']),
                'mass': float(inputs['monopile_mass']),
            }
                
            config['scour_protection_design'] = {
                'cost_per_tonne': 20,
            }
        
            
        self._orbit_config = config
        return config

    def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
        
        config = self.compile_orbit_config_file(inputs, outputs,
                                                discrete_inputs,
                                                discrete_outputs)

        project = ProjectManager(config)
        project.run_project()

        outputs['bos_capex'] = project.bos_capex
        outputs['total_capex'] = project.total_capex
        outputs['total_capex_kW'] = project.total_capex_per_kw
        outputs['installation_time'] = project.installation_time
        outputs['installation_capex'] = project.installation_capex
