package com.github.bkhezry.weather.utils;

import android.annotation.SuppressLint;
import android.app.Application;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.text.Html;
import android.text.Layout;
import android.text.Spannable;
import android.text.TextUtils;
import android.text.style.ClickableSpan;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.TextView;

import androidx.annotation.CheckResult;
import androidx.annotation.ColorInt;
import androidx.annotation.FloatRange;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresPermission;
import androidx.appcompat.widget.AppCompatEditText;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.core.os.ConfigurationCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;

import com.bumptech.glide.Glide;
import com.github.bkhezry.weather.R;
import com.github.bkhezry.weather.listener.OnSetApiKeyEventListener;
import com.github.pwittchen.prefser.library.rx2.Prefser;

import java.lang.reflect.InvocationTargetException;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;

import static android.Manifest.permission.ACCESS_NETWORK_STATE;

public class AppUtil {
  private static Interpolator fastOutSlowIn;

  /**
   * Get timestamp of start of day 00:00:00
   *
   * @param calendar instance of {@link Calendar}
   * @return timestamp
   */
  public static long getStartOfDayTimestamp(Calendar calendar) {
    Calendar newCalendar = Calendar.getInstance(TimeZone.getDefault());
    newCalendar.setTimeInMillis(calendar.getTimeInMillis());
    newCalendar.set(Calendar.HOUR_OF_DAY, 0);
    newCalendar.set(Calendar.MINUTE, 0);
    newCalendar.set(Calendar.SECOND, 0);
    newCalendar.set(Calendar.MILLISECOND, 0);
    return newCalendar.getTimeInMillis();
  }

  /**
   * Get timestamp of end of day 23:59:59
   *
   * @param calendar instance of {@link Calendar}
   * @return timestamp
   */

  public static long getEndOfDayTimestamp(Calendar calendar) {
    Calendar newCalendar = Calendar.getInstance(TimeZone.getDefault());
    newCalendar.setTimeInMillis(calendar.getTimeInMillis());
    newCalendar.set(Calendar.HOUR_OF_DAY, 23);
    newCalendar.set(Calendar.MINUTE, 59);
    newCalendar.set(Calendar.SECOND, 59);
    newCalendar.set(Calendar.MILLISECOND, 0);
    return newCalendar.getTimeInMillis();
  }

  /**
   * Add days to calendar and return result
   *
   * @param cal  instance of {@link Calendar}
   * @param days number of days
   * @return instance of {@link Calendar}
   */
  public static Calendar addDays(Calendar cal, int days) {
    Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
    calendar.setTimeInMillis(cal.getTimeInMillis());
    calendar.add(Calendar.DATE, days);
    return calendar;
  }

  /**
   * Set icon to imageView according to weather code status
   *
   * @param context     instance of {@link Context}
   * @param imageView   instance of {@link android.widget.ImageView}
   * @param weatherCode code of weather status
   */
  public static void setWeatherIcon(Context context, AppCompatImageView imageView, int weatherCode) {
    if (weatherCode / 100 == 2) {
      Glide.with(context).load(R.drawable.ic_storm_weather).into(imageView);
    } else if (weatherCode / 100 == 3) {
      Glide.with(context).load(R.drawable.ic_rainy_weather).into(imageView);
    } else if (weatherCode / 100 == 5) {
      Glide.with(context).load(R.drawable.ic_rainy_weather).into(imageView);
    } else if (weatherCode / 100 == 6) {
      Glide.with(context).load(R.drawable.ic_snow_weather).into(imageView);
    } else if (weatherCode / 100 == 7) {
      Glide.with(context).load(R.drawable.ic_unknown).into(imageView);
    } else if (weatherCode == 800) {
      Glide.with(context).load(R.drawable.ic_clear_day).into(imageView);
    } else if (weatherCode == 801) {
      Glide.with(context).load(R.drawable.ic_few_clouds).into(imageView);
    } else if (weatherCode == 803) {
      Glide.with(context).load(R.drawable.ic_broken_clouds).into(imageView);
    } else if (weatherCode / 100 == 8) {
      Glide.with(context).load(R.drawable.ic_cloudy_weather).into(imageView);
    }
  }

  /**
   * Show fragment with fragment manager with animation parameter
   *
   * @param fragment        instance of {@link Fragment}
   * @param fragmentManager instance of {@link FragmentManager}
   * @param withAnimation   boolean value
   */
  public static void showFragment(Fragment fragment, FragmentManager fragmentManager, boolean withAnimation) {
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    if (withAnimation) {
      transaction.setCustomAnimations(R.anim.slide_up_anim, R.anim.slide_down_anim);
    } else {
      transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
    }
    transaction.add(android.R.id.content, fragment).addToBackStack(null).commit();
  }

  /**
   * Get time of calendar as 00:00 format
   *
   * @param calendar instance of {@link Calendar}
   * @param context  instance of {@link Context}
   * @return string value
   */
  public static String getTime(Calendar calendar, Context context) {
    int hour = calendar.get(Calendar.HOUR_OF_DAY);
    int minute = calendar.get(Calendar.MINUTE);
    String hourString;
    if (hour < 10) {
      hourString = String.format(Locale.getDefault(), context.getString(R.string.zero_label), hour);
    } else {
      hourString = String.format(Locale.getDefault(), "%d", hour);
    }
    String minuteString;
    if (minute < 10) {
      minuteString = String.format(Locale.getDefault(), context.getString(R.string.zero_label), minute);
    } else {
      minuteString = String.format(Locale.getDefault(), "%d", minute);
    }
    return hourString + ":" + minuteString;
  }

  /**
   * Get animation file according to weather status code
   *
   * @param weatherCode int weather status code
   * @return id of animation json file
   */
  public static int getWeatherAnimation(int weatherCode) {
    if (weatherCode / 100 == 2) {
      return R.raw.storm_weather;
    } else if (weatherCode / 100 == 3) {
      return R.raw.rainy_weather;
    } else if (weatherCode / 100 == 5) {
      return R.raw.rainy_weather;
    } else if (weatherCode / 100 == 6) {
      return R.raw.snow_weather;
    } else if (weatherCode / 100 == 7) {
      return R.raw.unknown;
    } else if (weatherCode == 800) {
      return R.raw.clear_day;
    } else if (weatherCode == 801) {
      return R.raw.few_clouds;
    } else if (weatherCode == 803) {
      return R.raw.broken_clouds;
    } else if (weatherCode / 100 == 8) {
      return R.raw.cloudy_weather;
    }
    return R.raw.unknown;
  }

  /**
   * Get weather status string according to weather status code
   *
   * @param weatherCode weather status code
   * @param isRTL       boolean value
   * @return String weather status
   */
  public static String getWeatherStatus(int weatherCode, boolean isRTL) {
    if (weatherCode / 100 == 2) {
      if (isRTL) {
        return Constants.WEATHER_STATUS_PERSIAN[0];
      } else {
        return Constants.WEATHER_STATUS[0];
      }
    } else if (weatherCode / 100 == 3) {
      if (isRTL) {
        return Constants.WEATHER_STATUS_PERSIAN[1];
      } else {
        return Constants.WEATHER_STATUS[1];
      }
    } else if (weatherCode / 100 == 5) {
      if (isRTL) {
        return Constants.WEATHER_STATUS_PERSIAN[2];
      } else {
        return Constants.WEATHER_STATUS[2];
      }
    } else if (weatherCode / 100 == 6) {
      if (isRTL) {
        return Constants.WEATHER_STATUS_PERSIAN[3];
      } else {
        return Constants.WEATHER_STATUS[3];
      }
    } else if (weatherCode / 100 == 7) {
      if (isRTL) {
        return Constants.WEATHER_STATUS_PERSIAN[4];
      } else {
        return Constants.WEATHER_STATUS[4];
      }
    } else if (weatherCode == 800) {
      if (isRTL) {
        return Constants.WEATHER_STATUS_PERSIAN[5];
      } else {
        return Constants.WEATHER_STATUS[5];
      }
    } else if (weatherCode == 801) {
      if (isRTL) {
        return Constants.WEATHER_STATUS_PERSIAN[6];
      } else {
        return Constants.WEATHER_STATUS[6];
      }
    } else if (weatherCode == 803) {
      if (isRTL) {
        return Constants.WEATHER_STATUS_PERSIAN[7];
      } else {
        return Constants.WEATHER_STATUS[7];
      }
    } else if (weatherCode / 100 == 8) {
      if (isRTL) {
        return Constants.WEATHER_STATUS_PERSIAN[8];
      } else {
        return Constants.WEATHER_STATUS[8];
      }
    }
    if (isRTL) {
      return Constants.WEATHER_STATUS_PERSIAN[4];
    } else {
      return Constants.WEATHER_STATUS[4];
    }
  }

  /**
   * If thirty minutes is pass from parameter return true otherwise return false
   *
   * @param lastStored timestamp
   * @return boolean value
   */
  public static boolean isTimePass(long lastStored) {
    return System.currentTimeMillis() - lastStored > Constants.TIME_TO_PASS;
  }

  /**
   * Showing dialog for set api key value
   *
   * @param context  instance of {@link Context}
   * @param prefser  instance of {@link Prefser}
   * @param listener instance of {@link OnSetApiKeyEventListener}
   */
  public static void showSetAppIdDialog(Context context, Prefser prefser, OnSetApiKeyEventListener listener) {
    final Dialog dialog = new Dialog(context);
    dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); // before
    dialog.setContentView(R.layout.dialog_set_appid);
    dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
    dialog.setCancelable(false);
    WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
    lp.copyFrom(dialog.getWindow().getAttributes());
    lp.width = WindowManager.LayoutParams.MATCH_PARENT;
    lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
    dialog.getWindow().setAttributes(lp);
    dialog.findViewById(R.id.open_openweather_button).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        Intent i = new Intent(Intent.ACTION_VIEW,
            Uri.parse(Constants.OPEN_WEATHER_MAP_WEBSITE));
        context.startActivity(i);
      }
    });
    dialog.findViewById(R.id.store_button).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        AppCompatEditText apiKeyEditText = dialog.findViewById(R.id.api_key_edit_text);
        String apiKey = apiKeyEditText.getText().toString();
        if (!apiKey.equals("")) {
          prefser.put(Constants.API_KEY, apiKey);
          listener.setApiKey();
          dialog.dismiss();
        }
      }
    });
    dialog.show();
  }

  /**
   * Set text of textView with html format of html parameter
   *
   * @param textView instance {@link TextView}
   * @param html     String
   */
  @SuppressLint("ClickableViewAccessibility")
  public static void setTextWithLinks(TextView textView, CharSequence html) {
    textView.setText(html);
    textView.setOnTouchListener(new View.OnTouchListener() {
      @Override
      public boolean onTouch(View v, MotionEvent event) {
        int action = event.getAction();
        if (action == MotionEvent.ACTION_UP ||
            action == MotionEvent.ACTION_DOWN) {
          int x = (int) event.getX();
          int y = (int) event.getY();

          TextView widget = (TextView) v;
          x -= widget.getTotalPaddingLeft();
          y -= widget.getTotalPaddingTop();

          x += widget.getScrollX();
          y += widget.getScrollY();

          Layout layout = widget.getLayout();
          int line = layout.getLineForVertical(y);
          int off = layout.getOffsetForHorizontal(line, x);

          ClickableSpan[] link = Spannable.Factory.getInstance()
              .newSpannable(widget.getText())
              .getSpans(off, off, ClickableSpan.class);

          if (link.length != 0) {
            if (action == MotionEvent.ACTION_UP) {
              link[0].onClick(widget);
            }
            return true;
          }
        }
        return false;
      }
    });
  }

  /**
   * Change string to html format
   *
   * @param htmlText String text
   * @return String text
   */
  public static CharSequence fromHtml(String htmlText) {
    if (TextUtils.isEmpty(htmlText)) {
      return null;
    }
    CharSequence spanned;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
      spanned = Html.fromHtml(htmlText, Html.FROM_HTML_MODE_LEGACY);
    } else {
      spanned = Html.fromHtml(htmlText);
    }
    return trim(spanned);
  }

  /**
   * Trim string text
   *
   * @param charSequence String text
   * @return String text
   */

  private static CharSequence trim(CharSequence charSequence) {
    if (TextUtils.isEmpty(charSequence)) {
      return charSequence;
    }
    int end = charSequence.length() - 1;
    while (Character.isWhitespace(charSequence.charAt(end))) {
      end--;
    }
    return charSequence.subSequence(0, end + 1);
  }

  /**
   * Check version of SDK
   *
   * @param version int SDK version
   * @return boolean value
   */
  static boolean isAtLeastVersion(int version) {
    return Build.VERSION.SDK_INT >= version;
  }

  /**
   * Check current direction of application. if is RTL return true
   *
   * @param context instance of {@link Context}
   * @return boolean value
   */
  public static boolean isRTL(Context context) {
    Locale locale = ConfigurationCompat.getLocales(context.getResources().getConfiguration()).get(0);
    final int directionality = Character.getDirectionality(locale.getDisplayName().charAt(0));
    return directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
        directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC;
  }

  /**
   * Network status functions.
   */

  @SuppressLint("StaticFieldLeak")
  private static Application sApplication;


  private static void init(final Application app) {
    if (sApplication == null) {
      if (app == null) {
        sApplication = getApplicationByReflect();
      } else {
        sApplication = app;
      }
    } else {
      if (app != null && app.getClass() != sApplication.getClass()) {
        sApplication = app;
      }
    }
  }

  public static Application getApp() {
    if (sApplication != null) return sApplication;
    Application app = getApplicationByReflect();
    init(app);
    return app;
  }

  private static Application getApplicationByReflect() {
    try {
      @SuppressLint("PrivateApi")
      Class<?> activityThread = Class.forName("android.app.ActivityThread");
      Object thread = activityThread.getMethod("currentActivityThread").invoke(null);
      Object app = activityThread.getMethod("getApplication").invoke(thread);
      if (app == null) {
        throw new NullPointerException("u should init first");
      }
      return (Application) app;
    } catch (NoSuchMethodException | IllegalAccessException |
        InvocationTargetException | ClassNotFoundException e) {
      e.printStackTrace();
    }
    throw new NullPointerException("u should init first");
  }

  /**
   * If network connection is connect, return true
   *
   * @return boolean value
   */
  @RequiresPermission(ACCESS_NETWORK_STATE)
  public static boolean isNetworkConnected() {
    NetworkInfo info = getActiveNetworkInfo();
    return info != null && info.isConnected();
  }

  /**
   * Get activity network info instace
   *
   * @return instance of {@link NetworkInfo}
   */

  @RequiresPermission(ACCESS_NETWORK_STATE)
  private static NetworkInfo getActiveNetworkInfo() {
    ConnectivityManager cm =
        (ConnectivityManager) getApp().getSystemService(Context.CONNECTIVITY_SERVICE);
    if (cm == null) return null;
    return cm.getActiveNetworkInfo();
  }

  /**
   * Determine if the navigation bar will be on the bottom of the screen, based on logic in
   * PhoneWindowManager.
   */
  static boolean isNavBarOnBottom(@NonNull Context context) {
    final Resources res = context.getResources();
    final Configuration cfg = context.getResources().getConfiguration();
    final DisplayMetrics dm = res.getDisplayMetrics();
    boolean canMove = (dm.widthPixels != dm.heightPixels &&
        cfg.smallestScreenWidthDp < 600);
    return (!canMove || dm.widthPixels < dm.heightPixels);
  }

  static Interpolator getFastOutSlowInInterpolator(Context context) {
    if (fastOutSlowIn == null) {
      fastOutSlowIn = AnimationUtils.loadInterpolator(context,
          android.R.interpolator.fast_out_slow_in);
    }
    return fastOutSlowIn;
  }

  /**
   * Set the alpha component of {@code color} to be {@code alpha}.
   */
  static @CheckResult
  @ColorInt
  int modifyAlpha(@ColorInt int color,
                  @IntRange(from = 0, to = 255) int alpha) {
    return (color & 0x00ffffff) | (alpha << 24);
  }

  /**
   * Set the alpha component of {@code color} to be {@code alpha}.
   */
  public static @CheckResult
  @ColorInt
  int modifyAlpha(@ColorInt int color,
                  @FloatRange(from = 0f, to = 1f) float alpha) {
    return modifyAlpha(color, (int) (255f * alpha));
  }
}