﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using deleteonerror.GeoIP.Model;

namespace deleteonerror.GeoIP.Service
{
  /// <summary>
  /// 
  /// </summary>
  internal class DataService
  {
    // ReSharper disable once AssignNullToNotNullAttribute
    /// <summary>
    /// The database path
    /// </summary>
    public static string DbPath = new Uri(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase), "GeoLite2")).LocalPath;

    private static DataService _instance;
    private DataService()
    {

    }

    /// <summary>
    /// Gets the Service.
    /// </summary>
    /// <value>
    /// returns the instance of the Service
    /// </value>
    public static DataService Instance => _instance ?? (_instance = new DataService());

    /// <summary>
    /// Gets or sets a value indicating whether [use city resolution].
    /// </summary>
    /// <value>
    ///   <c>true</c> if [use city resolution]; otherwise, use country resolution <c>false</c>.
    /// </value>
    public bool UseCityResolution { get; set; }
    /// <summary>
    /// Gets or sets a value indicating whether [to use ip v6 blocks].
    /// </summary>
    /// <value>
    ///   <c>true</c> if [use ip v6]; otherwise, [use ip v4] <c>false</c>.
    /// </value>
    public bool UseIPv6Blocks { get; set; }

    private List<GeoLiteBlock> _geoLiteBlocks;
    /// <summary>
    /// Gets the geo lite IP blocks.
    /// </summary>
    /// <value>
    /// The geo lite IP blocks.
    /// </value>
    public List<GeoLiteBlock> GeoLiteBlocks
    {
      get
      {
        if (_geoLiteBlocks == null)
        {
          Initialize();
        }

        return _geoLiteBlocks;
      }
    }

    private List<GeoLiteLocation> _geoLiteLocations;

    /// <summary>
    /// Gets the geo lite locations.
    /// </summary>
    /// <value>
    /// The geo lite locations.
    /// </value>
    public List<GeoLiteLocation> GeoLiteLocations
    {
      get
      {
        if (_geoLiteLocations == null)
        {
          Initialize();
        }
        return _geoLiteLocations;
      }
    }

    /// <summary>
    /// Initializes this Service.
    /// if UseCityResolution is set to true it will update the cities from maxmind, 
    /// otherwise it will update the countries from maxmind.
    /// We also add the Privat IP Ranges descriped in RFC1918 to the locations
    /// </summary>
    private void Initialize()
    {
      if (UseCityResolution)
      {
        DatabaseUpdateService.Instance.UpdateCities();
      }
      else
      {
        DatabaseUpdateService.Instance.UpdateCoutries();
      }

      _geoLiteBlocks = GetBlocks();
      _geoLiteLocations = GetLocations();

      _geoLiteLocations.Add(new GeoLiteLocation
      {
        CountryName = "Country not found",
        Id = int.MinValue
      });
      _geoLiteLocations.Add(new GeoLiteLocation
      {
        CountryName = "Private IP Range",
        Id = int.MinValue + 1
      });
      _geoLiteBlocks.Add(new GeoLiteBlock
      {
        Country = int.MinValue + 1,
        FirstIP = "10.0.0.0",
        LastIP = "10.255.255.255",
        FirstNumericIP = IP.ToV4NumericIP("10.0.0.0"),
        LastNumericIP = IP.ToV4NumericIP("10.255.255.255")
      });
      _geoLiteBlocks.Add(new GeoLiteBlock
      {
        Country = int.MinValue + 1,
        FirstIP = "172.16.0.0",
        LastIP = "172.31.255.255",
        FirstNumericIP = IP.ToV4NumericIP("172.16.0.0"),
        LastNumericIP = IP.ToV4NumericIP("172.31.255.255")
      });
      _geoLiteBlocks.Add(new GeoLiteBlock
      {
        Country = int.MinValue + 1,
        FirstIP = "192.168.0.0",
        LastIP = "192.168.255.255",
        FirstNumericIP = IP.ToV4NumericIP("192.168.0.0"),
        LastNumericIP = IP.ToV4NumericIP("192.168.255.255")
      });
    }

    /// <summary>
    /// Gets the ip blocks from the CSV file.
    /// </summary>
    /// <returns>a List of geolite ip blocks</returns>
    private List<GeoLiteBlock> GetBlocks()
    {
      var path = "GeoLite2-";

      if (UseCityResolution)
      {
        path += "City-Blocks-IPv";
      }
      else
      {
        path += "Country-Blocks-IPv";
      }

      if (UseIPv6Blocks)
      {
        path += "6.csv";
      }
      else
      {
        path += "4.csv";
      }

      var file = Path.Combine(DbPath, path);


      if (!File.Exists(file))
      {
        if (UseCityResolution)
        {
          DatabaseUpdateService.Instance.UpdateCities();
        }
        else
        {
          DatabaseUpdateService.Instance.UpdateCoutries();
        }
      }
      string[] lines = { };
      try
      {
        lines = File.ReadAllLines(file);
      }
      catch (Exception e)
      {
        //Log.Error(e);
      }


      return lines.Skip(1).Select(line => new GeoLiteBlock(line)).ToList();
    }

    /// <summary>
    /// Gets the locations from the CSV file.
    /// </summary>
    /// <returns>A List of geolite Locations</returns>
    private List<GeoLiteLocation> GetLocations()
    {
      var path = "GeoLite2-";

      if (UseCityResolution)
      {
        path += "City-Locations-en.csv";
      }
      else
      {
        path += "Country-Locations-en.csv";
      }

      var file = Path.Combine(DbPath, path);

      if (!File.Exists(file))
      {
        if (UseCityResolution)
        {
          DatabaseUpdateService.Instance.UpdateCities();
        }
        else
        {
          DatabaseUpdateService.Instance.UpdateCoutries();
        }
      }
      string[] lines = { };
      try
      {
        lines = File.ReadAllLines(file);
      }
      catch (Exception e)
      {
        //Log.Error(e);
      }

      return lines.Skip(1).Select(line => new GeoLiteLocation(line)).ToList();
    }

  }
}
