#!/usr/bin/env python3
import os
import sys
from argparse import ArgumentParser

parser = ArgumentParser(prog='generate v1')
parser.add_argument('module', type=str,
                    help='the name of the module to generate')
parser.add_argument('-i', '--input', type=str,
                    help='The path and name of the input file')
parser.add_argument('-o', '--output', type=str,
                    help='The path of the output files')

args = parser.parse_args()
DIR = os.path.dirname(os.path.realpath(sys.argv[0]))
MODULE = args.module

def isNotKeyword(str):
    keywords = [
        "core",
        "ex",
        "recent",
        "service",
        "set",
        "toggle",
        "toggleNil",
        "vm",
        "$vm",
        "world",
    ]
    return str not in keywords

class CoreAction:
    def __init__(self, func, sink):
        self.func = func
        self.sink = sink

    def code(self):
        return f"""
      p.ctrl.m
        .compactMap {{ {self.func}($0) }}
        .receive(on: DispatchQueue.main)
        .sink {{ [weak core = p.core] v in {self.sink} }}
        .store(in: &p.core.subscriptions)
""".replace("\n\n", "\n")

class CorePipe:
    def __init__(self, name, props):
        self.name = name
        self.props = props
        self.resetMethod()
        self.resetDbg()
        self.resetSrc()
        self.resetExName()
        self.resetSteps()

    def code(self):
        return f"""
      p.ctrl.{self.method}(
        dbg: "{self.dbg}",
        sub: &p.core.subscriptions,
        {self.src}.eraseToAnyPublisher(),
{self.steps}
      )
""".replace("\n\n", "\n")

    def resetDbg(self):
        capitals = [l for l in self.name if l.isupper()]
        # Нет заглавных.
        if len(capitals) == 0:
            self.dbg = self.name
            return
        # Есть заглавные.
        firstCap = self.name.find(capitals[0])
        self.dbg = self.name[:firstCap] + "".join(capitals)

    def resetExName(self):
        firstLetter = self.name[0].capitalize()
        self.exName = f"""ex{firstLetter}{self.name[1:]}"""

    def resetMethod(self):
        if "toggle" in self.props:
            self.method = "pipe"
        else:
            self.method = "pipeValue"

    def resetSrc(self):
        if "core" in self.props:
            self.src = "p.core." + self.name
        elif "world" in self.props:
            self.src = "p.world." + self.name
        elif "vm" in self.props:
            self.src = "p.core.vm." + self.name
        elif "$vm" in self.props:
            self.src = "p.core.vm.$" + self.name
        else:
            # Если это что-то неизвестное заранее, то ищем строку,
            # отличную от известных ключевых слов.
            self.src = next(filter(isNotKeyword, self.props))

    def resetSteps(self):
        if "recent" and "ex" in self.props:
            self.steps = f"""
        {{
          $0.{self.name}.value = $1
          $0.{self.name}.isRecent = true
        }},
        {{
          $0.{self.name}.isRecent = false
          $0.{self.exName} = $1
        }}
"""
        elif "recent" in self.props:
            self.steps = f"""
        {{
          $0.{self.name}.value = $1
          $0.{self.name}.isRecent = true
        }},
        {{ m, _ in m.{self.name}.isRecent = false }}
"""
        elif "set" in self.props:
            self.steps = f"""
        {{ $0.{self.name} = $1 }}
"""
        elif "toggle" in self.props:
            self.steps = f"""
        {{ $0.{self.name} = true }},
        {{ $0.{self.name} = false }}
"""
        elif "toggleNil" in self.props:
            self.steps = f"""
        {{ $0.{self.name} = $1 }},
        {{ m, _ in m.{self.name} = nil }}
"""

class ServiceAction:
    def __init__(self, func, sink):
        self.func = func
        self.sink = sink

    def code(self):
        return f"""
      sp.ctrl.m
        .compactMap {{ {self.func}($0) }}
        .receive(on: DispatchQueue.main)
        .sink {{ v in {self.sink} }}
        .store(in: &sp.service.subscriptions)
""".replace("\n\n", "\n")

class ServicePipe:
    def __init__(self, name, props):
        self.name = name
        self.props = props
        self.resetMethod()
        self.resetDbg()
        self.resetSrc()
        self.resetExName()
        self.resetSteps()

    def code(self):
        return f"""
      sp.ctrl.{self.method}(
        dbg: "{self.dbg}",
        {self.src}.eraseToAnyPublisher(),
{self.steps}
      )
""".replace("\n\n", "\n")

    def resetDbg(self):
        capitals = [l for l in self.name if l.isupper()]
        # Нет заглавных.
        if len(capitals) == 0:
            self.dbg = self.name
            return
        # Есть заглавные.
        firstCap = self.name.find(capitals[0])
        self.dbg = self.name[:firstCap] + "".join(capitals)

    def resetExName(self):
        firstLetter = self.name[0].capitalize()
        self.exName = f"""ex{firstLetter}{self.name[1:]}"""

    def resetMethod(self):
        if "toggle" in self.props:
            self.method = "pipe"
        else:
            self.method = "pipeValue"

    def resetSrc(self):
        if "service" in self.props:
            self.src = "sp.service." + self.name
        elif "world" in self.props:
            self.src = "sp.world." + self.name
        else:
            # Если это и не сервис, и не мир, то
            # ищем строку, отличную от известных ключевых слов.
            self.src = next(filter(isNotKeyword, self.props))

    def resetSteps(self):
        if "recent" and "ex" in self.props:
            self.steps = f"""
        {{
          $0.{self.name}.value = $1
          $0.{self.name}.isRecent = true
        }},
        {{
          $0.{self.name}.isRecent = false
          $0.{self.exName} = $1
        }}
"""
        elif "recent" in self.props:
            self.steps = f"""
        {{
          $0.{self.name}.value = $1
          $0.{self.name}.isRecent = true
        }},
        {{ m, _ in m.{self.name}.isRecent = false }}
"""
        elif "set" in self.props:
            self.steps = f"""
        {{ $0.{self.name} = $1 }}
"""
        elif "toggle" in self.props:
            self.steps = f"""
        {{ $0.{self.name} = true }},
        {{ $0.{self.name} = false }}
"""
        elif "toggleNil" in self.props:
            self.steps = f"""
        {{ $0.{self.name} = $1 }},
        {{ m, _ in m.{self.name} = nil }}
"""

class State:
    isCoreAction = False
    isCoreController = False
    isServiceAction = False
    isServiceController = False

def generatePlatform(coreActions, corePipes, serviceActions, servicePipes):
    corePlatform = ""
    if len(coreActions) > 0 or len(corePipes) > 0:
        corePlatform = f"""
    static func setupPlatform(_ p: CoreParameters) {{
{coreActions}
{corePipes}
    }}
"""

    return f"""
// ВНИМАНИЕ: Сгенерировано автоматом, не менять руками!

import Foundation

extension {MODULE} {{
  enum SectionGenerated {{
{corePlatform}
    static func setupPlatform(_ sp: ServiceParameters) {{
{serviceActions}
{servicePipes}
    }}
  }}
}}
"""

def generateCoreAction(ln):
    parts = ln.split(": ")
    if len(parts) != 2:
        return None
    func = parts[0].lstrip()
    sink = parts[1].rstrip()
    action = CoreAction(func, sink)
    return action.code()

def generateCorePipe(ln):
    parts = ln.split(": [")
    if len(parts) != 2:
        return None
    name = parts[0].lstrip()
    props = parts[1][:-1].split(", ")
    pipe = CorePipe(name, props)
    return pipe.code()

def generateServiceAction(ln):
    parts = ln.split(": ")
    if len(parts) != 2:
        return None
    func = parts[0].lstrip()
    sink = parts[1].rstrip()
    action = ServiceAction(func, sink)
    return action.code()

def generateServicePipe(ln):
    parts = ln.split(": [")
    if len(parts) != 2:
        return None
    name = parts[0].lstrip()
    props = parts[1][:-1].split(", ")
    pipe = ServicePipe(name, props)
    return pipe.code()

def readFile(fileName):
    lines = []
    with open(fileName) as file:
        for line in file:
            lines.append(line.rstrip())
    return lines

def resetState(ln):
    if ln.startswith("ca:"):
        state.isCoreAction = True
        state.isCoreController = False
        state.isServiceAction = False
        state.isServiceController = False
    elif ln.startswith("cp:"):
        state.isCoreAction = False
        state.isCoreController = True
        state.isServiceAction = False
        state.isServiceController = False
    elif ln.startswith("sa:"):
        state.isCoreAction = False
        state.isCoreController = False
        state.isServiceAction = True
        state.isServiceController = False
    elif ln.startswith("sp:"):
        state.isCoreAction = False
        state.isCoreController = False
        state.isServiceAction = False
        state.isServiceController = True
    return state

def saveFile(fileName, data):
    with open(fileName, "w") as file:
        file.write(data)

def validateVersion(ln):
    if ln.startswith("version: 1") == False:
        print("ERROR: Invalid version")
        sys.exit(1)


# Main
state = State()
print(f"Generating platform for module '{MODULE}'...")
fileIn = args.input or f"{DIR}/../../../Modules/{MODULE}/{MODULE}.yml"
fileOut = args.output or f"{DIR}/../../../Modules/{MODULE}/src"
fileOut = os.path.join(fileOut, f'{MODULE}.SectionGenerated.swift')
lines = readFile(fileIn)
validateVersion(lines[0])
coreActions = ""
corePipes = ""
serviceActions = ""
servicePipes = ""
for ln in lines:
    st = resetState(ln)
    if st.isCoreAction:
        action = generateCoreAction(ln)
        if action:
            coreActions += action
    if st.isCoreController:
        pipe = generateCorePipe(ln)
        if pipe:
            corePipes += pipe
    if st.isServiceAction:
        action = generateServiceAction(ln)
        if action:
            serviceActions += action
    if st.isServiceController:
        pipe = generateServicePipe(ln)
        if pipe:
            servicePipes += pipe
result = generatePlatform(coreActions, corePipes, serviceActions, servicePipes)
saveFile(fileOut, result)