from typing import Any
from .sections import *
from .database import API_ADD, API_UPDATE, API_DELETE, ChangeSet, write, read, read_all, get_current_operation
from .extension_data import set_extension_data
from .s1_title import set_title
from .s2_junctions import set_junction, add_junction, delete_junction
from .s3_reservoirs import set_reservoir, add_reservoir, delete_reservoir
from .s4_tanks import set_tank, add_tank, delete_tank
from .s5_pipes import set_pipe, add_pipe, delete_pipe
from .s6_pumps import set_pump, add_pump, delete_pump
from .s7_valves import set_valve, add_valve, delete_valve
from .s8_tags import set_tag
from .s9_demands import set_demand
from .s10_status import set_status
from .s11_patterns import set_pattern, add_pattern, delete_pattern
from .s12_curves import set_curve, add_curve, delete_curve
from .s13_controls import set_control
from .s14_rules import set_rule
from .s15_energy import set_energy, set_pump_energy
from .s16_emitters import set_emitter
from .s17_quality import set_quality
from .s18_sources import set_source, add_source, delete_source
from .s19_reactions import set_reaction, set_pipe_reaction, set_tank_reaction
from .s20_mixing import set_mixing, add_mixing, delete_mixing
from .s21_times import set_time
from .s23_options_util import set_option, set_option_v3
from .s25_vertices import set_vertex, add_vertex, delete_vertex
from .s26_labels import set_label, add_label, delete_label
from .s27_backdrop import set_backdrop
from .s29_scada_device import set_scada_device, add_scada_device, delete_scada_device
from .s30_scada_device_data import set_scada_device_data, add_scada_device_data, delete_scada_device_data
from .s31_scada_element import set_scada_element, add_scada_element, delete_scada_element
from .s32_region import set_region, add_region, delete_region
from .s33_dma import set_district_metering_area, add_district_metering_area, delete_district_metering_area
from .s34_sa import set_service_area, add_service_area, delete_service_area
from .s35_vd import set_virtual_district, add_virtual_district, delete_virtual_district
from .batch_api_cs import rewrite_batch_api


def _execute_add_command(name: str, cs: ChangeSet) -> ChangeSet:
    type = cs.operations[0]['type']

    if type == s1_title:
        return ChangeSet()
    if type == s2_junction:
        return add_junction(name, cs)
    elif type == s3_reservoir:
        return add_reservoir(name, cs)
    elif type == s4_tank:
        return add_tank(name, cs)
    elif type == s5_pipe:
        return add_pipe(name, cs)
    elif type == s6_pump:
        return add_pump(name, cs)
    elif type == s7_valve:
        return add_valve(name, cs)
    elif type == s8_tag:
        return ChangeSet()
    elif type == s9_demand:
        return ChangeSet()
    elif type == s10_status:
        return ChangeSet()
    elif type == s11_pattern:
        return add_pattern(name, cs)
    elif type == s12_curve:
        return add_curve(name, cs)
    elif type == s13_control:
        return ChangeSet()
    elif type == s14_rule:
        return ChangeSet()
    elif type == s15_energy:
        return ChangeSet()
    elif type == s15_pump_energy:
        return ChangeSet()
    elif type == s16_emitter:
        return ChangeSet()
    elif type == s17_quality:
        return ChangeSet()
    elif type == s18_source:
        return add_source(name, cs)
    elif type == s19_reaction:
        return ChangeSet()
    elif type == s19_pipe_reaction:
        return ChangeSet()
    elif type == s19_tank_reaction:
        return ChangeSet()
    elif type == s20_mixing:
        return add_mixing(name, cs)
    elif type == s21_time:
        return ChangeSet()
    elif type == s22_report:
        return ChangeSet()
    elif type == s23_option:
        return ChangeSet()
    elif type == s23_option_v3:
        return ChangeSet()
    elif type == s24_coordinate:
        return ChangeSet()
    elif type == s25_vertex:
        return add_vertex(name, cs)
    elif type == s26_label:
        return add_label(name, cs)
    elif type == s27_backdrop:
        return ChangeSet()
    elif type == s28_end:
        return ChangeSet()
    elif type == s29_scada_device:
        return add_scada_device(name, cs)
    elif type == s30_scada_device_data:
        return add_scada_device_data(name, cs)
    elif type == s31_scada_element:
        return add_scada_element(name, cs)
    elif type == s32_region:
        return add_region(name, cs)
    elif type == s33_dma:
        return add_district_metering_area(name, cs)
    elif type == s34_sa:
        return add_service_area(name, cs)
    elif type == s35_vd:
        return add_virtual_district(name, cs)

    return ChangeSet()


def _execute_update_command(name: str, cs: ChangeSet) -> ChangeSet:
    type = cs.operations[0]['type']

    if type == 'extension_data':
        return set_extension_data(name, cs)
    if type == s1_title:
        return set_title(name, cs)
    if type == s2_junction:
        return set_junction(name, cs)
    elif type == s3_reservoir:
        return set_reservoir(name, cs)
    elif type == s4_tank:
        return set_tank(name, cs)
    elif type == s5_pipe:
        return set_pipe(name, cs)
    elif type == s6_pump:
        return set_pump(name, cs)
    elif type == s7_valve:
        return set_valve(name, cs)
    elif type == s8_tag:
        return set_tag(name, cs)
    elif type == s9_demand:
        return set_demand(name, cs)
    elif type == s10_status:
        return set_status(name, cs)
    elif type == s11_pattern:
        return set_pattern(name, cs)
    elif type == s12_curve:
        return set_curve(name, cs)
    elif type == s13_control:
        return set_control(name, cs)
    elif type == s14_rule:
        return set_rule(name, cs)
    elif type == s15_energy:
        return set_energy(name, cs)
    elif type == s15_pump_energy:
        return set_pump_energy(name, cs)
    elif type == s16_emitter:
        return set_emitter(name, cs)
    elif type == s17_quality:
        return set_quality(name, cs)
    elif type == s18_source:
        return set_source(name, cs)
    elif type == s19_reaction:
        return set_reaction(name, cs)
    elif type == s19_pipe_reaction:
        return set_pipe_reaction(name, cs)
    elif type == s19_tank_reaction:
        return set_tank_reaction(name, cs)
    elif type == s20_mixing:
        return set_mixing(name, cs)
    elif type == s21_time:
        return set_time(name, cs)
    elif type == s22_report: # no api now
        return ChangeSet()
    elif type == s23_option:
        return set_option(name, cs)
    elif type == s23_option_v3:
        return set_option_v3(name, cs)
    elif type == s24_coordinate: # do not support update here
        return ChangeSet()
    elif type == s25_vertex:
        return set_vertex(name, cs)
    elif type == s26_label:
        return set_label(name, cs)
    elif type == s27_backdrop:
        return set_backdrop(name, cs)
    elif type == s28_end: # end
        return ChangeSet()
    elif type == s29_scada_device:
        return set_scada_device(name, cs)
    elif type == s30_scada_device_data:
        return set_scada_device_data(name, cs)
    elif type == s31_scada_element:
        return set_scada_element(name, cs)
    elif type == s32_region:
        return set_region(name, cs)
    elif type == s33_dma:
        return set_district_metering_area(name, cs)
    elif type == s34_sa:
        return set_service_area(name, cs)
    elif type == s35_vd:
        return set_virtual_district(name, cs)

    return ChangeSet()


def _execute_delete_command(name: str, cs: ChangeSet) -> ChangeSet:
    type = cs.operations[0]['type']

    if type == s1_title:
        return ChangeSet()
    if type == s2_junction:
        return delete_junction(name, cs)
    elif type == s3_reservoir:
        return delete_reservoir(name, cs)
    elif type == s4_tank:
        return delete_tank(name, cs)
    elif type == s5_pipe:
        return delete_pipe(name, cs)
    elif type == s6_pump:
        return delete_pump(name, cs)
    elif type == s7_valve:
        return delete_valve(name, cs)
    elif type == s8_tag:
        return ChangeSet()
    elif type == s9_demand:
        return ChangeSet()
    elif type == s10_status:
        return ChangeSet()
    elif type == s11_pattern:
        return delete_pattern(name, cs)
    elif type == s12_curve:
        return delete_curve(name, cs)
    elif type == s13_control:
        return ChangeSet()
    elif type == s14_rule:
        return ChangeSet()
    elif type == s15_energy:
        return ChangeSet()
    elif type == s15_pump_energy:
        return ChangeSet()
    elif type == s16_emitter:
        return ChangeSet()
    elif type == s17_quality:
        return ChangeSet()
    elif type == s18_source:
        return delete_source(name, cs)
    elif type == s19_reaction:
        return ChangeSet()
    elif type == s19_pipe_reaction:
        return ChangeSet()
    elif type == s19_tank_reaction:
        return ChangeSet()
    elif type == s20_mixing:
        return delete_mixing(name, cs)
    elif type == s21_time:
        return ChangeSet()
    elif type == s22_report:
        return ChangeSet()
    elif type == s23_option:
        return ChangeSet()
    elif type == s23_option_v3:
        return ChangeSet()
    elif type == s24_coordinate:
        return ChangeSet()
    elif type == s25_vertex:
        return delete_vertex(name, cs)
    elif type == s26_label:
        return delete_label(name, cs)
    elif type == s27_backdrop:
        return ChangeSet()
    elif type == s28_end:
        return ChangeSet()
    elif type == s29_scada_device:
        return delete_scada_device(name, cs)
    elif type == s30_scada_device_data:
        return delete_scada_device_data(name, cs)
    elif type == s31_scada_element:
        return delete_scada_element(name, cs)
    elif type == s32_region:
        return delete_region(name, cs)
    elif type == s33_dma:
        return delete_district_metering_area(name, cs)
    elif type == s34_sa:
        return delete_service_area(name, cs)
    elif type == s35_vd:
        return delete_virtual_district(name, cs)

    return ChangeSet()


def execute_batch_commands(name: str, cs: ChangeSet) -> ChangeSet:
    new_cs = ChangeSet()
    for op in cs.operations:
        new_cs.merge(rewrite_batch_api(name, ChangeSet(op)))

    result = ChangeSet()

    todo = {}

    try:
        for op in new_cs.operations:
            todo = op
            operation = op['operation']
            if operation == API_ADD:
                result.merge(_execute_add_command(name, ChangeSet(op)))
            elif operation == API_UPDATE:
                result.merge(_execute_update_command(name, ChangeSet(op)))
            elif operation == API_DELETE:
                result.merge(_execute_delete_command(name, ChangeSet(op)))
    except:
        print(f'ERROR: Fail to execute {todo}')

    return result


def execute_batch_command(name: str, cs: ChangeSet) -> ChangeSet:
    write(name, 'delete from batch_operation where id > 0')
    write(name, "update operation_table set option = 'batch_operation' where option = 'operation'")

    new_cs = ChangeSet()
    for op in cs.operations:
        new_cs.merge(rewrite_batch_api(name, ChangeSet(op)))

    result = ChangeSet()

    todo = {}

    try:
        for op in new_cs.operations:
            todo = op
            operation = op['operation']
            if operation == API_ADD:
                result.merge(_execute_add_command(name, ChangeSet(op)))
            elif operation == API_UPDATE:
                result.merge(_execute_update_command(name, ChangeSet(op)))
            elif operation == API_DELETE:
                result.merge(_execute_delete_command(name, ChangeSet(op)))
    except:
        print(f'ERROR: Fail to execute {todo}')

    count = read(name, 'select count(*) as count from batch_operation')['count']
    if count == 1:
        write(name, 'delete from batch_operation where id > 0')
        write(name, "update operation_table set option = 'operation' where option = 'batch_operation'")
        return ChangeSet()

    redo_list: list[str] = []
    redo_cs_list: list[dict[str, Any]] = []
    redo_rows = read_all(name, 'select redo, redo_cs from batch_operation where id > 0 order by id asc')
    for row in redo_rows:
        redo_list.append(row['redo'])
        redo_cs_list += eval(row['redo_cs'])

    undo_list: list[str] = []
    undo_cs_list: list[dict[str, Any]] = []
    undo_rows = read_all(name, 'select undo, undo_cs from batch_operation where id > 0 order by id desc')
    for row in undo_rows:
        undo_list.append(row['undo'])
        undo_cs_list += eval(row['undo_cs'])

    redo = '\n'.join(redo_list).replace("'", "''")
    redo_cs = str(redo_cs_list).replace("'", "''")
    undo = '\n'.join(undo_list).replace("'", "''")
    undo_cs = str(undo_cs_list).replace("'", "''")

    parent = get_current_operation(name)
    write(name, f"insert into operation (id, redo, undo, parent, redo_cs, undo_cs) values (default, '{redo}', '{undo}', {parent}, '{redo_cs}', '{undo_cs}')")
    current = read(name, 'select max(id) as id from operation')['id']
    write(name, f"update current_operation set id = {current}")

    write(name, 'delete from batch_operation where id > 0')
    write(name, "update operation_table set option = 'operation' where option = 'batch_operation'")

    return result
