#!/bin/sh
#
# Copyright (C) 2021-2022 Claes Nästén
#

# pekwm configuration directory, setup by pekwm but use this to ensure
# that the script will run properly without running from pekwm.
if test -z "$PEKWM_CONFIG_PATH"; then
    PEKWM_CONFIG_PATH="$HOME/.pekwm"
fi

# URL for the pekwm theme site
PEKWM_THEMES="https://www.pekwm.se/themes"
# URL for the theme index file
REMOTE_THEME_INDEX="$PEKWM_THEMES/themes-v1.csv"
# Path to local copy of the theme index file
LOCAL_THEME_INDEX="$PEKWM_CONFIG_PATH/themes-v1.csv"
# Max age of the local theme index before it is re fetchted.
LOCAL_THEME_INDEX_MAX_AGE="86400"
# Path to local directory where screenshots are cached, used with the
# show command.
SCREENSHOT_CACHE_DIR="$HOME/.cache/pekwm-themes"
# Field delimiter
SEP="¤"

# Stop execution with exit code 1 after printing provided error
# message
#
# die error message
die()
{
    echo "ERROR: $@"
    exit 1
}

# Print progress message
#
# progress progress message
progress()
{
    echo "`date '+%Y%m%dT%H:%M:%S'`: $@"
}

# Print usage information
#
# usage exit-code
usage()
{
    echo "usage: $0 [install|uninstall|show|search|new|update]"
    echo ""
    echo "  install [-a] name   install theme"
    echo "  uninstall name      uninstall theme (removes local changes)"
    echo "  show name           display theme preview"
    echo "  search [pattern]    search for theme"
    echo "  new name            create new theme"
    echo "  update              update theme index"
    echo ""
    exit $1
}

# Set fetch_cmd and fetch_cmd_type
init_fetch_cmd()
{
    # OpenBSD and NetBSD ftp come with http(s) support,
    # fast forward and use it.
    case `uname -s` in
        OpenBSD|NetBSD)
            fetch_cmd="/usr/bin/ftp"
            fetch_cmd_type="ftp"
            return
            ;;
        *)
            ;;
    esac

    fetch_cmd=`which curl 2>/dev/null | /usr/bin/grep '^/'`
    if test "x$fetch_cmd" != "x"; then
        fetch_cmd_type="curl"
    else
        fetch_cmd=`which wget 2>/dev/null | /usr/bin/grep '^/'`
        if test "x$fetch_cmd" != "x"; then
            fetch_cmd_type="wget"
        else
            fetch_cmd=`which ftp 2>/dev/null | /usr/bin/grep '^/'`
            if test "x$fetch_cmd" != "x"; then
                $fetch_cmd -h 2>&1 | /usr/bin/grep -q http
                if $? -eq 0; then
                    fetch_cmd_type="ftp"
                fi
            else
                fetch_cmd_type="unknown"
            fi
        fi
    fi
}

# Fetch remote file using HTTP(S)/FTP.
#
# fetch_file url local-path
fetch_file()
{
    progress "fetching $1 to $2..."

    if test "x$fetch_cmd" = "x"; then
        init_fetch_cmd
    fi

    case $fetch_cmd_type in
        curl)
            $fetch_cmd -L -s -o "$2" "$1"
            ;;
        wget)
            $fetch_cmd -q -O "$2" "$1"
            ;;
        ftp)
            $fetch_cmd -V -o "$2" "$1"
            ;;
        *)
            die "no supported HTTP client (curl, wget, BSD ftp)"
            ;;
    esac
}

# Update local copy of the theme index
#
# update_index
update_index()
{
    if ! test -e "$PEKWM_CONFIG_PATH"; then
        mkdir -p "$PEKWM_CONFIG_PATH"
    fi
    if test -e "${LOCAL_THEME_INDEX}.stamp"; then
        created=`cat ${LOCAL_THEME_INDEX}.stamp`
        now=`pekwm_ctrl -a util timestamp 2>/dev/null`
        if test $? -eq 0; then
            age=$(($now - $created))
            if test $age -gt $LOCAL_THEME_INDEX_MAX_AGE; then
                rm -f "$LOCAL_THEME_INDEX"
            fi
        fi
    else
        # no stamp file exists, force a refresh
        rm -f "$LOCAL_THEME_INDEX"
    fi

    if test ! -e "$LOCAL_THEME_INDEX"; then
        fetch_file "$REMOTE_THEME_INDEX" "$LOCAL_THEME_INDEX"
        if test $? -ne 0; then
            die "failed to update theme index from $REMOTE_THEME_INDEX"
        fi
        pekwm_ctrl -a util timestamp >"${LOCAL_THEME_INDEX}.stamp" 2>/dev/null
        if test $? -ne 0; then
            rm -f "${LOCAL_THEME_INDEX}.stamp"
        fi
    fi
}

# Read theme data from the local theme index, populates theme_remote,
# theme_branch, theme_author and theme_title
#
# read_theme name
read_theme()
{
    line=`/usr/bin/sed 1d $LOCAL_THEME_INDEX | /usr/bin/grep "^$1$SEP"`
    if test -z "$line"; then
        die "no theme named $1"
    fi
    theme_name="$1"
    theme_remote=`echo $line | /usr/bin/awk -F "$SEP" '{ print $2 }'`
    theme_branch=`echo $line | /usr/bin/awk -F "$SEP" '{ print $3 }'`
    theme_base_path=`echo $line | /usr/bin/awk -F "$SEP" '{ print $4 }'`
    theme_author=`echo $line | /usr/bin/awk -F "$SEP" '{ print $6 }'`
    theme_title=`echo $line | /usr/bin/awk -F "$SEP" '{ print $7 }'`
}

# Set theme_path to local theme path
#
# set_theme_path name
set_theme_path()
{
    theme_path="$PEKWM_CONFIG_PATH/themes/$1"
}

# Install theme to $PEKWM_CONFIG_PATH/themes, if activate is given the
# current theme is changed.
#
# theme_install name [activate]
theme_install()
{
    read_theme "$1"
    set_theme_path "$1"

    if test -z "$theme_base_path"; then
        theme_install_plain
    else
        theme_install_base_path
    fi

    if test "x$2" = "xactivate"; then
        /usr/pkg/share/pekwm/scripts/pekwm_themeset.sh \
         "$PEKWM_CONFIG_PATH/themes" "$theme_path"
    fi
}

# Install/update theme residing in the root of the GIT repository (use
# the theme_ variables from read_theme)
#
# theme_install_plain
theme_install_plain()
{
    git_update_repo "$theme_remote" "$theme_branch" "$theme_path"
}

# Install/update theme not in the root of the GIT repository (use the
# theme_ variables from read_theme)
#
# theme_install_base_path
theme_install_base_path()
{
    mkdir -p "$PEKWM_CONFIG_PATH/themes_git"
    repo_path="$PEKWM_CONFIG_PATH/themes_git/`echo $theme_remote | tr -cd '[:alnum:]'`"
    git_update_repo "$theme_remote" "$theme_branch" "$repo_path"

    ln -sf "$repo_path/$theme_base_path" "$PEKWM_CONFIG_PATH/themes/$theme_name"
}

# Clone or update GIT repository
#
# remote branch local-path
git_update_repo()
{
    if test -d "$3"; then
        echo "$3 already exist, updating"
        git -C "$3" remote set-url origin "$1"
        git -C "$3" fetch origin
    else
        echo "cloning $1"
        git clone "$1" "$3"
    fi

    git -C "$3" checkout "$2"
    git -C "$3" reset --hard "origin/$2"
}

# Uninstall theme from $PEKWM_CONFIG_PATH/themes
#
# theme_uninstall name
theme_uninstall()
{
    read_theme $1
    set_theme_path $1
    if test -d "$theme_path"; then
        echo "uninstalling $1"
        rm -rf "$theme_path"
    fi
}

# Update local copy of theme screenshot
#
# update_theme_screenshot name
update_theme_screenshot()
{
    mkdir -p $SCREENSHOT_CACHE_DIR
    path="$SCREENSHOT_CACHE_DIR/$1.png"
    if test ! -e "$path"; then
        fetch_file $PEKWM_THEMES/$1.png $path
    fi
}

# Search for the
#
# theme_search [pattern]
theme_search()
{
    if test -z "$1"; then
        echo "available themes:"
    else
        echo "themes matching $1:"
    fi

    search="$1"
    if test -z "$search"; then
        search=".*"
    fi

    /usr/bin/sed 1d "$LOCAL_THEME_INDEX" | /usr/bin/awk -F "$SEP" '{ print $1 }' | /usr/bin/grep -- "$search"
}

# Show theme using pekwm_dialog, if install is choosen the theme is
# installed and activated.
#
# theme_show name
theme_show()
{
    read_theme "$1"
    set_theme_path "$1"
    update_theme_screenshot "$1"
    pekwm_dialog -i "$SCREENSHOT_CACHE_DIR/$1.png" -t "pekwm theme: $1" \
                 -o Install -o Close $theme_title
    if test $? -eq 0; then
        theme_install "$1" "activate"
    fi
}

# Generate a theme directory
#
# theme_new name
theme_new()
{
    theme_name="$1"

    if test -z "$theme_name"; then
        die "a theme name must be provided to pekwm_theme new"
    fi
    if test -e "$theme_name"; then
       die "a file or directory with the name $theme_name already exist"
    fi

    mkdir $theme_name || die "failed to create $theme_name"
    mkdir $theme_name/backgrounds \
          || die "failed to create $theme_name/backgrounds"

    cat > $theme_name/theme <<EOF
\$FONT = "Sans-12"
\$MENU_FONT = "Sans-12"

\$BG_TEX = "Solid #ffffff"

\$COLOR_FO = "#eeeeee"
\$COLOR_FO_HI = "#ffffff"
\$COLOR_FO_LO = "#dddddd"
\$COLOR_BD_FO = "#000000"
\$TEXT_FO = "#333333"

\$COLOR_FO_SEL = "#efefef"
\$COLOR_FO_SEL_HI = "#ffffff"
\$COLOR_FO_SEL_LO = "#dddddd"
\$TEXT_FO_SEL = "#000000"

\$COLOR_UN = "#aaaaaa"
\$COLOR_UN_HI = "#bbbbbb"
\$COLOR_UN_LO = "#999999"
\$COLOR_BD_UN = "#666666"
\$TEXT_UN = "#666666"

\$COLOR_UN_SEL = "#afafaf"
\$COLOR_UN_SEL_HI = "#bbbbbb"
\$COLOR_UN_SEL_LO = "#999999"
\$TEXT_UN_SEL = "#333333"

INCLUDE = "\$THEME_DIR/template"
EOF

    cat > $theme_name/template <<EOF
\$TEX_FO = "SolidRaised \$COLOR_FO \$COLOR_FO_HI \$COLOR_FO_LO 1 0"
\$TEX_FO_SEL = "SolidRaised \$COLOR_FO_SEL \$COLOR_FO_SEL_HI \$COLOR_FO_SEL_LO 1 0"
\$TEX_UN = "SolidRaised \$COLOR_UN \$COLOR_UN_HI \$COLOR_UN_LO 1 0"
\$TEX_UN_SEL = "SolidRaised \$COLOR_UN_SEL \$COLOR_UN_SEL_HI \$COLOR_UN_SEL_LO"
\$TEX_SEP_FO = "Solid \$COLOR_FO_HI 2x20"
\$TEX_SEP_UN = "Solid \$COLOR_UN_HI 2x20"

# Theme format version
Version = "2"

Require {
  Templates = "True"
}

Background {
  Texture = "\$BG_TEX"
}

ColorMaps {
  ColorMap = "Focused" {
    Map = "#aaaaaa" { To = "\$COLOR_FO" }
    Map = "#ffffff" { To = "\$COLOR_FO_LO" }
  }
  ColorMap = "Unfocused" {
    Map = "#aaaaaa" { To = "\$COLOR_UN" }
    Map = "#ffffff" { To = "\$COLOR_UN_LO" }
  }
  ColorMap = "Hoover" {
    Map = "#aaaaaa" { To = "\$COLOR_FO_SEL" }
    Map = "#ffffff" { To = "\$COLOR_FO_SEL_HI" }
    Map = "#000000" { To = "#ffffff" }
  }
  ColorMap = "Pressed" {
    Map = "#aaaaaa" { To = "\$COLOR_FO" }
    Map = "#ffffff" { To = "\$COLOR_FO_LO" }
    Map = "#000000" { To = "#ffffff" }
  }
}

Define = "Border" {
  Focused {
    TopLeft = "Solid \$COLOR_BD_FO 2x2"
    Top = "Solid \$COLOR_BD_FO 2x2"
    TopRight = "Solid \$COLOR_BD_FO 2x2"
    Left = "Solid \$COLOR_BD_FO 2x2"
    Right = "Solid \$COLOR_BD_FO 2x2"
    BottomLeft = "Solid \$COLOR_BD_FO 2x2"
    Bottom = "Solid \$COLOR_BD_FO 2x2"
    BottomRight = "Solid \$COLOR_BD_FO 2x2"
  }
  Unfocused {
    TopLeft = "Solid \$COLOR_BD_UN 2x2"
    Top = "Solid \$COLOR_BD_UN 2x2"
    TopRight = "Solid \$COLOR_BD_UN 2x2"
    Left = "Solid \$COLOR_BD_UN 2x2"
    Right = "Solid \$COLOR_BD_UN 2x2"
    BottomLeft = "Solid \$COLOR_BD_UN 2x2"
    Bottom = "Solid \$COLOR_BD_UN 2x2"
    BottomRight = "Solid \$COLOR_BD_UN 2x2"
  }
}

Define = "EmptyBorder" {
  Focused {
    TopLeft = "Empty"
    Top = "Empty"
    TopRight = "Empty"
    Left = "Empty"
    Right = "Empty"
    BottomLeft = "Empty"
    Bottom = "Empty"
    BottomRight = "Empty"
  }
  Unfocused {
    TopLeft = "Empty"
    Top = "Empty"
    TopRight = "Empty"
    Left = "Empty"
    Right = "Empty"
    BottomLeft = "Empty"
    Bottom = "Empty"
    BottomRight = "Empty"
  }
}

Define = "Buttons" {
  Right = "Close" {
    Focused = "ImageMapped Focused button-close.xpm"
    Unfocused = "ImageMapped Unfocused button-close.xpm"
    Hoover = "ImageMapped Hoover button-close.xpm"
    Pressed = "ImageMapped Pressed button-close.xpm"
    Button = "1" { Actions = "Close" }
    Button = "3" { Actions = "Kill" }
  }
}

Define = "Decor" {
  Height = "20"
  Pad = "2 2 5 5"

  Focused = "\$TEX_FO"
  Unfocused = "\$TEX_UN"

  Tab {
    Focused = "\$TEX_FO"
    FocusedSelected = "\$TEX_FO_SEL"
    Unfocused = "\$TEX_UN"
    UnfocusedSelected = "\$TEX_UN_SEL"
  }
  Separator {
    Focused = "\$TEX_SEP_FO"
    Unfocused = "\$TEX_SEP_UN"
  }
  Font {
    Focused = "\$FONT#Center"
  }
  FontColor {
    Focused = "\$TEXT_FO"
    FocusedSelected = "\$TEXT_FO_SEL"
    Unfocused = "\$TEXT_UN"
    UnfocusedSelected = "\$TEXT_UN_SEL"
  }
}

Define = "EmptyDecor" {
  Height = "0"
  Pad = "0 0 0 0"
  Focused = "Empty"
  Unfocused = "Empty"
  Tab {
    Focused = "Empty"
    FocusedSelected = "Empty"
    Unfocused = "Empty"
    UnfocusedSelected = "Empty"
  }
  Separator {
    Focused = "Empty"
    Unfocused = "Empty"
  }
  Font {
    Focused = "Empty"
  }
  FontColor {
    Focused = "Empty"
    FocusedSelected = "Empty"
    Unfocused = "Empty"
    UnfocusedSelected = "Empty"
  }
}

Decor = "Default" {
  @Decor
  Border {
    @Border
  }
  Buttons {
    @Buttons
  }
}

Decor = "InputDialog" {
  @EmptyDecor
  Border {
    @Border
  }
}

Decor = "Menu" {
  @Decor
  Border {
    @Border
  }
}

Decor = "StatusWindow" {
  @EmptyDecor
  Border {
    @Border
  }
}

Decor = "WorkspaceIndicator" {
  @EmptyDecor
  Border {
    @Border
  }
}

CmdDialog {
  Font = "\$FONT"
  Texture = "\$TEX_UN"
  Text = "\$TEXT_UN"
  Pad = "4 4 4 4"
}

Dialog {
  Background = "Solid \$COLOR_UN"
  TitleFont = "\$MENU_FONT"
  TitleColor = "\$TEXT_FO_SEL"
  Font = "\$FONT"
  Text = "\$TEXT_FO"
  Pad = "4 4 4 4"

  Button {
    Font = "\$FONT"
    Text = "\$TEXT_FO_SEL"

    Focused = "\$TEX_FO"
    Unfocused = "\$TEX_UN"
    Hoover = "\$TEX_FO_SEL"
    Pressed = "\$TEX_UN_SEL"
  }
}

Harbour {
  Texture = "\$TEX_UN"
}

Menu {
  Focused {
    Font = "\$MENU_FONT"
    Background = "\$TEX_FO"
    Item = "Empty"
    Text = "\$TEXT_FO"
    Separator = "Solid \$COLOR_FO_HI 1x1"
    Arrow = "Solid \$COLOR_FO_LO 5x5"
  }
  Unfocused {
    Font = "\$MENU_FONT"
    Background = "\$TEX_UN"
    Item = "Empty"
    Text = "\$TEXT_UN"
    Separator = "Solid \$COLOR_UN_HI 1x1"
    Arrow = "Solid \$COLOR_UN_LO 5x5"
  }
  Selected {
    Font = "\$MENU_FONT"
    Background = "\$TEX_FO_SEL"
    Item = "\$TEX_FO_SEL"
    Text = "\$TEXT_FO_SEL"
    Arrow = "Solid \$COLOR_FO_SEL_LO 5x5"
  }
  Pad = "2 2 4 4"
}

Status {
  Font = "\$FONT"
  Texture = "Solid \$COLOR_UN"
  Text = "\$TEXT_UN"
  Pad = "0 0 0 0"
}

WorkspaceIndicator {
  Font = "\$FONT#Center"
  Background = "Solid \$COLOR_UN"
  Workspace = "Solid \$COLOR_UN_SEL"
  WorkspaceActive = "Solid \$COLOR_FO_SEL"
  Text = "\$TEXT_FO"
  EdgePadding = "2"
  WorkspacePadding = "2"
}
EOF

    cat > $theme_name/button-close.xpm <<EOF
/* XPM */
static char *button_close[] = {
/* columns rows colors chars-per-pixel */
"20 20 3 1 ",
"  c #aaaaaaaaaaaa",
". c black",
"X c white",
/* pixels */
"XXXXXXXXXXXXXXXXXXXX",
"X..          ......X",
"X...        ...... X",
"X...       .....   X",
"X....     .....    X",
"X.....   .....     X",
"X ..... .....      X",
"X  .........       X",
"X   .......        X",
"X    .....         X",
"X    ......        X",
"X   ........       X",
"X  ..........      X",
"X .....  .....     X",
"X ....    .....    X",
"X....      .....   X",
"X....       ...... X",
"X...         ......X",
"X..           .....X",
"XXXXXXXXXXXXXXXXXXXX"
};
EOF

    echo "generated theme $theme_name"
}

# Update theme index
#
# theme_update_index
theme_update()
{
    rm -f "$LOCAL_THEME_INDEX.stamp" "$LOCAL_THEME_INDEX"
    update_index
}

if test -z "$PEKWM_CONFIG_PATH"; then
    die "none of \$HOME and \$PEKWM_CONFIG_PATH is set, can not proceed"
fi
if test -z "`which git 2>/dev/null | /usr/bin/grep '^/'`"; then
    die "git must be available in the path, can not proceed"
fi

if test -z "$1"; then
    usage 1
elif test "x$1" = "xupdate"; then
    theme_update
else
    update_index

    if test "x$1" = "xinstall"; then
        if test "$2" = "-a"; then
            theme_install "$3" "activate"
        else
            theme_install "$2"
        fi
    elif test "x$1" = "xuninstall"; then
        theme_uninstall "$2"
    elif test "x$1" = "xshow"; then
        theme_show "$2" "$3"
    elif test "x$1" = "xsearch"; then
        theme_search "$2"
    elif test "x$1" = "xnew"; then
        theme_new "$2"
    else
        usage 1
    fi
fi
