#!/usr/bin/env python3

"""
This script uses the i3ipc python module to switch the layout splith / splitv
for the currently focused window, depending on its dimensions.
It works on both sway and i3 window managers.

Inspired by https://github.com/olemartinorg/i3-alternating-layout

Copyright: 2019-2021 Piotr Miller & Contributors
e-mail: [email protected]
Project: https://github.com/nwg-piotr/autotiling
License: GPL3

Dependencies: python-i3ipc>=2.0.1 (i3ipc-python)
"""
import argparse
import os
import sys
from functools import partial

from i3ipc import Connection, Event

try:
   from .__about__ import __version__
except ImportError:
   __version__ = "unknown"


def temp_dir():
   return os.getenv("TMPDIR") or os.getenv("TEMP") or os.getenv("TMP") or "/tmp"


def save_string(string, file_path):
   try:
       with open(file_path, "wt") as file:
           file.write(string)
   except Exception as e:
       print(e)


def output_name(con):
   if con.type == "root":
       return None

   if p := con.parent:
       if p.type == "output":
           return p.name
       else:
           return output_name(p)


def switch_splitting(i3, e, debug, outputs, workspaces, depth_limit, splitwidth, splitheight, splitratio):
   try:
       con = i3.get_tree().find_focused()
       output = output_name(con)
       # Stop, if outputs is set and current output is not in the selection
       if outputs and output not in outputs:
           if debug:
               print(f"Debug: Autotiling turned off on output {output}", file=sys.stderr)
           return

       if con and not workspaces or (str(con.workspace().num) in workspaces):
           if con.floating:
               # We're on i3: on sway it would be None
               # May be 'auto_on' or 'user_on'
               is_floating = "_on" in con.floating
           else:
               # We are on sway
               is_floating = con.type == "floating_con"

           if depth_limit:
               # Assume we reached the depth limit, unless we can find a workspace
               depth_limit_reached = True
               current_con = con
               current_depth = 0
               while current_depth < depth_limit:
                   # Check if we found the workspace of the current container
                   if current_con.type == "workspace":
                       # Found the workspace within the depth limitation
                       depth_limit_reached = False
                       break

                   # Look at the parent for next iteration
                   current_con = current_con.parent

                   # Only count up the depth, if the container has more than
                   # one container as child
                   if len(current_con.nodes) > 1:
                       current_depth += 1

               if depth_limit_reached:
                   if debug:
                       print("Debug: Depth limit reached")
                   return

           is_full_screen = con.fullscreen_mode == 1
           is_stacked = con.parent.layout == "stacked"
           is_tabbed = con.parent.layout == "tabbed"

           # Exclude floating containers, stacked layouts, tabbed layouts and full screen mode
           if (not is_floating
                   and not is_stacked
                   and not is_tabbed
                   and not is_full_screen):
               new_layout = "splitv" if con.rect.height > con.rect.width / splitratio else "splith"

               if new_layout != con.parent.layout:
                   result = i3.command(new_layout)
                   if result[0].success and debug:
                       print(f"Debug: Switched to {new_layout}", file=sys.stderr)
                   elif debug:
                       print(f"Error: Switch failed with err {result[0].error}", file=sys.stderr)

               if e.change in ["new", "move"] and con.percent:
                   if con.parent.layout == "splitv" and splitheight != 1.0:  # top / bottom
                       # print(f"split top fac {splitheight*100}")
                       i3.command(f"resize set height {int(con.percent * splitheight * 100)} ppt")
                   elif con.parent.layout == "splith" and splitwidth != 1.0:  # top / bottom:                     # left / right
                       # print(f"split right fac {splitwidth*100} ")
                       i3.command(f"resize set width {int(con.percent * splitwidth * 100)} ppt")

       elif debug:
           print("Debug: No focused container found or autotiling on the workspace turned off", file=sys.stderr)

   except Exception as e:
       print(f"Error: {e}", file=sys.stderr)


def get_parser():
   parser = argparse.ArgumentParser(prog="autotiling", description="Script for sway and i3 to automatically switch the horizontal / vertical window split orientation")

   parser.add_argument("-d", "--debug", action="store_true",
                       help="print debug messages to stderr")
   parser.add_argument("-v", "--version", action="version",
                       version=f"%(prog)s {__version__}, Python {sys.version}",
                       help="display version information")
   parser.add_argument("-o", "--outputs", nargs="*", type=str, default=[],
                       help="restricts autotiling to certain output; example: autotiling --output  DP-1 HDMI-0")
   parser.add_argument("-w", "--workspaces", nargs="*", type=str, default=[],
                       help="restricts autotiling to certain workspaces; example: autotiling --workspaces 8 9")
   parser.add_argument("-l", "--limit", type=int, default=0,
                       help='limit how often autotiling will split a container; '
                            'try "2" if you like master-stack layouts; default: 0 (no limit)')
   parser.add_argument("-sw",
                       "--splitwidth",
                       help='set the width of the vertical split (as factor); default: 1.0;',
                       type=float,
                       default=1.0, )
   parser.add_argument("-sh",
                       "--splitheight",
                       help='set the height of the horizontal split (as factor); default: 1.0;',
                       type=float,
                       default=1.0, )
   parser.add_argument("-sr",
                       "--splitratio",
                       help='Split direction ratio - based on window height/width; default: 1;'
                            'try "1.61", for golden ratio - window has to be 61%% wider for left/right split; default: 1.0;',
                       type=float,
                       default=1.0, )

   """
   Changing event subscription has already been the objective of several pull request. To avoid doing this again
   and again, let's allow to specify them in the `--events` argument.
   """
   parser.add_argument("-e", "--events", nargs="*", type=str, default=["WINDOW", "MODE"],
                       help="list of events to trigger switching split orientation; default: WINDOW MODE")

   return parser

def main():
   args = get_parser().parse_args()

   if args.debug:
       if args.outputs:
           print(f"autotiling is only active on outputs: {','.join(args.outputs)}")
       if args.workspaces:
           print(f"autotiling is only active on workspaces: {','.join(args.workspaces)}")

   # For use w/ nwg-panel
   ws_file = os.path.join(temp_dir(), "autotiling")
   if args.workspaces:
       save_string(','.join(args.workspaces), ws_file)
   else:
       if os.path.isfile(ws_file):
           os.remove(ws_file)

   if not args.events:
       print("No events specified", file=sys.stderr)
       sys.exit(1)

   handler = partial(
       switch_splitting,
       debug=args.debug,
       outputs=args.outputs,
       workspaces=args.workspaces,
       depth_limit=args.limit,
       splitwidth=args.splitwidth,
       splitheight=args.splitheight,
       splitratio=args.splitratio
   )
   i3 = Connection()
   for e in args.events:
       try:
           i3.on(Event[e], handler)
           print(f"{Event[e]} subscribed")
       except KeyError:
           print(f"'{e}' is not a valid event", file=sys.stderr)

   i3.main()


if __name__ == "__main__":
   main()