#!/usr/bin/env python3

from os import path
import sys
import math

N_ = lambda text: text


script_dir = path.join(path.dirname(__file__), '..', '..', '..', 'utils', 'outfits')
sys.path.append(path.realpath(script_dir))
from outfit import outfit

def get_outfit_dict( nam, core= False, doubled= False ):
   nam = path.realpath(path.join(path.dirname( __file__ ), '..', nam))
   o = outfit(nam, is_multi= core, w= False)
   o.stack(o if doubled else None)
   out = o.to_dict()
   out['size'] = o.size_name()
   return out

# Based on some XML templates and the specs below, we're going to generate families of outfit XML files.
# First things first: what are we supposed to be doing?

class Build:
   ''' By default, build everything in-tree. '''

   def read_template( self, name ):
      return open(f'templates/{name}').read()

   def file_names( self, names ):
      return [f"{n.lower().replace(' ','_')}.xml" for n in names]

   def open_output( self, names ):
      return (open(fn, 'w') for fn in self.file_names(names))

class MesonBuild(Build):
   ''' If Meson invokes generate.py <template path> -o <output paths>, build only the outfit family that matches the command. '''

   def __init__( self, template_path, _, *output_paths ):
      self.template_path = template_path
      self.output_paths = output_paths
      self.template_name = path.basename(template_path)
      self.output_names = [path.basename(p) for p in output_paths]

   def read_template( self, name ):
      if name == self.template_name:
         return open(self.template_path).read()

   def open_output( self, names ):
      if self.file_names(names) == self.output_names:
         for fn in self.output_paths:
            yield open(fn, 'w')

build = MesonBuild(*sys.argv[1:]) if sys.argv[2:3] == ['-o'] else Build()

intify = lambda x: int(x) if round(x) == x else x
# Hopefully we can refactor this once the behaviour is finalized and we're unafraid of merge conflicts. Anyway, back to the show.

def lerpt( t ):
   return lambda x: t[int(math.floor(x*(len(t)*0.99999)))]

def lerp( a, b, ta = 0.0, tb = 1.0 ):
   return lambda x: intify(a + (x-ta) * (b-a) / (tb-ta))

def lerpr( a, b, ta = 0.0, tb = 1.0 ):
   def f( x ):
      res = int(round(lerp(a, b, ta, tb)(x)))
      return 0 if res < 0 else res;
   return f

from math import log, exp

# Does geometric interpolation instead of arithmetic interpolation.
def eerp( a, b, ta = 0.0, tb = 1.0 ):
   return lambda x: intify(round(exp(lerp(log(a), log(b), ta, tb)(x)), 2))

def eerpr( a, b, ta = 0.0, tb = 1.0 ):
   return lambda x: int(round(eerp(a, b, ta, tb)(x)))

class BioOutfit:
   def __init__( self, template, params ):
      self.template = template
      self.params = params
      self.txt = build.read_template(template)

   def run( self, names ):
      files = build.open_output(names)
      for k, (n, f) in enumerate(zip(names, files)):
         x = 1 if len(names) == 1 else k/(len(names)-1)
         p = {k: v(x) if callable(v) else v for k, v in self.params.items()}
         p['name']  = n
         with f:
            txt = self.txt.format(**p)
            txt = txt.replace( '<general>', '<!-- THIS OUTFIT IS AUTOGENERATED. DO NOT EDIT DIRECTLY! -->\n <general>' )
            f.write( txt )

desc = {}
desc['brain'] = N_("The brain is a Soromid bioship's equivalent to core systems in synthetic ships. Possibly the most important organ, the brain provides processing power and allocates energy to the rest of the organism. All brains start off undeveloped, but over time, just like the ships themselves, they grow and improve.")
desc['engine'] = N_("The gene drive is a Soromid bioship's equivalent to engines in synthetic ships. It is charged with moving the organism through space and is even capable of hyperspace travel. All gene drives start off undeveloped, but over time, just like the ships themselves, they grow and improve.")
desc['hull'] = N_("The shell is a Soromid bioship's natural protection, equivalent to hulls of synthetic ships. The shell is responsible both for protecting the organism's internal organs and for creating camouflage to reduce the risk of being detected by hostiles. All shells start off undeveloped, but over time, just like the ships themselves, they grow and improve.")

typename = {}
typename['brain'] = N_("Bioship Brain")
typename['engine'] = N_("Bioship Gene Drive")
typename['hull'] = N_("Bioship Shell")


## BioOutfit generation rules.
## See comments in this directory's meson.build file: these rules must match the build rules.


## Gene Drive recipe:
## Corresponds (when maxed) to some existing engine <ref> mentionned in the comment.
##    "price":        lerpr( <ref>/2.0, <ref> ),
##    "accel":        lerp(  <ref>-15%, <ref> ),
##    "turn":         lerp(  <ref>-15%, <ref> ),
##    "speed":        lerp(  <ref>-15%, <ref> ),

## Cortex recipe:
##    "price":        lerpr(     <S&K>/2, <S&K>),
##    "absorb":       lerpr( <Unicorp>-3, <S&K>-3 ),
##    "armour":       lerp(    <Unicorp>, <S&K> )
##    "cargo":        lerpr(       <S&K>, (<S&K>+<Unicorp>)/2 ),
##    "mass":         <S&K>,
##    "armour":       lerp(    <Unicorp>, <S&K> )

## Cerebrum recipe part 1 (small):
##    "price":        lerpr(  <orion>, <orion>*1.5 ),
##    "mass":         lerpr(  <orion>, <orion>*1.25 ),
##    "shield" :      lerp(   <orion>, <orion>*1.25 ),
##    "shield_regen": lerp(   <orion>, <orion>*1.25 ),

## Cerebrum recipe part 1 (medium/large):
##    "price":        lerpr(  <orion>, <orion>+1/2*(<shield_booster>+<shield capacitor>)),
##    "mass":         lerpr(  <orion>, <orion>+1/2*(<shield_booster>+<shield capacitor>)),
##    "shield":       lerp(   <orion>, <orion>+1/2*<shield capacitor> ),
##    "shield_regen": lerp(   <orion>, <orion>+1/2*<shield_booster> ),

## Cerebrum recipe part 2:
##    "energy" :      lerp(   <orion>, <orion>*1.25 ),
##    "energy_regen": lerp(   <orion>, <orion>*1.25 ),
##    "cpu":          handmade ! (because builtin weapons have no CPU requirements)

# Perlevis Gene Drive   =>  Tricon Zephyr
# Laeviter Gene Drive   =>  Tricon Zephyr x2
# Laevis Gene Drive     =>  Melendez Ox x2
# Mediocris Gene Drive  =>  Tricon Cyclone
# Largus Gene Drive     =>  Tricon Cyclone x2
# Ponderosus Gene Drive =>  Tricon Typhoon
# Grandis Gene Drive    =>  Melendez Mammoth
# Magnus Gene Drive     =>  Tricon Typhoon x2
# Immanis Gene Drive    =>  Nexus Bolt 3000 x2
for siz, nam, db, temp, gfx, output_pref, outputs in [
   ('small', 'tricon_zephyr_engine.xml',     False,   'gene_drive_tricon',    'fast_s',   'Perlevis',    ['I', 'II']          ),
   ('small', 'tricon_zephyr_engine.xml',     True,    'gene_drive_tricon',    'fast_s',   'Laeviter',    ['I', 'II']          ),
   ('small', 'melendez_ox_engine.xml',       True,    'gene_drive_melendez',  'strong_s', 'Laevis',      ['I', 'II']          ),
   ('medium', 'tricon_cyclone_engine.xml',   False,   'gene_drive_tricon',    'fast_m',   'Mediocris',   ['I', 'II', 'III']   ),
   ('medium', 'tricon_cyclone_engine.xml',   True,    'gene_drive_tricon',    'strong_m', 'Largus',      ['I', 'II']          ),
   ('large', 'tricon_typhoon_engine.xml',    False,   'gene_drive_tricon',    'fast_l',   'Ponderosus',  ['I', 'II', 'III']   ),
   ('large', 'melendez_mammoth_engine.xml',  False,   'gene_drive_melendez',  'strong_l', 'Grandis',     ['I', 'II', 'III']   ),
   ('large', 'tricon_typhoon_engine.xml',    True,    'gene_drive_tricon',    'strong_l', 'Magnus',      ['I', 'II', 'III']   ),
   ('large', 'nexus_bolt_3000_engine.xml',   True,    'gene_drive',           'strong_l', 'Immanis',     ['I', 'II', 'III']   ),
]:
   ref = get_outfit_dict(path.join('core_engine', siz, nam), True, db)
   BioOutfit( temp+'.xml.template', {
      'typename':     typename['engine'],
      'size':         ref['size'],
      'price':        lerpr(  ref['price']/2, ref['price'] ),
      'mass':         ref['mass'],
      'desc':         desc['engine'],
      'gfx_store':    lerpt(('organic_engine_'+gfx+'1', 'organic_engine_'+gfx+'2')),
      'accel':        lerpr(  0.85*ref['accel'], ref['accel'] ),
      'speed':        lerpr(  0.85*ref['speed'], ref['speed'] ),
      'turn':         lerpr(  0.85*ref['turn'], ref['turn'] ),
      'fuel':         ref['fuel'],
      'energy_malus': ref['energy_regen_malus'],
      'engine_limit': ref['engine_limit']
   } ).run( [ N_(output_pref+' Gene Drive '+s) for s in outputs ] )


##
# Cortex
#

# Perlevis Cortex    =>  (1) Unicorp_d2      (2) S&K Skirmish Plating
# Laevis Cortex      =>  (1) Unicorp_d2 x2   (2) S&K Skirmish Plating x2
# Mediocris Cortex   =>  (1) Unicorp_d23     (2) S&K Battle Plating
# Largus Cortex      =>  (1) Unicorp_d23 x2  (2) S&K Battle Plating x2
# Ponderosus Cortex  =>  (1) Unicorp_d58     (2) S&K War Plating
# Immanis Cortex     =>  (1) Unicorp_d58 x2  (2) S&K War Plating x2
for pref, nam1, nam2, dbl, gfx, output_pref, outputs in [
   ('small',  'unicorp_d2_light_plating.xml',   'sk_skirmish_plating.xml', False, 't', 'Perlevis',   ['I', 'II']              ),
   ('small',  'unicorp_d2_light_plating.xml',   'sk_skirmish_plating.xml', True,  's', 'Laevis',     ['I', 'II']              ),
   ('medium', 'unicorp_d23_medium_plating.xml', 'sk_battle_plating.xml',   False, 'm', 'Mediocris',  ['I', 'II']              ),
   ('medium', 'unicorp_d23_medium_plating.xml', 'sk_battle_plating.xml',   True,  'l', 'Largus',     ['I', 'II', 'III']       ),
   ('large',  'unicorp_d58_heavy_plating.xml',  'sk_war_plating.xml',      False, 'h', 'Ponderosus', ['I', 'II', 'III', 'IV'] ),
   ('large',  'unicorp_d58_heavy_plating.xml',  'sk_war_plating.xml',      True,  'x', 'Immanis',    ['I', 'II', 'III']       ),
]:
   ref1 = get_outfit_dict(path.join('core_hull', pref, nam1), True, dbl)
   ref2 = get_outfit_dict(path.join('core_hull', pref, nam2), True, dbl)
   BioOutfit( 'cortex.xml.template', {
      'typename':     typename['hull'],
      'size':         pref,
      'price':        lerpr( ref2['price']/2,  ref2['price'] ),
      'mass':         int(ref2['mass']),
      'desc':         desc['hull'],
      'gfx_store':    'organic_hull_' + gfx,
      'cargo':        lerpr( ref2['cargo'],    round((ref1['cargo']+ref2['cargo'])/2.0) ),
      'absorb':       lerpr( ref1['absorb']-3, ref2['absorb']-3 ),
      'armour':       lerpr( ref1['armour'],   ref2['armour'] )
   } ).run( [ N_(output_pref + " Cortex " + s) for s in outputs ] )


##
# Cerebrum
#

# Perleve Cerebrum    =>  Orion_2301
# Laevum Cerebrum     =>  Orion_2301 x2
# Mediocre Cerebrum   =>  Orion_4801
# Largum Cerebrum     =>  Orion_4801 x2
# Ponderosum Cerebrum =>  Orion_8601
# Immane Cerebrum     =>  Orion_8601 x2
for siz, nam, dbl, gfx, output_pref, outputs, cpu in [
   ('small','milspec_orion_2301_core_system.xml',  False, 's1', 'Perleve',    ['I', 'II'],         (     5,     6 )),
   ('small','milspec_orion_2301_core_system.xml',   True, 's2', 'Laevum',     ['I', 'II'],         (    24,    32 )),
   ('medium','milspec_orion_4801_core_system.xml', False, 'm1', 'Mediocre',   ['I', 'II'],         (    48,   100 )),
   ('medium','milspec_orion_4801_core_system.xml',  True, 'm2', 'Largum',     ['I', 'II'],         (   100,   200 )),
   ('large','milspec_orion_8601_core_system.xml',  False, 'l1', 'Ponderosum', ['I', 'II', 'III'],  (   100,   200 )),
   ('large','milspec_orion_8601_core_system.xml',   True, 'l2', 'Immane',     ['I', 'II', 'III'],  ( 370*3, 600*3 ))
]:
   ref = get_outfit_dict(path.join('core_system', siz, nam), True, dbl)

   if gfx[0] == 's':
      price = 0.5*ref['price']
      mass = 0.25*ref['mass']
      shield = 0.25*ref['shield']
      shield_regen = 0.25*ref['shield_regen']
   else:
      shield_capacitor = get_outfit_dict(path.join('structure', 'shield_capacitor_ii'+('i' if gfx[0] ==' l' else '')+'.xml'))
      shield_booster = get_outfit_dict(path.join('structure', ref['size']+'_shield_booster.xml'))

      price = 0.5*(shield_booster['price']+shield_capacitor['price'])
      mass = 0.5*(shield_booster['mass']+shield_capacitor['mass'])
      shield = 0.5*shield_capacitor['shield']
      shield_regen = 0.5*shield_booster['shield_regen']

   BioOutfit( 'cerebrum.xml.template', {
      'price':          lerpr(ref['price'],        ref['price']+price),
      'mass':           lerpr(ref['mass'],         ref['mass']+mass),
      'shield':         lerpr(ref['shield'],       ref['shield']+shield),
      'shield_regen':   lerpr(ref['shield_regen'], ref['shield_regen']+shield_regen),
      'typename':       typename['brain'],
      'desc':           desc['brain'],
      'size':           ref['size'],

      'energy' :        lerp(ref['energy'],        ref['energy']*1.25 ),
      'energy_regen' :  lerp(ref['energy_regen'],  ref['energy_regen']*1.25 ),
      'gfx_store':      'organic_core_'+gfx,
      'cpu':            lerpr(*cpu),
   } ).run( [ N_(output_pref+' Cerebrum '+s) for s in outputs ] )


##
# Weapons
#

# Stinger  =>  Plasma Blaster MK1  &  MK2
extrapol = (0.25, 0.75)
follow = (0.5, 1.0)
BioOutfit( 'weapon.xml.template', {
   'typename':      N_("Bioship Weapon Organ"),
   'size':          'small',
   'mass':          eerpr(    3,    6, *follow),
   'price' :        eerpr( 19e3, 45e3, *extrapol),
   'desc':          N_("The Stinger Organ is able to convert energy into hot plasma that is able to eat easily through shield and armour of opposing ships over time. While not an especially powerful offensive organ, it is prized for its reliability."),
   'gfx_store':     lerpt(('organic_plasma_s1', 'organic_plasma_s2', 'organic_plasma_s3')),
   'specific':      'bolt',
   'gfx':           'plasma',
   'gfx_end':       'plasma2-end',
   'sound':         'bioplasma',
   'spfx_shield':   'Shi-S',
   'spfx_armour':   'Pla-S',
   'lua':           'bioplasma.lua',
   'delay':         eerp(   1.2,  1.4, *follow),
   'speed':         700,
   'range':         eerpr(  700,  800, *extrapol),
   'falloff':       eerpr(  600,  650, *extrapol),
   'energy':        eerpr(    6, 16.5, *follow),
   'trackmin':      0,
   'trackmax':      eerpr( 2000, 3000, *follow),
   'penetrate':     lerpr(    0,   10, *follow),
   'damage':        eerp(    20,   27, *extrapol),
   'extra':         '<swivel>22</swivel>',
} ).run( [
   N_("Stinger Organ I"),
   N_("Stinger Organ II"),
   N_("Stinger Organ III"),
] )

# Talon  =>  Plasma Blaster mk2  &  Plasma Cluster
# We want Talon(0) = Plasma Blaster mk2  and  Talon(3) = Plasma Cluster
# This way, if we admit PB mk2 is size 2 (fighter size) and PCluster is size 3.5 (medium),
# then Talon II is size 3, TIII size 3.5 et TIV size 4.
# A few tweaks for keeping Talon almost the same.
extrapol = (-1.0/3, 2.0/3)

BioOutfit( 'weapon.xml.template', {
   'typename':      N_("Bioship Weapon Organ"),
   'size':          'medium',
   'mass' :         eerpr(    6,    16, -1.0/3, 1.0), # have Talon(4) weight as much as a standard med weapon.
   'price' :        eerpr( 45e3,  95e3, *extrapol),
   'desc':          N_("The Talon Organ is an enlarged and more powerful version of the Stinger Organ. Like its smaller counterpart, is able to convert energy into hot plasma that is able to eat easily through shield and armour of opposing ships. The hot plasma is able to cling to ship's shields and hulls dealing continuous damage after impact."),
   'gfx_store':     'organic_plasma_l',
   'specific':      'bolt',
   'gfx':           'plasma2',
   'gfx_end':       'plasma2-end',
   'sound':         'bioplasma',
   'spfx_shield':   'Shi-S',
   'spfx_armour':   'Pla-S2',
   'lua':           'bioplasma2.lua',
   'delay':         eerp(   1.4,  2.2, *extrapol),
   'speed' :        700,
   #
   'range' :        eerpr(  800, 1200, *extrapol),
   'falloff':       eerpr(  650, 1000, *extrapol),
   'energy':        eerpr( 16.5,   66, *extrapol),
   'trackmin':      lerpr(    0, 1000, *extrapol),
   'trackmax':      eerpr( 3000, 5000, *extrapol), # 5000 was 6000
   'penetrate':     lerpr(   10,   42, *extrapol),
   'damage':        eerp(    27,   68, *extrapol),
   #'range' :       lerp(  1000, 1400 ),
   #'falloff':      lerp(   900, 1200 ),
   #'energy':       lerp(    46,   58 ),
   #'trackmin':     lerp(  1500, 2000 ),
   #'trackmax':     lerp(  4500, 6000 ),
   #'penetrate':    lerpr(   35,   50 ),
   #'damage':       lerp(    35,   47 ),
   'extra':         '<swivel>22</swivel>',
} ).run( [
   N_("Talon Organ I"),
   N_("Talon Organ II"),
   N_("Talon Organ III"),
   N_("Talon Organ IV"),
] )

# Tentacle Organ  =>  .. complicated ..
#
# By examining plasma_turret mk2 and laser_turret mk2, we could guess the following:
#  - laser has +20% range +20% falloff +20% tackmin/max +12.5% penetrate over plasma +1/16 more costly
#  - plasma has +40% delay +40% energy +10% damage over laser
#  - they have same mass and CPU usage
#  - speed is constant, 800 in the case of plasma.
#
# Now we can guess what a heavy plasma turret and turboplasma turret would look like.
# There only remains to interpolate/extrapolate as we did for stinger.

# heavy plasma = tentacle 2  turboplasma = tentacle 4
extrapol = (1.0/3.0, 1.0)
extrapol_bias = (1.0/3, 1.0)#+1.0/6)

BioOutfit( 'weapon.xml.template', {
   'typename':      N_("Bioship Weapon Organ"),
   'size':          'large',
   'mass':          eerpr(         36,          75, *extrapol),
   'price':         eerpr(320e3*16/17, 700e3*16/17, *extrapol),
   'desc':          N_("The Tentacle Organ has the distinction of being the only fully rotating organic weapon while boasting a fully developed power output that is hard to beat with conventional weaponry found throughout the galaxy. The large globs of hot plasma it launches can corrode through seemingly impregnable armours, seeping into and melting ships from the inside upon contact."),
   'gfx_store':     'organic_plasma_t',
   'specific':      'turret bolt',
   'gfx':           'plasma',
   'gfx_end':       'plasma2-end',
   'sound':         'bioplasma_large',
   'spfx_shield':   'Shi-M',
   'spfx_armour':   'Pla-M',
   'lua':           'bioplasma.lua',
   'delay':         eerp(    1.4*1.4,  1.4*1.45, *extrapol),
   'speed' :        700,
   #
   'range' :        eerpr(  1750/1.2,  2400/1.2, *extrapol),
   'falloff':       eerpr(  1400/1.2,  1800/1.2, *extrapol),
   'energy':        eerpr(    49*1.4,   174*1.4, *extrapol_bias),
   'trackmin':      eerpr(  2400/1.2,  6000/1.2, *extrapol),
   'trackmax':      eerpr(  9600/1.2, 24000/1.2, *extrapol),
   'penetrate':     lerpr(  62/1.125,  80/1.125, *extrapol),
   'damage':        eerp(     48*1.1,    70*1.1, *extrapol),
   #'range' :       lerp(       1800,  2000 ),
   #'falloff':      lerp(       1500,  1800 ),
   #'energy':       lerp(        155,   190 ),
   #'trackmin':     lerp(       3000,  4000 ),
   #'trackmax':     lerp(      16000, 20000 ),
   #'penetrate':    lerpr(        70,   100 ),
   #'damage':       lerp(         51,    65 ),
   'extra':         '<swivel>22</swivel>',
} ).run( [
   N_("Tentacle Organ I"),
   N_("Tentacle Organ II"),
   N_("Tentacle Organ III"),
   N_("Tentacle Organ IV")
] )
