import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:flutter_flow_chart/flutter_flow_chart.dart';
import 'package:oc_front/core/models/workspace_local.dart';
import 'package:oc_front/models/abstract.dart';
import 'package:oc_front/models/logs.dart';
import 'package:oc_front/models/response.dart';
import 'package:oc_front/models/search.dart';

class Check extends SerializerDeserializer<Check> {
  bool is_available = false;

  Check({
    this.is_available = false,
  });

  @override deserialize(dynamic json) {
    try { json = json as Map<String, dynamic>;
    } catch (e) { return Check(); }   
    return Check(
      is_available: json.containsKey("is_available") ? json["is_available"] : false,
    );
  }
  @override Map<String, dynamic> serialize() {
    return {
      "is_available": is_available,
    };
  }
}


class WorkflowExecutions extends SerializerDeserializer<WorkflowExecutions> {
  List<WorkflowExecution> executions = [];
  String? executionData;
  int? status;
  String? workflowId;

  WorkflowExecutions({
    this.executions = const [],
  });

  @override deserialize(dynamic json) {
    try { json = json as List<dynamic>;
    } catch (e) { return WorkflowExecutions(); }   
    return WorkflowExecutions(
      executions: fromListJson(json, WorkflowExecution()),
    );
  }
  
  @override Map<String, dynamic> serialize() {
    return {};
  }

}

class WorkflowExecution extends SerializerDeserializer<WorkflowExecution> {
  String? id;
  String? name;
  String? executionData;
  String? endDate;
  int? status;
  String? workflowId;

  List<Log>? logs;
  

  WorkflowExecution({
    this.id,
    this.executionData,
    this.status,
    this.workflowId,
    this.name,
    this.endDate,
  });

  @override deserialize(dynamic json) {
    try { json = json as Map<String, dynamic>;
    } catch (e) { return WorkflowExecution(); }   
    return WorkflowExecution(
      id: json.containsKey("id") ? json["id"] : "",
      endDate: json.containsKey("end_date") ? json["end_date"] : "",
      executionData: json.containsKey("execution_date") ? json["execution_date"] : "",
      status: json.containsKey("state") ? json["state"] : 1,
      workflowId: json.containsKey("workflow_id") ? json["workflow_id"] : "",
      name: json.containsKey("name") ? json["name"] : "",
    );
  }
  
  @override Map<String, dynamic> serialize() {
    return {
      "id": id,
      "name": name,
      "end_date": endDate,
      "execution_data": executionData,
      "status": status,
      "workflow_id": workflowId,
    };
  }

}

class Workflow extends SerializerDeserializer<Workflow>  implements ShallowData {
  String? id;
  String? name;
  List<dynamic> data;
  List<dynamic> compute;
  List<dynamic> storage;
  List<dynamic> processing;
  List<dynamic> workflows;
  Graph? graph;
  Scheduler? schedule;
  bool scheduleActive = false;
  List<dynamic> shared;

  Workflow({
    this.id,
    this.name = "",
    this.data = const [],
    this.compute = const [],
    this.storage = const [],
    this.processing = const [],
    this.workflows = const [],
    this.graph,
    this.schedule,
    this.scheduleActive = false,
    this.shared = const [],
  });

  @override String getID() => id ?? "";
  @override String getName() => name ?? "";
  @override String getDescription() => "";

  @override deserialize(dynamic json) {
    try { json = json as Map<String, dynamic>;
    } catch (e) { return Workflow(); }   
    return Workflow(
      id: json.containsKey("id") ? json["id"] : "",
      name: json.containsKey("name") ? json["name"] : "",
      workflows: json.containsKey("workflows") ? json["workflows"] : [],
      processing: json.containsKey("processings") ? json["processings"] : [],
      compute: json.containsKey("computes") ? json["computes"] : [],
      data: json.containsKey("datas") ? json["datas"] : [],
      scheduleActive: json.containsKey("schedule_active") ? json["schedule_active"] : false,
      storage: json.containsKey("storages") ? json["storages"] : [],
      shared: json.containsKey("shared") ? json["shared"] : [],
      graph: json.containsKey("graph") ? Graph().deserialize(json["graph"]) : null,
      schedule: json.containsKey("schedule") ?  Scheduler().deserialize(json["schedule"]) : null,
    );
  }
  @override Map<String, dynamic> serialize() {
    var obj = {
      "id": id,
      "name": name,
      "datas": data,
      "computes" : compute,
      "storages": storage,
      "processings": processing,
      "workflows": workflows,
      "schedule_active": scheduleActive,
      "schedule": schedule?.serialize(),
    };
    if (graph != null) {
      obj["graph"] = graph!.serialize();
    }
    return obj;
  }

  void fromDashboard(Map<String, dynamic> j) {
    id = j["id"];
    name = j["name"];
    scheduleActive = j["schedule_active"];
    if (j.containsKey("graph")) { 
      graph = Graph();
      graph!.fromDashboard(j["graph"]);
    }
    if (j.containsKey("schedule")) {
      schedule = Scheduler();
      schedule!.fromDashboard(j["schedule"]);
    }
  }
  Map<String, dynamic> toDashboard() {
    return {
      "id": id,
      "name": name,
      "graph": graph?.toDashboard(),
      "schedule_active": scheduleActive,
      "schedule": schedule?.toDashboard(),
      "shared": shared,
    };
  }
}

class Scheduler extends SerializerDeserializer<Scheduler> {
  String? id;
  String? name;
  String? cron;
  DateTime? start;
  DateTime? end;
  int? mode;

  Scheduler({
    this.id,
    this.name,
    this.cron,
    this.start,
    this.end,
    this.mode,
  });

  void fromDashboard(Map<String, dynamic> j) {
    id = j["id"];
    name = j["name"];
    cron = j["cron"];
    mode =j["mode"];
    try {
      start = j["start"] != null ? DateTime.parse(j["start"]) : DateTime.now().add( const Duration(minutes: 1)).toUtc();
      if (start == DateTime.utc(0)) {
        start = DateTime.now().add( const Duration(minutes: 1)).toUtc();
      }
      if (j.containsKey("end") && j["end"] != null) {
        end = DateTime.parse(j["end"]);
      }
      
    } catch (e) {}
  }
  Map<String, dynamic> toDashboard() {
    return {
      "id": id,
      "name": name,
      "cron": cron,
      "mode": mode ?? 1,
      "start": start?.toIso8601String(),
      "end": end?.toIso8601String(),
    };
  }

  @override deserialize(dynamic json) {
    try { json = json as Map<String, dynamic>;
    } catch (e) { return Scheduler(); }   
    return Scheduler(
      id: json.containsKey("id") ? json["id"] : null,
      name: json.containsKey("name") ? json["name"] : "",
      cron: json.containsKey("cron") ? json["cron"] : "",
      mode: json.containsKey("mode") ? json["mode"] : "",
      start: json.containsKey("start") && json["start"] != null ? DateTime.parse(json["start"]) : null,
      end: json.containsKey("end") && json["end"] != null ? DateTime.parse(json["end"]) : null,
    );
  }
  @override Map<String, dynamic> serialize() {
    try {
      return {
        "id": id,
        "name": name,
        "cron": cron ?? "",
        "mode": mode ?? 1,
        "start": start?.toIso8601String(),
        "end": end?.toIso8601String(),
      };
    } catch (e) {
      return {
        "id": id,
        "name": name,
        "cron": cron ?? "",
        "start": start?.toIso8601String(),
        "end": end?.toIso8601String(),
      };
    }
  }
}

class Graph extends SerializerDeserializer<Graph> {
  double zoom;
  Map<String, GraphItem> items = {};
  List<GraphLink> links = [];

  Graph({
    this.zoom = 1,
    this.items = const {},
    this.links = const [],
  });

  void fromDashboard(Map<String, dynamic> j) {
    items = {};
    for (var el in (j["elements"] as Map<dynamic, dynamic>).values) {
      var d = GraphItem();
      d.fromDashboard(el as Map<String, dynamic>);
      items[d.id ?? ""] = d;
    }
    links = (j["arrows"] as List<dynamic>).map( (el) {
      var d = GraphLink();
      d.fromDashboard(el);
      return d;
    }).toList();
    j["zoom"] = zoom;
  }

  Map<String, dynamic> toDashboard() {
    List<Map<String, dynamic>> elements = [];
    List<Map<String, dynamic>> arrows = [];
    for (var el in items.values) {
      elements.add(el.toDashboard());
    }
    for (var l in links) {
      arrows.add(l.toDashboard());
    }
    return {
      "zoom": zoom,
      "elements": elements,
      "arrows": arrows,
    };
  }

  @override deserialize(dynamic json) {
    try { json = json as Map<String, dynamic>;
    } catch (e) { return Graph(); }   
    return Graph(
      zoom: json.containsKey("zoom") ? double.parse(json["zoom"].toString()) : 0,
      links: json.containsKey("links") ? fromListJson(json["links"], GraphLink()) : [],
      items: json.containsKey("items") ? fromMapJson<GraphItem>(json["items"], GraphItem()) : {},
    );
  }
  @override Map<String, dynamic> serialize() => {
    "zoom": zoom,
    "items": toMapJson(items),
    "links": toListJson(links),
  };
}

class GraphLink extends SerializerDeserializer<GraphLink> {
  Position? source;
  Position? destination;
  GraphLinkStyle? style;

  GraphLink({
    this.source,
    this.destination,
    this.style,
  });

  void fromDashboard(Map<String, dynamic> j) {
    source = Position(id: j["from"]["id"], x: j["from"]["x"], y: j["from"]["y"]);
    destination = Position(id: j["to"]["id"], x: j["to"]["x"], y: j["to"]["y"]);
    style = GraphLinkStyle();
    style!.fromDashboard(j["params"]);
  }

  Map<String, dynamic> toDashboard() {
    return {
      "from": {
        "id": source?.id ?? "",
        "x" : source?.x  ?? 0,
        "y": source?.y ?? 0,
      },
      "to": {
        "id": destination?.id ?? "",
        "x" : destination?.x  ?? 0,
        "y": destination?.y ?? 0,
      },
      "params": style?.toDashboard(),
    };
  }

  @override deserialize(dynamic json) {
    try { json = json as Map<String, dynamic>;
    } catch (e) { return GraphLink(); }   
    return GraphLink(
      source: json.containsKey("source") ? Position().deserialize(json["source"]) : null,
      destination: json.containsKey("destination") ? Position().deserialize(json["destination"]) : null,
      style: json.containsKey("style") ? GraphLinkStyle().deserialize(json["style"]) : null,
    );
  }
  @override Map<String, dynamic> serialize() {
    var obj = <String, dynamic>{};
    if (source != null) {
      obj["source"] = source!.serialize();
    }
    if (destination != null) {
      obj["destination"] = destination!.serialize();
    }
    if (style != null) {
      obj["style"] = style!.serialize();
    }
    return obj;
  }
}

class GraphLinkStyle extends SerializerDeserializer<GraphLinkStyle> {
  Color color = Colors.black;
  double stroke = 1;
  double tension = 23;
  double headRadius = 10;
  double dashWidth = 0;
  double dashSpace = 0;
  double startArrowWidth = 1;
  double endArrowWidth = 10;
  Position? startArrow;
  Position? endArrow;
  ArrowStyle arrowStyle = ArrowStyle.curve;
  ArrowDirection arrowDirection = ArrowDirection.forward;

  GraphLinkStyle({
    this.color = Colors.black,
    this.stroke = 1,
    this.tension = 23,
    this.headRadius = 10,
    this.dashWidth = 0,
    this.dashSpace = 0,
    this.startArrowWidth = 10,
    this.endArrowWidth = 10,
    this.startArrow,
    this.endArrow,
    this.arrowStyle = ArrowStyle.curve,
    this.arrowDirection = ArrowDirection.forward
  });

  void fromDashboard(Map<String, dynamic> j) {
    dashSpace = j["dash_space"];
    dashWidth = j["dash_width"];
    endArrowWidth = j["backward_arrow_width"];
    startArrowWidth = j["forward_arrow_width"];
    stroke = j["thickness"];
    arrowDirection = ArrowDirection.values.firstWhere((element) => element.index == j["direction"]);
    headRadius = j["head_radius"];
    color = Color(j["color"]);
    arrowStyle = ArrowStyle.values.firstWhere((element) => element.index == j["arrow_style"]);
    tension = j["tension"];
    startArrow = Position(x: j["start_arrow_position_x"], y: j["start_arrow_position_y"]);
    endArrow = Position(x: j["end_arrow_position_x"], y: j["end_arrow_position_y"]);
  }

  Map<String, dynamic> toDashboard() {
    return {
      "dash_space": dashSpace,
      "dash_width": dashWidth,
      "backward_arrow_width": endArrowWidth,
      "forward_arrow_width": startArrowWidth,
      "thickness": stroke,
      "direction": arrowDirection.index,
      "head_radius": headRadius,
      "tail_length": 25.0,
      "color": int.parse(color.toHexString(), radix: 16),
      "arrow_style" : arrowStyle.index,
      "tension" : tension,
      "start_arrow_position_x": startArrow?.x ?? 0,
      "start_arrow_position_y": startArrow?.y ?? 0,
      "end_arrow_position_x": endArrow?.x ?? 0,
      "end_arrow_position_y": endArrow?.y ?? 0,
    };
  }

  @override deserialize(dynamic json) {
    try { json = json as Map<String, dynamic>;
    } catch (e) { return GraphLinkStyle(); }   
    return GraphLinkStyle(
      color: json.containsKey("color") ? Color(json["color"] as int) : Colors.black,
      stroke: json.containsKey("stroke") ? double.parse(json["stroke"].toString()) : 0,
      tension: json.containsKey("tension") ? double.parse(json["tension"].toString()) : 0,
      headRadius: json.containsKey("head_radius") ? double.parse(json["head_radius"].toString()) : 0,
      dashWidth: json.containsKey("dash_width") ? double.parse(json["dash_width"].toString()) : 0,
      dashSpace: json.containsKey("dash_space") ? double.parse(json["dash_space"].toString()) : 0,
      startArrow: json.containsKey("start_arrow") ? Position().deserialize(json["start_arrow"]) : null,
      endArrow: json.containsKey("end_arrow") ? Position().deserialize(json["end_arrow"]) : null,
      arrowDirection: json.containsKey("arrow_direction") ? ArrowDirection.values.firstWhere((el) => el.index == json["arrow_direction"]) : ArrowDirection.forward,
      arrowStyle: json.containsKey("arrow_style") ? ArrowStyle.values.firstWhere((el) => el.index == json["arrow_style"]) : ArrowStyle.curve,
      startArrowWidth: json.containsKey("start_arrow_width") ? double.parse(json["start_arrow_width"].toString()) : 0,
      endArrowWidth: json.containsKey("end_arrow_width") ? double.parse(json["end_arrow_width"].toString()) : 0,
    );
  }
  @override Map<String, dynamic> serialize() {
    var obj = <String, dynamic> {
      "color" : int.parse(color.toHexString(), radix: 16),
      "stroke" : stroke,
      "tension" : tension,
      "head_radius" : headRadius,
      "dash_width" : dashWidth,
      "dash_space" : dashSpace,
      "arrow_direction" : arrowDirection.index,
      "arrow_style" : arrowStyle.index,
      "start_arrow_width" : startArrowWidth,
      "end_arrow_width" : endArrowWidth,
    };
    if (startArrow != null) {
      obj["start_arrow"] = startArrow!.serialize();
    }
    if (endArrow != null) {
      obj["end_arrow"] = endArrow!.serialize();
    }
    return obj;
  }
}

class GraphItem extends SerializerDeserializer<GraphItem> {
  String? id;
  double? width;
  double? height;
  Position? position;
  DataItem? data;
  ProcessingItem? processing;
  StorageItem? storage;
  ComputeItem? compute;
  WorkflowItem? workflow;

  GraphItem({
    this.id,
    this.width,
    this.height,
    this.position,
    this.data,
    this.processing,
    this.storage,
    this.compute,
    this.workflow,
  });

  AbstractItem? getElement() {
    if (data != null) { return data!; }
    if (processing != null) { return processing!; }
    if (storage != null) { return storage!; }
    if (compute != null) { return compute!; }
    if (workflow != null) { return workflow!; }
    return null;
  }

  void fromDashboard(Map<String, dynamic> j) {
    id = j["id"];
    position = Position(x: j["x"], y: j["y"]);
    width = j["width"];
    height = j["height"];
    
    var abs = WorkspaceLocal.getItem(j["element"]?["id"] ?? "", true) as AbstractItem<FlowData>?;
    if (abs != null) {
      if (abs.topic == "data") { 
        data = DataItem().deserialize(abs.serialize()); 
        data!.model = ResourceModel().deserialize(j["element"]["resource_model"]);
      } else if (abs.topic == "processing") { 
        processing = ProcessingItem().deserialize(abs.serialize());
        processing!.model = ResourceModel().deserialize(j["element"]["resource_model"]);
        if ((j["element"] as Map<String, dynamic>).containsKey("container")) {
          processing!.container = Containered().deserialize(j["element"]["container"]);
          processing!.container?.env?.removeWhere((key, value) => key == "" || value == "");
          processing!.container?.volumes?.removeWhere((key, value) => key == "" || value == "");
          processing!.expose.removeWhere((element) => element.port == null || element.port == 0 || (element.PAT == 0 && element.path == ""));

        }
      } else if (abs.topic == "compute") { 
        compute = ComputeItem().deserialize(abs.serialize()); 
      } else if (abs.topic == "storage") { 
        storage = StorageItem().deserialize(abs.serialize()); 
      } else if (abs.topic == "workflow") { 
        workflow = WorkflowItem().deserialize(abs.serialize()); 
      } else {
        compute = null;
        data = null;
        processing = null;
        storage = null;
        workflow = null;
      }
    } else {
        compute = null;
        data = null;
        processing = null;
        storage = null;
        workflow = null;
      } 
  }

  Map<String, dynamic> toDashboard() {
    Map<String, dynamic> element = {};
    for(var el in [data, processing, storage, compute, workflow]) {
      if (el != null && el.getID() != "") {
        element = el.serialize();
        break;
      }
    }
    return {
      "id": id,
      "x": (position?.x ?? 0),
      "y": (position?.y ?? 0),
      "width": width ?? 0,
      "height": height ?? 0,
      "element": element, 
    };
  }

  @override deserialize(dynamic json) {
    try { json = json as Map<String, dynamic>;
    } catch (e) { return GraphItem(); }   
    return GraphItem(
      id: json.containsKey("id") ? json["id"] : null,
      width: json.containsKey("width") ? double.parse(json["width"].toString()) : null,
      height: json.containsKey("height") ? double.parse(json["height"].toString()) : null,
      position: json.containsKey("position") ? Position().deserialize(json["position"]) : null,
      data: json.containsKey("data") ? DataItem().deserialize(json["data"]) : null,
      processing: json.containsKey("processing") ? ProcessingItem().deserialize(json["processing"]) : null,
      storage: json.containsKey("storage") ? StorageItem().deserialize(json["storage"]) : null,
      compute: json.containsKey("compute") ? ComputeItem().deserialize(json["compute"]) : null,
      workflow: json.containsKey("workflow") ? WorkflowItem().deserialize(json["workflow"]) : null,
    );
  }
  @override Map<String, dynamic> serialize() { 
    return {
      "id": id,
      "width": width,
      "height": height,
      "data": data?.serialize(),
      "processing": processing?.serialize(),
      "storage": storage?.serialize(),
      "compute": compute?.serialize(),
      "workflow": workflow?.serialize(),
      "position": position?.serialize(),
    };
  }
}

class Position extends SerializerDeserializer<Position> {
  String? id;
  double? x;
  double? y;

  Position({
    this.id,
    this.x,
    this.y,
  });

  @override deserialize(dynamic json) {
    try { json = json as Map<String, dynamic>;
    } catch (e) { return Position(); }   
    return Position(
      id: json.containsKey("id") ? json["id"] : null,
      x: json.containsKey("x") ? double.parse(json["x"].toString()) : null,
      y: json.containsKey("y") ? double.parse(json["y"].toString()) : null,
    );
  }
  @override Map<String, dynamic> serialize() => {
    "id": id,
    "x": x ,
    "y": y,
  };
}